// ==UserScript==
// @name           Dot Earth Defender aka NYTimes Blog Valet
// @namespace      warming101.com
// @description    NYTimes blog comment reformatting script 
// @include        http://*.blogs.nytimes.com/*
// @include        http://*.dblogs.nytimes.com/*
// @author         anna haynes
// ==/UserScript==
// -----------------------------------------------------
// This script is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.

var gsVersion = '4.0' //  for recent version history, see below after 'most useful features'

var gsOurBaseURL = GetBaseURL();


GM_log(gsOurBaseURL);
var gsBlogname = BlognameFrURL(gsOurBaseURL);
var gbItsDED = (gsBlogname=='dotearth');

gsOurBaseURL='';

var gsScriptname = (gbItsDED)?	'Dot Earth Defender' : 'NYTimes Blog Valet';
var gsScriptPurpose =	(gbItsDED)? 'your handy horror remover' : 'redressing the comments to suit you better';
var gsBlogHostname = (gbItsDED)?	"ANDY REVKIN" : ''; // for use if host participates in comments; if length is 0, participation is overlooked, 

var gsScriptRequirement='under Firefox with Greasemonkey add-on'; 
var gsScriptHomePage =	'http://www.warming101.com/dotearth/';


var maxCommentsPerPage = 25; // alas...
/* 

-----------------------------------------------------
This script's most useful features
-----------------------------------

Commenting:  
  * "Preview my comment" functionality
  * Simple 1-click "Reply to this comment" (which will pre-fill-in the comment form's textfield with basic info about(&link back to) the comment you're replying to)
  * Reciprocal links so you can bounce back and forth between your reply and the comment(s) you're replying to.

Reading:
  * At-a-glance list of links to recent posts (currently about 2 weeks' worth)
	* Above the comments, a list (with links) of people who've commented on this post, you can find your favorites.
  * Comment threading - for each comment, you can see (pretty much) which comments it's a reply to, and which comments reply to it.  And you can easily jump back and forth along the chain of replies, since the comment#s are displayed as clickable links.
	* A shrink-expand button for each comment.

Filtering
  * Comment recommendations - 1-click "recommend" of especially good comments;  NOTE: for this to do any good, you must comment, your recommendations will be appended to your comment.
 	* (Dot Earth only) somewhat configurable "credibility classes" of regular commenters, for determining who to shrink and whose recommendations are credible. 
  * (on Dot Earth blog only, and only if desired) Automatically shrink the comments of (in your view) utterly useless commenters.

-----------------------------------------------------
Recent version history (more at warming101.com/dotearth/DEDFeatureHistory.html)
-----------------------------------

v4.0: 
	* Fixed so it'll work after a fashion with the NYTimes's new (paginated comments) awful wordpress format 
		(sept 7 2008) - HOWEVER alas I haven't (yet?) been able to glue 
		the subpages back together successfully.  
		And permalinks to comments on other pages won't work well.
		And I did this one in a rush, so the code is a bit of a mess with piles of debug stuff, sections that don't work but might some day, etc
	* Categorized some commenters.
v3.1.2: 
	* There's a simpler way to get a list of recent posts.  We use it now.
v3.1.1: 
	* oops - the initial prompt popup's text was too long.
v3.1.0: 
	* User-friendly archive (recent posts list) - about a week of posts.
	* The script now displays a notice on the page to let you know it's running.
	* Sped up page loading (now we don't re-render on every change).
	* Script now runs on blogposts even if they don't have any comments yet.

*/

// ------------------------------------------
// Commenter names and categories
// (these are ignored if we're not on Dot Earth)

// climate scientists
var clisciNames = [
		'arthur smith', 'eli rabett', 'gavin schmidt', 'michael tobis', 'jamie morison', 'raypierre', 'josh willis', 'bill patzert','stephen schneider','naomi oreskes', 'anthony socci', 'richard somerville', 'james risbey'
		];

// very smart guys and journalists		
var layexpertNames = [
		'a siegel', 'andy revkin', 'andrew c. revkin','andrew revkin','bud ward', 'charlie petit', 'dano', 'david b. benson', 'david goldston','dick purcell', 'eric roston', 'hank roberts', 'joe romm', 'john mashey', 'lee schipper', 'mike roddy','randy olson', 'steve bloom', 'wayne hamilton','zeke hausfather'
	];
	
	// others	
var regularNames = [
		'adelbert','alex washburne', 'anna haynes', 'charles kay','chris colose','coolplanet','danny bloom', 'david lewis','david lews','donna light-donovan','edwin hall', 'elizabeth tjader', 'eric cato', 'gary peters', 'geny', 'george mobus', 'greenpa', 'guido', 'ian mcgregor', 'jeff huggins', 'james charles wilson', 'john hunka', 'josh hill', 'karl s schwartz', 'kit stolz', 'laurie dougherty', 'lou grinzo', 'luke silburn','magne karlsen', 'meltwater', 'meltyman', 'mitch golden', 'michael may', 'michael may a3k',  'neilt', 'paul t. horan','ray ladbury', 'richard schumacher', 'richard b. simon', 'rm reiss', 'scott wahlstrom', 'swatter0', 'spencer b.', 'steve bolger', 'steven earl salmony', 'tenney naumer', 'trinifar',  'wang suya', 'whitebeard'
	];
	
// commenters at odds with the science
var denierNames = [
		'aaron kramer','alan scott','arvin schloos','bill-tb','billtb', 'bob b', 'bob odonnellwoodstock',	'commanderbill','dave from ga.','des','doug l','e. oneal','elery fudge','e.patrick mosman','e. patrick mosman','flip','frank','fred moore','giovanni','greg burton', 'jamesg','jepe','jerry magnan', 'jeff martin', 'julian flood','larry g.','laurence dishman','ken maize', 'kim','leebert','lilith taveril','martin dijkema','mike mangan','mike m.','patrick henry', 'paminator', 'richard patton','robert verdi','roberts','sanjong thapa','sas','sashka', 'shane murphy','spalding craft','spinsir','steven goddard','steven rowitt', 'stanley kerns','syl','tilo reber','isodope', 'david gladstone'
		];

// ----------------
function BlognameFrURL(surlOrig) {
	var surl = surlOrig.toLowerCase();
	var re = /dotearth/;
	if (re.test(surl)) {
		return 'dotearth'; // might be a file locally, so http wouldnt work
	} else {
		return surl.replace(/\..*/,'').replace(/http:../,'').toLowerCase();
	}
}

// ----------------
function NumItemsInHash(ahash)
{
	var n=0;
	for (var x in ahash) {
		++n;
	}
	return n;
}
function trimmed(s)
{
	return s.replace(/^\s*/,'').replace(/\s*$/,'');
}
function dequoted(s)
{
	return s.replace(/"/mg,'-');
}
function clientjsd(s)
{
	return dequoted(s).replace(/\n/mg,'\\n');
}

// ----------------

// dunno if these 2 are needed:
function GM_safesetValue(skey,sval) {
	GM_setValue(escape(skey), escape(sval));
}
function GM_safegetValue(skey) {
	var ret = unescape(GM_getValue(escape(skey)));
	return (ret=='undefined')? '' : ret;
}
// -------
function Str2FrHash(slabel, ahash) // not bold
{
	var sitems = [];
	for (var k in ahash) {
		sitems.push(k + ':' + (ahash[k].length)? ahash[k] : '[none]' );
	}
	return (slabel + ':\n' + sitems.join(","));
}
// ----------
function StrFrHash(slabel, ahash) // bold
{
	var sitems = [];
	for (var k in ahash) {
		if (ahash[k].length) {
			sitems.push('<b>' + k + '</b>:\n' + ahash[k]); 
	} }
	return (slabel + ':\n' + sitems.join("\n"));
}
// ----------
function ReportHash(slabel, ahash) 
{
	var sitems = [];
	var n=0;
	for (var k in ahash) {
		sitems.push(k + ':' + ahash[k]);
		++n;
	}
	GM_log(slabel + '(' + n + ' items):\n' + sitems.join("\n"));
}
// ----------
function ReportGMSettingsHash(ahash) 
{
	var sitems = [];
	var n=0;
	for (var k in ahash) {
		sitems.push(k + ':' + ahash[k]);
		if (ahash[k] !=  GM_safegetValue(k)) {
			sitems.push('(differs from persistent! i.e. ' + GM_safegetValue(k));
		}
		++n;
	}
	GM_log(n + ' items in SettingsHash:\n' + sitems.join("\n"));
}
// ----------
function UpdateLastModDate()
{
			var d = new Date();
			GM_safesetValue('lastmod',d.toLocaleString());
}
function LastModDate()
{
			return GM_safegetValue('lastmod');
}

// -------------------------------------	
function ResponseToBoolPrompt(sprompt) // y, n, '' or null
{
	return (prompt(sprompt + '(y/n)').toLowerCase())
}
// ------------
function AskNewValueForItem(bUseCurrentIfUserSkips, 
		skey, sCurrentVal, sprompt, defaultval)
{
		var sHintCurr = (!bUseCurrentIfUserSkips)? '' : 
			" (currently '" + sCurrentVal + "'- just hit Enter (or OK) to keep it)";
		
		var sval = prompt('Please set ' + skey + ': ' + sprompt + "\n (default=" + defaultval + ")" + sHintCurr );
		if (sval==null) {	// user hit Cancel
			return null;
		}
		if (!sval.length ) {
			sval = (bUseCurrentIfUserSkips)? sCurrentVal : defaultval;	// cant be undef
		}
		return trimmed(sval);
}
// ------------
function InitSavedTBVSettings(iforceEditVal) // 0=use existing saved settings, 1=ask if edit, 2=edit
{
	var bMightEdit = (iforceEditVal>0);
	// GM_log("InitSavedTBVSettings(iforceEditVal)" + iforceEditVal);
	var tmpcfg = new Array (
		{name:"myname", default:'', prompt:"the name you want in the comment form" },
		{name:"itsreal", default:'n', prompt:"is that your real (first and last) name?  y or n" },
		{name:"myemail", default:'', prompt:"the email address you want in the comment form" },
		{name:"labelauthors", default:'n', prompt:"y=do (e.g. 'clisci'), n=dont." },
		{name:"filtermode", default:0, prompt:"0=trust(view) all, 1=trust climate scientists, 2=trust Rush Limbaugh" },
		{name:"shrinkdistrusted", default:'y', prompt:"Initially shrink distrusted commenters? y/n" },
		{name:"safemode", default:0, prompt:"a secondary filter - 0=normal, 1 if comments are noisy, 2 if horrific" },
		{name:"brandmycomment", default:'y', prompt:"Add 'Using [scriptname]' statement to your comment? y/n" }
	//	, {name:"mysig", default:'', prompt:"your 'signature', appended to every comment" }
	);
	// only relevant for Dot Earth:
	var filterkeys = ["labelauthors","filtermode","shrinkdistrusted","safemode"];
	
	var bchanged=false;
	var bAlreadysetfiltermode=false;
	
	// first, if D.E. and new user (or if existing user wants to set settings), ask about filtering

	var smid = '';
	if (bMightEdit && gbItsDED) {
		var slist = "U.S. National Academy of Sciences; American Association for the Advancement of Science (AAAS); American Meteorological Society (AMS); National Research Council; Canadian Meteorological and Oceanographic Society (CMOS); Federal Climate Change Science Program; National Oceanic and Atmospheric Administration (NOAA); American Geophysical Union; Geological Society of America; American Chemical Society; Federal Climate Change Science Program, 2006 - commissioned by the Bush administration in 2002; Engineers Australia (The Institution of Engineers Australia); American Association of State Climatologists; US Geological Survey (USGS); National Center for Atmospheric Research (NCAR) ; NASA's Goddard Institute of Space Studies (GISS); World Meteorological Organization; Canadian Foundation for Climate and Atmospherice Sciences; International Council on Science; Environmental Protection Agency (EPA); American Astronomical Society; Chinese Academy of Sciences, China; Royal Society, United Kingdom; Russian Academy of Sciences, Russia; Academia Brasiliera de Ciencias, Brazil; Royal Society of Canada, Canada; Academie des Sciences, France; Deutsche Akademie der Naturforscher, Germany; Indian National Science Academy, India; Accademia dei Lincei, Italy; Science Council of Japan, Japan; Australian  Academy of Sciences; Royal Flemish Academy of Belgium for Sciences and the Arts; Brazilian Academy of Sciences; Royal Society of Canada; Caribbean Academy of Sciences; Chinese Academy of Sciences; French Academy of Sciences; German Academy of Natural Scientists Leopoldina; Indian National Science Academy; Indonesian Academy of Sciences; Royal Irish Academy; Accademia Nazionale dei Lincei (Italy); Academy of Sciences Malaysia; Academy Council of the Royal Society of New Zealand; Royal Swedish Academy of Sciences; Royal Society (UK); Woods Hole Research Center; Intergovernmental Panel on Climate Change (IPCC); The Australian Meteorological And Oceanographic Society; the American Institute of Physics; The Pentagon; Presidents from 319+ Universities and Colleges; and others; (source:LogicalScience.com)";
		var ret = ResponseToBoolPrompt('Would you like to filter out global warming deniers - those at odds with the ' + slist + '?\n'); 
		if (ret.length) { // ie didnt cancel
			if (ret=='y') { 
				GM_safesetValue("filtermode",1); 
				bAlreadysetfiltermode=true;
				bchanged = true;
			} else {
				ret = ResponseToBoolPrompt('OK, then would you prefer to filter out global warming accepters - those in *agreement* with the ' + slist + '?\n'); 
				if (ret.length) {
					GM_safesetValue("filtermode",(ret=='y')? 2 : 0); 
					bAlreadysetfiltermode=true;
					bchanged = true;
				}
			}
			smid = ' the rest of';
		}
	}

	// set any never-set savedvalues to defaults
	for (var i=0; i<tmpcfg.length; ++i) {
		var skey = tmpcfg[i].name;
		var sval = GM_safegetValue(skey);
		if (sval==null) {
			GM_safesetValue(skey, tmpcfg[i].default);
		} 
	}
	var bUseCurrentValIfUserSkips = false;
	if (bMightEdit) {
		var bEditPrefs = (iforceEditVal==2);
		if (iforceEditVal==1) {
			var ret = ResponseToBoolPrompt('Would you like to edit' + smid + ' your script settings?'); 
			bEditPrefs = (ret=='y');
		}
		if (bEditPrefs) {
			var ret = ResponseToBoolPrompt('Would you like your current settings (if any) to be the default values?');
			if (!ret) { 
				alert("Not editing..."); 
				bEditPrefs = false;
			} else {
				bUseCurrentValIfUserSkips = (ret=='y');
			}
		}
	}
	var skeys=[];
	for (var i=0; i<tmpcfg.length; ++i) {
		var skey = tmpcfg[i].name;
		skeys.push(skey);

		if (bEditPrefs) {
			if (skey=='filtermode' && bAlreadysetfiltermode) {
				continue;
			}
			if (strInArr(skey,filterkeys) && !gbItsDED) {
				continue; // no point in making user set settings s/he wont use
			}
			var scurrentval = GM_safegetValue(skey);
			var snewval = AskNewValueForItem(bUseCurrentValIfUserSkips,
				skey, scurrentval, tmpcfg[i].prompt, tmpcfg[i].default ); 
			if (snewval==null) { 
				bEditPrefs = false; 
			} else if (snewval!=scurrentval) {
					GM_safesetValue(skey,snewval);
					bchanged = true;
			}
		}
	}
	if (bchanged) {
		UpdateLastModDate();
	}
	return skeys
}
function InitTBVSettingsHashFrSaved(skeys)
{
	var ret=[];
	for (var i=0; i<skeys.length; ++i) {
		var skey = skeys[i];
		ret[skey] = GM_safegetValue(skey);
	}
	return ret;
}
// -----------------------------------------
function EditDEDPrefs() {
		var prevmod = LastModDate();
    var settings = InitSavedTBVSettings(2);
		ReportGMSettingsHash(settings);
		if (prevmod != LastModDate()) {
			alert("You probably need to reload the page before your changes will show up.");
		}
}
var slastmod = LastModDate();
GM_registerMenuCommand("Edit " + gsScriptname + " preferences", EditDEDPrefs);
// tbvgmlog(slastmod.length + "Settings were last modified " + 	slastmod);

var itmp = (slastmod.length>0)? 0 : 1; // if newbie, we'll want to ask whether to set settings
 // 0 =use existing settings, 1=ask, 2=go ahead and edit
// tbvgmlog(itmp+"slastmod=" + slastmod);
var TBVSettings=InitTBVSettingsHashFrSaved(InitSavedTBVSettings(itmp));
// ReportGMSettingsHash(TBVSettings);

// -----------------------------------------
var gbFilterMode = (gbItsDED)? TBVSettings['filtermode'] : 0;		// I"ve only tested 0, 2 and 3
// 0=egalitarian (trust everyone, value all comments equally)
// 1= trust regulars
// 2= trust deniers


var ourMinCommentIndex = 0; // if a subpage, we'll increase this val

var gbLabelAuthors=(gbItsDED)? TBVSettings['labelauthors'].toLowerCase() =='y' : false;
var bClassifyAuths = (gbItsDED && (gbLabelAuthors || gbFilterMode>0));

var giSafeMode= (bClassifyAuths)? TBVSettings['safemode'] : 0; 
	// Set it to 0 under normal conditions.
	// Set it to 1 if the noise is particularly bad,
	// Set it to 2 if you _only_ want comments from trusted commenters
								
var gbShrinkDistrusted = (bClassifyAuths)? TBVSettings['shrinkdistrusted'] : false; // def. false; // even if you dont do recs, still shrink the negative-credibility commenters?

var gbBrandMyComment = (TBVSettings['brandmycomment'].toLowerCase()=='y'); //  initialize the comment with "I'm using [scriptname]" text

var gsUsingScriptStr = (!gbBrandMyComment)? '' : [
			"Using <a href='",
			gsScriptHomePage,
			"'>",
			gsScriptname,
			"</a> v.",
			gsVersion,
			"."
		].join('');
                
var gsMySig = TBVSettings['mysig']; // appended to empty comment field
if (!gsMySig) gsMySig = '';
var gsMyNameLC = 	TBVSettings['myname'].toLowerCase();	
// var gsMaxArchiveDays=14;
// ------------------------------------------------------------------
// sadly, recmode may  be unreliable:
var recmode = 1;
// 0 = dont do recs
// 1 = count the trusted ones only
// 2 = uncats can rec (and have it count)
// 3 = anyone (even distrusted) can rec (and have it count)
// ----------------

// -----------------------------------------
var classTbl = [];
var authTbl = [];
var comTbl = [];

// lookup index from name - associative array
var classFrNameAA = {};
var authFrNamelcAA = {}; // lower case
var classFrNameAA = {};
// -----------------------------------------
function ClassIsHot(iclass)
{
	return (gbFilterMode==2)? (iclass==1) : (iclass==3 || iclass==4);
}
function BasicTrustedClass()
{
	return (gbFilterMode==2)? 1 : 2;
}
function PopulateClassTbl(behavmode)
{
	classTbl = [];
	classTbl[0] = { name: "uncat", bk:"#FFDAB9" }; // 0 must be the Unks!


	if (bClassifyAuths) {
		classTbl[1] = { name: 'denier', bk:'#DDA' };
		classTbl[2] = { name: 'regular', bk:'#DFD' };
		classTbl[3] = { name: 'layexpert', bk:'#BFB' };
		classTbl[4] = { name: 'clisci', bk:'#8F8' };
	}
	classFrNameAA = {};
	for (var i=0; i<classTbl.length; ++i) {
		classFrNameAA[classTbl[i].name] = i;
	}
	
	// set trust levels from filtermode
	// itrust 0 = distrust, 1= unk, 2=trust
	// behaviormodes -
	// 0=egalitarian (trust everyone)
	// 1=trust regulars
	// 2=trust deniers

	classTbl[classFrNameAA['uncat']].trust = (behavmode==0)? 2 : 1;
	if (bClassifyAuths) {
		classTbl[classFrNameAA['denier']].trust = (behavmode==1)? 0 : 2;
		classTbl[classFrNameAA['regular']].trust = (behavmode!=2)? 2 : 0;
		classTbl[classFrNameAA['layexpert']].trust = (behavmode!=2)? 2 : 0;
		classTbl[classFrNameAA['clisci']].trust = (behavmode!=2)? 2 : 0;
	}
	
	if (giSafeMode==2) {
		classTbl[classFrNameAA['uncat']].trust = 0;
	}
	vtbvgmlog("Done with PopulateClassTbl");
}
function AuthClassHash()
{
	var ret=[];
	for (var i=0; i<classTbl.length; ++i) {
		ret[i] = classTbl[i].name;
	}
	return ret;
}
function ClassTrustLevel(iclass)
{
if (iclass==null) {
	GM_log('odear null class');
	return 0;
	}
	// tbvgmlog('trust for class ' + iclass);
	return classTbl[iclass].trust;
}
// -----------------
var authnamesByClassTbl = {};
function PopulateAuthnamesByClassTbl()
{
	authnamesByClassTbl=[];
	authnamesByClassTbl['uncat'] = [];

	if (bClassifyAuths) {
		authnamesByClassTbl['denier'] = denierNames;
		authnamesByClassTbl['regular'] = regularNames;
		authnamesByClassTbl['layexpert'] = layexpertNames;
		authnamesByClassTbl['clisci'] = clisciNames;
	}
	vtbvgmlog("Done with PopulateAuthnamesByClassTbl");
}
function AddAuthorOfClass(namelc, iclass)
{
	var iauth = authTbl.push(	{ 
			"namelc": namelc,
			"class": iclass,
			"comments": []
				} )
				-1 // empirical
				;
				// tbvgmlog('new author ' + iauth + ' of class ' + iclass);
				// (derive: classTbl[iclass].trust)
				
	authFrNamelcAA[namelc] = iauth;

	return iauth;
}
function SanitizedAuthorName(sname)
{
		return sname.replace(/\s*$/,'').replace(/[^0-9 A-Za-z.\-]/g,'').slice(0,40); // keep it clean, no odd chars
}
function AuthIndexFrNamelc(namelc, bAddIfNec)
{
	ret = authFrNamelcAA[namelc];
	if (ret==null) { // cant check for !ret since if 0, it'll barf
		// GM_log("need to add '" + namelc + "'");
		return (!bAddIfNec)? -1 : AddAuthorOfClass(namelc, 0);
	}
	else {
		return ret;
	}
}

function PopulateAuthTblWithKnownAuthors()
{
	authTbl = [];
	PopulateAuthnamesByClassTbl();

	for (var sclass in authnamesByClassTbl) {
		var iclass = classFrNameAA[sclass];
		var names = authnamesByClassTbl[sclass];
		for (var i=0; i<names.length; ++i) {
			var iauth = AddAuthorOfClass(names[i], iclass);
		}
	}
	if (bClassifyAuths) {
		// I trust myself, add me if needed.
		if (gsMyNameLC.length) {
			var smynamelc = SanitizedAuthorName(gsMyNameLC);
			// tbvgmlog("i am " + smynamelc);
			if (AuthIndexFrNamelc(smynamelc,0)<0) {
				GM_log("(add me as a trusted commenter, I trust myself)");
					AddAuthorOfClass(smynamelc, BasicTrustedClass());
			}
		}
	}
}

// --------------

function AuthnameLC(iauth)
{
	return authTbl[iauth].namelc;
}
function AuthnameSUC(iauth)
{
	return authTbl[iauth].name;
}
function AuthorTrustLevel(iauth)
{
	var iclass = authTbl[iauth].class;
	// tbvgmlog('class for auth ' + iauth + ' is ' + iclass);
	var itrust = ClassTrustLevel(iclass);
	if (giSafeMode>0 && itrust==1) {	// dunno whether auth is trustworthy
		var sauth = authTbl[iauth].namelc;
		if (sauth.length<8 && !sauth.match(/ /)) { itrust=0; } // likely new sock puppets aren't
	}
	return itrust;
}
function AuthorIsTrusted(iauth) {
	return (AuthorTrustLevel(iauth)==2);
}
function AuthorIsDistrusted(iauth) {
	return (AuthorTrustLevel(iauth)==0);
}
function NumTrustedInArr(iauths)
{
	var ret=0;
	var n = iauths.length;
	for (var i=0; i<n; ++i) {
		if (AuthorIsTrusted(iauths[i])) {
			++ret;
		}
	}
	return ret;
}
function AuthorIsHot(iauth) {
	return ClassIsHot( authTbl[iauth].class);
}

function ClassnameFrAuthor(iauth) {
	return (iauth<0)? '' : classTbl[authTbl[iauth].class].name;
}
function Authname(iauth) {
	return (iauth<0)? 'unk' : authTbl[iauth].namelc;
}

// ----------------

function ReCapped(snamelc) 
{
	return snamelc.toLowerCase().replace(/\b[a-z]/g, cnvrt);
	function cnvrt() {
			return arguments[0].toUpperCase();
	}
}
function ReCapFirstLetter(s) 
{
	return s.replace(/^[a-z]/, cnvrt);
	function cnvrt() {
			return arguments[0].toUpperCase();
	}
}

function AsTinyName(snameparam) // could be improved.
{
	var sname = snameparam.replace(/^\s*/,'').replace(/\s*$/,''); // trim
	if (!sname || sname=='')
	{
		GM_log('no tiny name');
		return 'anon';
	}
	var stmp = sname.replace(/\./g,''); // rm dots
	if (stmp.slice(-3).match(/\s/)) { // lastname too short - glom together first+last
		return stmp.replace(/\s+/g,'').slice(0,10);
	}
	var letter0 = (stmp.match(/ /))? stmp[0] : '';
	return letter0 + stmp.replace(/.* /,'').slice(0,10); // use firstletter+last name
}
// --------------
function Firstname(sname) { 
	var ret= sname.replace(/ .*/,'');
	return (ret=='wang')? sname.replace(/.* /,'') : ret;  // last name is first name
}
// --------------
function TinyNameFrAuthor(iauth) {
	return AsTinyName(Authname(iauth));
}
// --------------

function InitCommentInTbl(cindex,sliid,iauth)
{
	comTbl[cindex] = {
		authindex: iauth,
		sliindex:sliid,
		upstrs: [], 	dnstrs: [],			// we're replying to,  replying to us
		reccers: [],unreccers: [],	// others' recs of this comment
		trustedUnreccers: [],
		myrecs: [],	myunrecs: [] 		// my recs of other comments (should be in authtbl?)
		}; 
	if (cindex) {
		authTbl[iauth].comments.push(cindex);
	}
}
// --------------
function InitDataTbls(behavmode)
{
	GM_log("InitDataTbls");
		PopulateClassTbl(behavmode);
		PopulateAuthTblWithKnownAuthors();
	comTbl = [];
	InitCommentInTbl(0,0,0);	// since there is no comment #0.
}
// -----------------

function AddCommentToTbl(index,sliid,authname)
{
	var authnamelc = authname.toLowerCase();
	var iauth = AuthIndexFrNamelc(authnamelc,1); // add if !there
	if (iauth>=0) {
		InitCommentInTbl(index,sliid,iauth);
	}
	// tbvgmlog("(AddCommentToTbl: comment#" + index + ' aka ' + sliid + ' by ' + authname + iauth  +')');
}
function LiIdStrFrIndex(index)
{
	if (index<comTbl.length) {
		return comTbl[index].sliindex;
	} else {
		return '';
	}
}
function LiIdnoFrIndex(index) // not currently used
{
	if (index<comTbl.length) {
		return comTbl[index].sliindex.replace(/[^\d]/g,'') // 44976
	} else {
		return 0;
	}
}

// -----------------
function AuthorFrComment(index)
{
	return (index<comTbl.length)? parseInt(comTbl[index].authindex) : -1;
}
// -----------------
function AuthorsFrComments(ciarr)
{
	var ret=[];
	var n=ciarr.length;
	for (var i=0; i<n; ++i) {
		ret.push(AuthorFrComment(ciarr[i]));
	}
	return ret.sort();
}
// -----------------
function ClassnameFrComment(index)
{
	return ClassnameFrAuthor(AuthorFrComment(index));
}
function AuthnameFrComment(index)
{
	return Authname(AuthorFrComment(index));
}
function TinyAuthnameFrComment(index)
{
	return TinyNameFrAuthor(AuthorFrComment(index));
}
function LastIndexFrCommentTbl()
{
	var nitems = comTbl.length;
	return (nitems>0)? nitems-1 : 0;
}
function RecsOfThisCommentAreHot(index)
{
	// if (index==1)
	//	tbvgmlog(comTbl[index].reccers.join('-'));
	return (NumTrustedInArr(AuthorsFrComments(NumSortArr(comTbl[index].reccers)))>0);
}
// -----------------
function CIndexFrLiNode(linode)
{
	if (!linode) {
		return 0;
	} else {
		var anode = linode.childNodes[1];
		// tbvgmlog(anode.nodeName); lognode
		return anode.innerHTML.replace(/\./,'');	
	}
	// rm the . of 125.
}
// -----------------
function CIndexFrSlink(slink)
{
	if (slink.match(/href=(.*?)#(.*?)\1/)) {
			// tbvgmlog('CIndexFrSlink' + slink + '=' + RegExp.$2 );
			return CIndexFrLiNode(document.getElementById(RegExp.$2));
	}
	return 0; // not a link
}
// -----------------
function commentIndexesFrSlinksCSV(ourCIndex,scsv)  
{	// might be just a csv of comment indexes, or they might be linkified.
	var sarr = scsv.split(',');
	var re = /</;
	if (re.test(scsv)) {
		var ret=[];
		var n = sarr.length;
		for (var i=0; i<n; ++i) {
				ret.push(CIndexFrSlink(sarr[i]));
		}
		return ret;
	} else {
		return sarr; // they're just comment indexes, not linkified
	} 
}
// -----------------
function AddIndexAsReccerOfCSVComments(ourCIndex,reccsv) 
{ // reccsv might be just a csv of comment indexes, or they might be linkified.
//	comTbl[ourCIndex].myrecs = recciarr; // who c is recommending
	var cis = (reccsv)? commentIndexesFrSlinksCSV(ourCIndex,reccsv) : [];
	var n = cis.length;
	for (var i=0; i<n; ++i) {
		var cindex = parseInt(cis[i]);
		if (cindex>ourMinCommentIndex && cindex<ourCIndex) {
			comTbl[cindex].reccers.push(ourCIndex);
		}
	}
}
// ------
function AddIndexAsUnreccerOfCSVComments(ourCIndex,unreccsv)
{
var unrecciarr = unreccsv.split(',');
	comTbl[ourCIndex].myunrecs = unrecciarr;
	var btrusted = AuthorIsTrusted(comTbl[ourCIndex].authindex);
	var n = unrecciarr.length;
	for (var i=0; i<n; ++i) {
		var cindex = unrecciarr[i];
		if (cindex>ourMinCommentIndex && cindex<ourCIndex) {
			comTbl[cindex].unreccers.push(ourCIndex);
			if (btrusted) {
				comTbl[cindex].trustedUnreccers.push(ourCIndex);
			}
		}
	}
}
function IsUnreccedByNTrustedUsers(ourCIndex,n)
{
	return comTbl[ourCIndex].trustedUnreccers.length >= n;
}
// ------
function AddIndexAsReplierToComments( ourCIndex, ciarr)
{
	var ourcom = comTbl[ourCIndex];
	ourcom.upstrs = [];

	var n = ciarr.length;
	for (var i=0; i<n; ++i) {
		var cindex = ciarr[i];
		if (cindex>ourMinCommentIndex && cindex<ourCIndex) {
			AddItemToUniqArr(ourCIndex,comTbl[cindex].dnstrs);
			AddItemToUniqArr(cindex,ourcom.upstrs);
		}
	}
}
// -----------------
// con permiso

function OKToShowRecs()
{
	return (recmode>0);
}
function OKToRec( isTrusted)
{
	if (!OKToShowRecs()) { return false; }
	return ( recmode>1 || isTrusted);
}
// -----------------

var MAX_DUMP_DEPTH = 10;
function dumpObj(obj, name, indent, depth) { // thanks Scott Van Vliet!
	if (depth > MAX_DUMP_DEPTH) {
				 return indent + name + ": <Maximum Depth Reached>\n";
	}
	if (typeof obj == "object") {
				 var child = null;
				 var output = indent + name + "\n";
				 indent += "\t";
				 for (var item in obj) {
							 try {
											child = obj[item];
							 } catch (e) {
											child = "<Unable to Evaluate>";
							 }
							 if (typeof child == "object") {
											output += dumpObj(child, item, indent, depth + 1);
							 } else {
											output += indent + item + ": " + child + "\n";
							 }
				 }
				 return output;
	} else {
				 return obj;
	}
}
// --------------------------------------------------
       
function DumpTables()
{
	GM_log(dumpObj(classTbl,'classTbl',3,5));
	GM_log(dumpObj(authTbl,'authTbl',3,5));
	GM_log(dumpObj(comTbl,'comTbl',3,5));
}
function DumpCommentTable()
{
	GM_log(dumpObj(comTbl,'comTbl',3,5));
}
function DumpAuthorTable()
{
	GM_log(dumpObj(authTbl,'authTbl',3,5));
}
function DumpComment(cindex)
{
	GM_log(dumpObj(comTbl[cindex],'comTbl[cindex]',3,5));
}



// lookup index from name - associative array
// var classFrNameAA = {};
// var authFrNamelcAA = {}; // lc = lower case
// var classFrNameAA = {};

// --------------------------------------------------
// Utils
// ---------------------------------
function indexInArray(name, arr) 
{
	var n = arr.length;
	for(var i = 0; i < n; i++) {
    if (arr[i]==name) return i;
  }
  return -1;
}
function strInArr(s, arr) {
	return indexInArray(s,arr)>=0;
}
function AddItemToUniqArr(item, arr)
{
	if (indexInArray(item,arr)<0) {
		arr.push(item);
	}
}
function sortNumber(a, b)
{
	return a - b;
}
function NumSortArr(arr)
{
	return arr.sort(sortNumber);
}
function HashToStr(h) {
	var sarr = [];
	for( var i in h ) {	sarr.push(i + ':' + h[i]); }
	return sarr.join(';');
}
function ArrFrCSV(scsv)
{
	return (!scsv)? [] : scsv.replace(/,$/,'').split(',').sort();
}
function ArrOfMaxSizeFrCSV(scsv, nmax)
{
	return (ArrFrCSV(scsv).slice(0,nmax));
}

// --------------------------------------------------
// deciphering the comment - general
// ---------------------------------

function xpath(query) {
    return document.evaluate(query, document, null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}
// -------------
function WithAllTagsRemoved(shtml) // workaround
{
	return shtml.replace(/\<[^\>]+>/g,'');
}
// --------------------------------------------------
// deciphering the comment - specific
// ---------------------------------
function SafeAuthnameFrCiteNode(citeNode)
{
		var sauth =  WithAllTagsRemoved(citeNode.innerHTML);
		return SanitizedAuthorName(sauth.replace(/.*?\s/m,'').replace(/\s+/mg,' ').replace(/^\s*/,''));
}
// ---------
function CommentIndexFrIndexNode(indexNode)
{
// tbvgmlog("CommentIndexFrIndexNode ");
	return (!indexNode)? 0 : parseInt( indexNode.innerHTML.replace(/\./,'') );
}

function LiNodeFrCContentNode(ccontentNode)
{
		return ccontentNode.parentNode; 		// li class="alt" id="comment-32370"
}
function CContentNodeFrCiteNode(citeNode)
{
//	tbvgmlog("CContentNodeFrCiteNode "+ citeNode.innerHTML);
		var authNode = citeNode; // citeNode.parentNode;		// p/cite
		return authNode.parentNode; 		// div class=comment-content
}
function IndexNodeFrCContentNode(ccontentNode)
{
//	tbvgmlog("IndexNodeFrCContentNode "+  ccontentNode.innerHTML);
		var liNode = ccontentNode.parentNode;			// id="comment-44976"
		var ret= liNode.childNodes[1].childNodes[1]; // childNodes[1];
//		tbvgmlog('liNode.childNodes[1]='+liNode.childNodes[1].nodeName);
		return ret;
}
function IndexNodeFrCiteNode(citeNode)
{
	// tbvgmlog("IndexNodeFrCiteNode "+  citeNode.innerHTML);
	var ccnode = CContentNodeFrCiteNode(citeNode);
	return IndexNodeFrCContentNode(ccnode);
}
function CommentIndexFrCiteNode(citeNode)
{
// tbvgmlog("CommentIndexFrCiteNode "+  citeNode.innerHTML);
	var indexNode = IndexNodeFrCiteNode(citeNode);
	if (!indexNode) { 
		tbvgmlog("CommentIndexFrCiteNode undef -"+  citeNode.innerHTML );
	}
	return CommentIndexFrIndexNode(indexNode);
}
function GetBaseURL()
{
// turns
// http://dotearth.blogs.nytimes.com/2008/09/06/confirmation-of-open-water-circling-north-pole/?apage=2#comments
// into 
// http://dotearth.blogs.nytimes.com/2008/09/06/confirmation-of-open-water-circling-north-pole

	return window.location.href.replace(/\/\?.*/,'').replace(/#.*/,'').replace(/\/$/,'');
}

 

		
// ---------------------------
// a small ugly hack
function InnerTextFromNodeBeforeContainedElement(vNode,selem) // e.g. '<cite'
{
	var vhtml = vNode.innerHTML;
	return WithAllTagsRemoved(vhtml.substr(0,vhtml.indexOf(selem)));
}

function pullCSVRepliesFromComment(comnode,authnamelc)
{
	// for test  -
	// var stext = 'To Marina&#8221;HotFo#1rWor #8; ds&#8221;: at 5#13, ';

	var stext = ' ' +			WithAllTBVStuffRemoved(InnerTextFromNodeBeforeContainedElement(comnode,'<cite')).replace(/\n/mg,"\n ");
		 //  add a preceding char to each line(hack, for unicode-detection reasons)
	
	// then match
	var matches = stext.match(/.\#\d+/mg);
	if (!matches) {
		return '';
	}	
		
	var ret = matches.join(',').replace(/\&.*?,/g,''); // rm unicodes 
	
	// rm the chars preceding '#' (fyi,could be digit); then all nondigits (mainly # signs)
	return ret.replace(/[^,]#/,'#').replace(/[^\d,]/g,'');
}
// --------------------------------------------------
//    digesting data
// -------------------
function DigestRecsAndUnrecs(ourCIndex,csvRecs,csvUnrecs)
{
	// tbvgmlog(ourCIndex + ' rec,un:' + csvRecs + ' - ' + csvUnrecs);
	if (csvRecs && csvRecs.length) AddIndexAsReccerOfCSVComments(ourCIndex,csvRecs);

// for now we dont do unrecs
// if (csvUnrecs && csvUnrecs.length) AddIndexAsUnreccerOfCSVComments(ourCIndex,csvUnrecs);

// ??  also in development, albeit not at all rapid
	// if (OKToRate(ratemode,isTrusted)) {
	// 		ApplyCommenterRatingsIfAny(ourCIndex,recarr,1);
	// 		ApplyCommenterRatingsIfAny(ourCIndex,unrecarr,-1);
	// 		++numRatings;
	// }
}


// --------------------------------------------------
// Misc tweaks to the page
// ---------------------------------
function FillInClassOfAllRecLinks()
{
	var tbdNodes = xpath("//a[@class='classtbd']");
	var n = tbdNodes.snapshotLength;
	for (var i = 0; i < n; i++) {
	    var tnode = tbdNodes.snapshotItem(i);
	    var cindex =tnode.getAttribute("href").replace(/[^\d]/g,''); // del all but the index, from "#index82"
	    var sclass = ClassnameFrComment(cindex);
	    if (sclass.length) {
	    	var sauth = TinyAuthnameFrComment(cindex);
	   		tnode.setAttribute('class', sclass);
	   		tnode.setAttribute('title', sauth+ "#" + cindex);
	   	}
	}
}

// --------------------------------------------------
//       CSS
// -----------
function addGlobalStyle(css) { // thanks mark pilgrim!
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}
// -----------
function ArrOfCommenterStyles()
{
	var ret=[];
	var n = classTbl.length;
	for (var i=0; i<n; ++i)	{
		var sbg = classTbl[i].bk; // bgcolor
		if (sbg!='') {
			ret.push( ['.', classTbl[i].name, ' { background:', sbg, '; }'].join('') );
		}
	}
	return ret;
}
// -----------
function SetTBVStyles() // CSS
{
	addGlobalStyle(ArrOfCommenterStyles().join("\n"));
		GM_addStyle("a { text-decoration: underline; }");

			// original elements
			
		//	'div#shell div#page {	width:1200px;		} ',
		//	'div#masthead div#login div#navigation {	width:960px;		} ',
	//		ul.tabs li.selected ul 
//			'#blog {width:1000px; } ', // background-image:url();} ',	
//			'#aCol { width:700px;} ',
	//		'#cCol { width:200px; margin:10px;} ',
	//		'#content { width:100%;} ',
		//		'.hfeed .entry { width:90%; } ',
		//		'.entry-meta { width:90%; } ',
		var otherstylesArr = [
	//	'#cCol { float:right;} ',
	//	'#aCol { float:right;} ',
		'#comments a { color: #004276; text-decoration: underline !important;}',			
			'#comments .commentmetadata { margin:2px;}',
			'#comments .index { font-size:120%; font-weight:bold; padding:4px; margin:8px; }',
			'#comments cite { display:none; }',

		'#comments .comment { margin-left:10px; width:120%; } ',

			// script-added elements
		
			'#comments div.TBVxref {width:80%; float:left}',
			'#comments div.comment-meta { width:100%;  float:left; }',
//			'#comments div.commentmeta { width:100%;  float:right; }',
			'#TBVcommentpreviewpane { color:red; padding:5px; margin:5px;  border:2px solid orange; width:90%; min-height:60px; }',
			'#TBVcommentrecspane { color:red; padding:5px; margin:5px; border:2px solid orange; width:90%; min-height:60px; }',
			'div.showhidebtns { background:#EEF;padding:3px; margin:5px;  } ', // lightblue
			'div.btnwrapper { background:#6EF; width:300px; padding:3px;margin-bottom:15px; } ',
			'#comments div.morelessbtns { height:10px; font-size:80%; padding:3px; margin-right:60px; float:right; } ',

			'#comments .TBVAuthList { font-size:85%; opacity:.65;  }',
			'#comments .bloghost { background:#FFD700; }',
			'#comments .arealname { font-weight:bold; color:green; }',
			'#comments .labeling { opacity:.75; }',
// does nothing.			'#comments  #comment-content { display:none;}', //  margin-left:-80px !important ; width:200% !important ; margin-bottom:20px; } ', // space for the 'more' btn
//	TBVsmallbutton		
			
			'#comments .viewTBVSettings {  font-size:90%;  font-weight:normal;}',
			'div.TBVSettingsdata {  }',
			
			'#comments .TBVDoemallBtns { float:right; opacity:.80;}',
			'#comments .TBVsmallbutton {  height:20px; opacity:.80;}',
			'#comments .TBVreplyingto { white-space: nowrap;}',
			'#comments .TBVCommentBtns { float:left; width:100%; height:5px; opacity:.80; padding:2px; font-size:80%; display:block;white-space: nowrap; margin-bottom:15px; }', 
//			'#comments div.floatleft { float:left;}',
			'#comments div.TBVcbodyresizebtns { float:left;}',
			'#comments div.TBVdorecbtns { float:left; }',
			'#comments .TBVclearfloats { clear:both; opacity:.0;}',
			'#comments .TBVToAnchors { font-size:130%;}',
			
			'#comments .TBVrecstatus { display:block; float:left; padding:3px; margin-left,margin-right:5px; opacity:1.0;}',
			'#comments div.TBVthreading {	clear:left; background: #EAEAEA; white-space: nowrap; }',
			'#comments .TBVciteB { border: 2px solid #CCC; padding: 1px; font-size: 120%; font-weight:bold; white-space:nowrap; }',
			'#comments .TBVbegaticon { font-family: monospace; padding: 0px; margin: 2px; }',
			'#comments .TBVraters { margin-bottom: 5px; float:left; }',
			'#comments .TBVrecicon { border: 2px solid #0D0; color: #0D0; background: #FFF; font-size: 120%; font-weight: bold; padding: 1px; margin: 2px; }',
			'#comments .TBVunrecicon { border: 2px solid #999; color: #000; background: #EEE; font-size: 120%; font-weight: bold; padding: 1px; margin: 2px; }',
			'#comments .TBVnames {	padding:2px; font-size:85%;	opacity:.80;}',
			'#comments .shrinkme {	font-size:60%;	opacity:.80;}',
			'#comments span { display:inline; }',


			'#commentform { border:2px solid orange; height:110%; width:110%; margin-left:-40px; margin-top:-20px; padding:3px; } ',			
			'.respond #sidenode {	float:left; width:120px;font-size:110%;  }',
			
			'.respond #TBVbtnpreview{ margin-top:10px; float:left;}',
			'.respond .submit-comment { float:left; margin-left:180px; margin-right:100px; margin-bottom:0px; }',
			'.respond .comment { float:right; height:100px; }',

			'.respond .returntos { font-color:darkorange; border:1px dashed orange; padding:3px; }',
			'#comments .recsby {	opacity:.60;}'
			];
	 addGlobalStyle(otherstylesArr.join("\n"));
}
// -----------------------------------------
function addGlobalJS(sscript) {

    var head = document.getElementsByTagName('head')[0];
    if (!head) { return; }

		// tbvgmlog('clientJS:\n' + sscript);
    var scriptelem = document.createElement('script');
    scriptelem.type = 'text/javascript';
    scriptelem.innerHTML = sscript;
    head.appendChild(scriptelem);
}
function AddClientJS()
{
	var sLimboClass = classTbl[0].name;
	
	var clientJSArr = [
"		function ItemInArr(item,iarr) {		",
"			var n = iarr.length;						",
"		 for (var i=0; i<n; ++i) {				",
"			  if (iarr[i]==item) { return true; }",
"		 } return false;									",
"		}		",
"		function trimmed(s) {		", // fyi, this removes newlines too
"			return s.replace(/^\\s*/mg,'').replace(/\\s*$/mg,'');		",
"		}		",
"		function HashFrStr(s) {						",
"			var h=[];  ",
"			var sarr = s.replace(/;\\s*/mg,';').split(';'); var n = sarr.length;		",
"			for( var i =0; i<n; ++i) {			",
"				var kv = sarr[i].split(':');	",
"				h[kv[0]] = kv[1];		",
"			}	return h;		",
"		}		",
"		function NonEmpties(arr) {		",
"			var ret= [];		",
"			var n= arr.length;		",
"			for (var i=0; i<n; ++i) {		", 
"				var val = trimmed(arr[i]);		", 
"				if (val && val.length) { ret.push(val);  }	",
"			}	",
"			return(ret);	",
"		}	",
"		function WithAllTagsRemoved(s) {	",
"			return s.replace(/\\\<[^\\\>]+>/g,' ');	", // looks like too many backslashes to me too, but it works.
"		}	",
"		function TextTruncatedToMaxLength(s,n) {	",
"			return (s.length<=n)? s : s.slice(0,n) + '...';	",
"		}	",
"		function TBVMetaHash4All( ) {			",
"			return HashFrStr(document.getElementById('TBVmetaall').innerHTML);	",
"		}		",
"		function TBVMeta4All(skey ) {			",
"			return TBVMetaHash4All()[skey]	",
"		}		",
"		function TBVMetaHash4Comment(index ) {		",
"			var sid = 'TBVmeta'+index;	",
"			var anode = document.getElementById(sid);	",
"			if (!anode) alert('Cant getElementById for ' + sid);	",
"			return (anode)? HashFrStr(anode.innerHTML) : '';		",
"		}		",
"		function TBVMeta4Comment(index,skey ) {		",
"			return TBVMetaHash4Comment(index)[skey];		",
"		}		",

"		var myrecs=[];		",
"		function RecAndUnrecCSVs() {		",
"			var recs=[];		",
"			var unrecs=[];	",
"			var rec ='';		",
"			var n = myrecs.length;		",
"			for (var i=1; i<n; ++i)	{	",
"				rec = myrecs[i];				",
"				if (rec) {		",
"					if (rec>0) { recs.push(i); }		",
"					else { unrecs.push(i); }		",
"			}	}		",
"			return [ recs.join(','), unrecs.join(',')];		",
"		}		",

// need this func. since if Submit fails, we need to remove TBVstuff before we add it upon next Submit attempt.
"		function WithAllTBVStuffRemoved(stext) {		",
"			return stext.replace(/\\[_.*?_\\]/gm,'');		",
"		}		",

"		function AuthnameFrCommentIfOfClass(cindex,sclass)  ",
"		{ ",
"			var aelem = document.getElementById('TBVmeta' + cindex); ",
"			if (aelem && aelem.childNodes[1] && aelem.childNodes[1].className ==sclass) { ",
"				return HashFrStr(aelem.innerHTML)['authname']; ",
"			} else return ''; ",
"		} ",
"		 ",
"		function AuthorsOfTheseCommentsFromThisClass(scsv,sclass) {	 ",
"			// alas barfs var aaauthnames = []; ", // assoc array (want uniq names)
"			var ret=[]; ",
"			var ciarr = scsv.split(','); ", // commentindex array
"			for (var i=0; i<ciarr.length; ++i) { ",
"					var sauthname = AuthnameFrCommentIfOfClass(ciarr[i],sclass);  ",
"					if (sauthname.length) { ret.push(sauthname) } ",
"			} ",
"			return ret;	 ",
"		} ",

"var gsBlogname = \"" + gsBlogname + "\"; ",
"var bClassifyAuths = " + bClassifyAuths + "; ",

"		function TBVHashAsStr() {		",
"			var auxhash=[]; ",
"			var reccsvs = RecAndUnrecCSVs();	",
"			var sauths = AuthorsOfTheseCommentsFromThisClass(reccsvs[0], '" + sLimboClass + "')",
"			var itsreal = document.getElementById('TBVckrealname').checked? 'y' : ''; ",
"			auxhash['r'] = LinkifiedCommentCSV(reccsvs[0]);	",
"			auxhash['u'] = LinkifiedCommentCSV(reccsvs[1]);	",
"			auxhash['a'] = (bClassifyAuths)? sauths : [];	",
"			auxhash['rn'] = itsreal;	",
"			var aharr =[]; ",
"			var karr =['r','u','a','rn']; ", // to get around hashkeys bug
"			for (var ik=0; ik<karr.length; ++ik) { ",
"				var k =  karr[ik]; ",
"				var val =  auxhash[k]; ",
"				if (val && val.length) { ",
"					aharr.push(k + ':' + val); ",
"			} }",
"			return (!aharr.length)? '' : '[' + aharr.join('; ') + ']'; ",
"		}		",
"		function MyRecStr() {		",
"			var ret=[];		",
"			var reccsvs = RecAndUnrecCSVs();	",
"			return VerboseHTMLFromCSV('I recommend:',reccsvs[0]); ",
"		}		",

 
// " // we need for the clientside js to know these: ",

"var gsUsingScriptStr = \"" + clientjsd(gsUsingScriptStr) + "\"; ",
"var gsMySig = \"" + clientjsd(gsMySig) + "\"; ",
"var gsOurBaseURL = \"" + clientjsd(gsOurBaseURL) + "\"; ",

"		function AsTBVInfoString(sarr) {		",
"			var sarr2 = NonEmpties(sarr);		",
"			return(!sarr2.length)? '' : '<i>[_ ' + sarr2.join(' ') + ' _]</i>\\n'	",
"		}	",
"		function AppendTBVInfoToComment() {		",
"			var scelem = document.getElementById('comment');		",
"			if (!scelem) { return; }		",
"			scelem.value = ",
"				WithAllTBVStuffRemoved(scelem.value) + '\\n' + ",
"				AsTBVInfoString( [gsMySig, MyRecStr(),gsUsingScriptStr,TBVHashAsStr()] ); ",
"		}		",
"		var backlinkstothese = [];		",
"			function Reply2Comment(index,sliid, stitle) {	",
"				var scelem = document.getElementById('comment');		",
"				if (!scelem) { 	alert('null scelem ');	return; }		",
"				var sauth = stitle.replace(/.*eply to /i,''); ",
// "				var slink = \" (<a href='#index\" + index + \"'>#\" + index + \"</a>)\"; 		",
"				var slink = \" (<a href='\"+ gsOurBaseURL + \"#\" + sliid + \"'>#\" + index + \"</a>)\"; 		",
"				if (!ItemInArr(index,backlinkstothese)) {		",
"					var stext = ''; ",
"					if (!backlinkstothese.length) { stext = '<i>Return to..</i><br/>'; }		",
"					backlinkstothese.push(index);		",
"					stext += sauth.slice(0,10) + slink + '<br/>';		",
"					var backlinksdiv= document.getElementById('returntos'); ",
"					backlinksdiv.innerHTML += stext; ",
"				}		",
"				var sseltext = TextTruncatedToMaxLength( WithAllTagsRemoved(window.getSelection().toString()),400); ",
"				if (!sseltext.length) { sseltext = ' . . . . '; }",
"				scelem.value += '\\n' + sauth + slink + ' wrote: \"<i>' + sseltext + '</i>\"\\n';		",
"				scelem.focus();		",
"			}		",
"		function VerboseHTMLFromCSV(slabel, scsv) {		",
"			var sarr = [];		",
"			if (scsv && scsv.length) { ",
"			 var ciarr = scsv.split(',');		",
"			 var n = ciarr.length;		",
"			 for (var i=0; i<n; ++i)	{		",
"					var slink = TBVMeta4Comment(ciarr[i],'linkstr');		",
"					if (slink && slink!='') { sarr.push(slink);}		",
"			 }}		",
"			return  (!sarr.length)? '' : slabel + ' ' + sarr.join(', ') + '.';		",
"		}		",
"		function LinkifiedCommentCSV(scsv) {		",
"			var sarr = [];		",
"			if (scsv && scsv.length) { ",
"			 var ciarr = scsv.split(',');		",
"			 var n = ciarr.length;		",
"			 for (var i=0; i<n; ++i)	{		",
"			 		var ci=ciarr[i];		",
"					var sliid = TBVMeta4Comment(ci,'sliid');		",
"			 		if (sliid && sliid!='') {		",
"						var slinked = ['<a href=\\'', gsOurBaseURL, '#', sliid, '\\'>', ci, '</a>'].join('');		",
"						sarr.push(slinked)		",
"			 }}}		",
"			return sarr.join(', ');		",
"		}		",
"		function VerboseMyRecsAndUnrecsStr() {		",
"			var reccsvs = RecAndUnrecCSVs();		",
"			var shtmlrecs = VerboseHTMLFromCSV('I\\'m recommending:',reccsvs[0]); ",
"			var shtmlunrecs = VerboseHTMLFromCSV('I\\'m unrecommending:',reccsvs[1]); ",
"			var smid = (shtmlrecs=='')? '' : '<br/>';		",
"			var sterse = '<p/>or, for future importing ' ",
"				+ '(copy and paste it somewhere for insurance):<br/><b>' ",
"				+ 'Recs: ' + reccsvs[0] + ' &nbsp; Unrecs: ' +  reccsvs[1] + '</b><br/>';		",
"			return shtmlrecs + smid + shtmlunrecs + sterse;		",
"		}		",
"		function AddAsRecs(suserinput,dir) {		",
" 		var s = suserinput.replace(/ /g,'');		",
" 		if (!s.length) return;		",
" 		var recs = s.split(',');		",
"			for (var i=0; i<recs.length; ++i)	{	",
"				increcstatus(recs[i],dir);		",
"		}	}	",
"		function ImportMyRecsAndUnrecs() {		",
" 		AddAsRecs(prompt('Recs: (e.g. 9,11,13)'),1);		",
" 		AddAsRecs(prompt('Unrecs: (e.g. 1,4)'),-1);		",
"			VerboseViewMyRecsAndUnrecs(); ",
"		}		",
"		function VerboseViewMyRecsAndUnrecs() {		",
" 		var scontent = VerboseMyRecsAndUnrecsStr();		",
"			var selem = document.getElementById('TBVcommentrecspane');		",
"			selem.innerHTML =	(scontent=='')? 'No recs or unrecs yet.' :	 ",
"			scontent + '\\n<br/><i>(Recs will be appended to your comment upon Submit.' ",
"			if (bClassifyAuths) { ",
"				scontent+= 'and (DotEarth only!)fyi, there\\'s no need to unrec commenters already in a \\'credibility-impaired\\' category.)' ",
"			}		",
"			scontent+= '</i> '; ", 
"		}		",
"		function PrepareCommentForSubmit() {		",
"			var scelem = document.getElementById('comment');		",
"			if (!scelem || scelem.type!='textarea') { return; }		",
"			var stext =  ",
"				WithAllTBVStuffRemoved(scelem.value) + '\\n' +		",
"				AsTBVInfoString( [gsMySig, MyRecStr(), gsUsingScriptStr,TBVHashAsStr()]); ",
"			scelem.value= stext; ",
"		}		",
"		function PreviewComment() {		",
"			var scelem = document.getElementById('comment');		",
"			if (!scelem || scelem.type!='textarea') { return; }		",
"			var stext =  ",
"				WithAllTBVStuffRemoved(scelem.value) + '\\n' +		",
"				AsTBVInfoString( [gsMySig, MyRecStr(), gsUsingScriptStr,TBVHashAsStr()]); ",
"			var divprev = document.getElementById('TBVcommentpreviewpane');		",
"			divprev.innerHTML =  stext.replace(/\\n/mg,'<br/>');		",
"			window.location.href = '#TBVcommentpreviewpane'; ",
"		}		",
"		function showIt(sid, bshow) { ",
"			if (!document.getElementById('show' + sid)) return; ", // this one doesn't have showhide btns
"			document.getElementById('show' + sid).style.display = (bshow)? 'none' : 'block'; ",
"			document.getElementById('hide' + sid).style.display = (bshow)?  'block' : 'none'; ",
"			document.getElementById(sid).style.display = (bshow)?  'block' : 'none'; ",
"		} ",

"		function openRequestedPopup(strUrl, strWindowName) {	",
"		  if(WindowObjectReference == null || WindowObjectReference.closed)	{ ",
"		    WindowObjectReference = window.open(strUrl, strWindowName,	",
"		           'resizable=yes,scrollbars=yes,status=yes');	",
"		  } else { WindowObjectReference.focus();  };	",
" }",

"		function increcstatus(cindex,dir) {		",
"			if (!myrecs[cindex]) { myrecs[cindex] = 0;	}		",
"			var irec = myrecs[cindex];		",
"			if (dir<0 && irec>=0) {	irec -= 1;}		",
"			else if (dir>0 && irec<=0) {irec += 1;}		",
"			myrecs[cindex] = irec;		",
"			var sbg = 'none';		",
"			var srec= '';		",
"			switch(irec) {		",
"				case -1: cbresize(cindex, 0); sbg = '#DDA'; srec = ' I UNrecommend'; break;		",
"				case 1:	cbresize(cindex, 1); sbg = 'lightgreen'; srec = ' I recommend'; break;		",
"			}		",
"			var recstatelem = document.getElementById('TBVrecstatus'+cindex);		",
"			if (!recstatelem) alert('cant find recstate for comment#' + cindex);		",
"			recstatelem.style.background = sbg;		",
"			recstatelem.innerHTML = srec;		",
"		}		",
"		function cbresize(cindex, bshow) {		",
"			var odiv = document.getElementById('TBVcbody' + cindex);		",
"			if (!odiv) {return;}		",
"			odiv.style.fontSize = (bshow==0)? '2pt' : '10pt';		",
"			document.getElementById('TBVbtnupcbody'+cindex).style.display = (bshow)? 'none' : 'block';		",
"			document.getElementById('TBVbtndncbody'+cindex).style.display = (bshow)? 'block' :'none';		",
"			var obr = document.getElementById('TBVbreakwpast' + cindex);		",
"			if (obr) {obr.style.display = (bshow)? 'inline' : 'none';}		",
"			var orated = document.getElementById('TBVrating' + cindex);		",
"			if (orated) {orated.style.display = (bshow)? 'block' : 'none';}		",
"			odiv = document.getElementById('TBVxref' + cindex);		",
"			if (odiv) {odiv.style.fontSize = (bshow)? '100%' : '80%';}		",

"		}		",
"		function numcomments() {		",
"			return TBVMeta4All('numcomments');		",
"		}		",
"		function expandtheseonly(iarr) {		",
"			var n = numcomments();		",
"		 for (var index=1; index<=n; ++index) {		",
"			var bshow = ItemInArr(index,iarr);		",
"			cbresize(index,bshow);		",
"			if (bshow) showIt('moretext'+index,bshow);		",
"		}}		",
"		function cbresizeall( bshow) {		", // cb=commentbody
"			var n = numcomments();		",
"		 for (var index=1; index<=n; ++index) {		",
"			cbresize(index,bshow);		",
"			showIt('moretext'+index,bshow);		",
"		}}		",
"		function cbrestoresizeall() {		",
"			var n = numcomments();		",
"		 for (var index=1; index<=n; ++index) {		",
"			  var odiv = document.getElementById('TBVcbody' + index);		",
"			  if (odiv) {	cbresize(index,TBVMeta4Comment(index,'origvis')); showIt( 'moretext' + index,false); } 	",
"		}}"
		];
	addGlobalJS(clientJSArr.join("\n"));
}
// --------------
function WithAllTBVStuffRemoved(shtml) {	
	 // bounded by [_   _]
		return shtml.split("\n").join(' ').replace(/\[_.*_\]/m,'');
	//	return stext.replace(/\[_.*_\]/m,'');	 // bounded by [_   _]
}
function TBVStuffFrText(shtml) {	
	// hack, shouldnt have to do this, multiline should handle it
	var stext = shtml.split("\n").join(' ');

	var re = /\[_(.*?)_\]/m;
	if (re.test(stext)) {
		var ret=RegExp.$1;
		//	if (bdebug) {					tbvgmlog("matches! " + ret); }
		return ret.replace(/<\/?b>/g,''); // WithAllTagsRemoved(ret); //rm bolding
	}
	return '';
}
function TBVHashStrFrText(shtml) {	
	var re = /\[(.*?)\]/m;
	if (re.test(shtml)) {
		var ret=RegExp.$1.replace(/;\s*/mg,';');
		return ret;
	}
	return '';
}
// I recommend: mtobis#185</a>. Using Dot Earth Defender v.2.6.
function TBVIrecStrFrText(shtml) {	
	var re = /I recommend: (.*?)>\./; // beware of david b. benson!
	return (re.test(shtml))? RegExp.$1+'>' : '';
}

// --------------
function SetFieldValueIfEmpty(fieldnode, sval)
{
	if (!fieldnode.value.length && sval && sval.length) {
		GM_log('Setting ' + fieldnode.name + ' to ' + sval);
		fieldnode.value = sval;
	}
	else {
		GM_log('Not Setting field_' + fieldnode.name + '(its ' + fieldnode.value + ') to ' + sval);
	}
}
function tbvgmlognode(s,anode)
{
	tbvgmlog(s + anode.nodeName + anode.className);
}

// --------------------------------------------
function AddSomePageNavLinksEtc(bPageWillHaveArchives) // mostly green-highlighted stuff
{
	tbvgmlog("AddSomePageNavLinksEtc");
	var nodeToModify = document.getElementById('header'); // was frontpage_link');
	
	
	if (!bPageWillHaveArchives) { // we're not on the front page
	 	// we'll want to provide handy nav links
		nodeToModify.setAttribute('style',"background:#CFC; font-weight:normal;");
		
			var junknode = CreateNodeOfClass('span', '');
			junknode.innerHTML = '<br/>'  + LinkToAnchorStr('GO TO COMMENTS','comments');

			nodeToModify.appendChild(junknode); 
	}

	// now for the tasteful branding
	var tbvbranddiv = CreateNodeOfClassAndId('div','tbvpagebrand','');
	tbvbranddiv.innerHTML =  [
		'On the job:',
		gsScriptname,
		'v.' + gsVersion,
		',',
		gsScriptPurpose
		].join(' ');
	// trbl
	var sstyle = 'background:#CFC; opacity:.40; font-weight:italic; margin:20px 5px 6px 5px;';
	tbvbranddiv.setAttribute('style', sstyle);
		
	nodeToModify.appendChild(tbvbranddiv);
}	

function tbvgmlog(s)
{
	GM_log(s);
}
function vtbvgmlog(s)
{
	// GM_log(s);
}
function lognodename(anode)
{
	tbvgmlog(anode.nodeName);
}
// --------------------------------------------
function ModifyCommentForm()	
{
	var formnode = document.getElementById('commentform');
	var sformOnsubmitAction = formnode.getAttribute("action"); // onsubmit"); // get this asap before we muck with it - "return http://dotearth.blogs.nytimes.com/wp-comments-post.php"
	
	GM_log(sformOnsubmitAction);
	
	// NO, alas. it dont work no more. (this used to be the new Submit action)
	var sTBVHandleSubmit = [
		"		function TBVHandleSubmit() {		",
		"			AppendTBVInfoToComment();		",
//		"			alert('Submitting...'); ",
					sformOnsubmitAction + ";" ,
		"		}		"
		].join("\n");
//	GM_log(sTBVHandleSubmit);
//	addGlobalJS(sTBVHandleSubmit);

	var authfield = document.getElementById('author');
	var authfor = authfield.parentNode;

	var emailfield = document.getElementById('email');
	// var emailfor = emailfield.parentNode;

	var commentfield = document.getElementById("comment");
	var commentfor = commentfield.parentNode;

	SetFieldValueIfEmpty(emailfield,TBVSettings['myemail']);
	SetFieldValueIfEmpty(authfield,TBVSettings['myname']);
	commentfield.value = WithAllTBVStuffRemoved(commentfield.value);
	
	
//	emailfield.value = ''; // safety, preventing more accidental Submits

	var authRealNode = CreateNodeOfClass('span','itsreal');
	var brealname =(TBVSettings['itsreal']=='y');
	if (brealname && authfield.value != TBVSettings['myname']) {
		brealname = false; // if it's changed, we dunno if it's real or not
		tbvgmlog("diffnames -" + TBVSettings['myname'] + "vs" + authfield.value);
	}
	var schecked = brealname? 'checked' : 'unchecked';
	vtbvgmlog('schecked='+schecked);	
	authRealNode.innerHTML =  [
		"<label for='TBVckrealname' style='color:darkorange;'>",
		"<span>It's my real name</span>",
		"<input type='checkbox' name='TBVckrealname' id='TBVckrealname' " + schecked + ">",
		"</label><br/>"
		].join("");
	formnode.insertBefore(authRealNode, authfor.nextSibling)
	vtbvgmlog('done authreal');	


	var newCommSideNode = CreateNodeOfClassAndId('div','sidenode','');
	var backlinksNode = CreateNodeOfClassAndId('div','returntos','');
	newCommSideNode.appendChild(backlinksNode);
// tbvgmlog('done newCommSideNode');	
	var sTBVbtnPrep4Submit = NewButtonStr('TBVbtnprep','Prep for Submit', 'PrepareCommentForSubmit()');
	var sTBVbtnpreview = NewButtonStr('TBVbtnpreview','Preview (below)', 'PreviewComment()');
	newCommSideNode.appendChild(CreateSpanContaining(sTBVbtnpreview  + ' ' + sTBVbtnPrep4Submit));

vtbvgmlog('done newCommSideNode2');	
//	formnode.insertBefore(CreateNodeOfClass('div', 'TBVclearfloats'),commentfor);
//lognodename(commentfield);
//lognodename(newCommSideNode);

//	var dum = CreateNodeOfClassAndId('div','dumm','');
	var button4Submit = document.getElementById('submit-comment');

	formnode.insertBefore(newCommSideNode,button4Submit.parentNode); // commentfield.parentNode.nextSibling);
vtbvgmlog('done newCommSideNode22');	

//	lognodename(button4Submit);
	var spreviewpane = [
			"<label for='TBVcommentpreviewpane' ><div style='color:darkorange;'>Preview</div>",
			"<div name='TBVcommentpreviewpane' disabled='1'  id='TBVcommentpreviewpane'>(no preview yet)</div>",
			"</label>"
		].join("\n");
vtbvgmlog('done previewNode0');	
	var previewNode = CreateSpanContaining(spreviewpane);
vtbvgmlog('done previewNode1');	
//	lognodename(previewNode);
//	lognodename(formnode);
// this breaks.	formnode.insertBefore(previewNode,button4Submit);
		formnode.appendChild(previewNode);
vtbvgmlog('done previewNode');	

	if (OKToShowRecs()) {				// still need this since otherwise unrecs don't show.
		var sviewrecs = [
			NewButtonStr('TBVbtnviewmyrecs','View my Recs and Unrecs','VerboseViewMyRecsAndUnrecs();'),
			NewButtonStr('TBVimportmyrecs','Import Recs and Unrecs','ImportMyRecsAndUnrecs();'),
			"<div name='TBVcommentrecspane' disabled=1  id='TBVcommentrecspane'>",
				"(click the button to see which comments you've recommended or unrecommended)</div>",
		].join("\n");
		var	viewrecsNode = CreateSpanContaining(sviewrecs);
		formnode.appendChild(viewrecsNode);
		vtbvgmlog('done previewNode');	
	}

	button4Submit.value = button4Submit.value.replace(/submit/i,'SUBMIT comment') + ' (but copy and save it somewhere safe first!)';

	// if developing, use this: 
	// formnode.setAttribute("action","alert('not submitting');" ); // 
	//	for release, use this:
//	formnode.setAttribute("action","return TBVHandleSubmit();");
	// formnode.style = stylesaved;
tbvgmlog('done modcommentform');	
}

// -----------------------------------------------------
// generating html
// ---------------------
function AsNamesSpan(s) {
	if (!s) { GM_log('asnamesspan s null '); }
	var ret= "<span class='TBVnames'>" + s + "</span>"
	return ret;
}
function QuotedElUnlessEmpty(selem, sval)
{
	return (sval=='')? '' : [' ',selem,"='",sval,"'"].join('');
}
function NewButtonStr(sid,svalue,sonclick, bhide)
{
	var sstyle = (!bhide)? '' : " style='display:none'";
	var ret= [
		"<input type='button' class='TBVsmallbutton' name='",
		sid,
		"' id ='",
		sid,
		"' value ='",
		svalue,
		"' onclick='",
		sonclick.replace(/'/mg,"\""),
		"' ",
			sstyle,
		">"
		].join('');
		// tbvgmlog(ret);
		return ret;
}		

function AsSpanOfClassAndTitle(sclass, stitle, scontent) {
	if (!scontent) { return ''; }
	
	var tmptitle = QuotedElUnlessEmpty("title",stitle);
	
	var ret= [
			"<span class='",
		sclass,
			"' ",
		tmptitle,
			">",
		scontent,
			"</span>" 
		].join('');
	// tbvgmlog(ret);
	return ret;
}
function AsSpanOfClassAndId(sclass,sid, scontent) {
	var tmpclass = QuotedElUnlessEmpty("class",sclass);
	var tmpid = QuotedElUnlessEmpty("id",sid);
	var ret= [
			"<span ",
		, tmpclass,tmpid,
			">",
		scontent,
			"</span>" 
		].join('');
	// tbvgmlog(ret);
	return ret;
}

function CreateNodeOfClass(snodetype,sclass)
{
		var retNode = document.createElement(snodetype);
		if (sclass!='') {
			retNode.setAttribute('class', sclass);
		}
		return retNode;
}
function CreateNodeOfClassAndFillWithText(snodetype,sclass,stext)
{
		var retNode = CreateNodeOfClass(snodetype,sclass);
		retNode.innerHTML = stext.replace(/\n/mg,'<br/>');
		return retNode;
}
function CreateNodeOfClassAndId(snodetype,sclass, index)
{
		var retNode = CreateNodeOfClass(snodetype,sclass);
		if (sclass!='') {
			retNode.setAttribute('id', sclass+index);
		}
		return retNode;
}
function CreateSpanContaining(sinner)
{
		var retNode = document.createElement('span');
		retNode.innerHTML = sinner;
		return retNode;
}
function CommentIdentStr(sname,cindex)
{
	return AsTinyName(sname) + '#' + cindex;
}
function LabelFrAuthor(iauth,prependwspace)
{
	if (!gbLabelAuthors) { return ''; }
	sspace = (prependwspace)? ' ' : '';
	return [
		sspace,
		'(',
		ClassnameFrAuthor(iauth),
		')'
		].join('');
}	
function MiniLinkstrForCIndex(cindex)
{
	if (!cindex) { return ''; }
	var iauth = AuthorFrComment(cindex);
	return [
		"<a href='" + gsOurBaseURL + "#",
//		"index" + cindex,
		LiIdStrFrIndex(cindex),
		"' class='",
		ClassnameFrAuthor(iauth), 
		"' title='",
		Authname(iauth), 
		"'>",
		cindex,
		'</a>'
		].join('');
}
function LinkstrForCIndex(cindex)
{
	if (!cindex) { return ''; }
	var iauth = AuthorFrComment(cindex);
	var sname = Authname(iauth);
	if (!sname || sname=='')
	{
		tbvgmlog('no authorlc for #' + cindex );
		return 'unknown';
	}

	return	[		
	"<a href='" + gsOurBaseURL + "#",
	//		"index" + cindex,
		LiIdStrFrIndex(cindex),
		"' class='",
		ClassnameFrAuthor(iauth), 
		"' title='",
		sname,
		LabelFrAuthor(iauth,true),
		"'>",
		CommentIdentStr(sname,cindex),
		'</a>'
		].join('');
}
function SimplyLinkified(cindex)
{
	var sliid = LiIdStrFrIndex(cindex);
	return (!sliid.length)? cindex + '(?)' : 
		 "<a href='" + gsOurBaseURL  + "#" + sliid + "'>#" + cindex + "</a>";
}

function LinkstrsForCArr(indexes)
{
	var htmls = [];
	var iarr = NumSortArr(indexes);
	var n = iarr.length;
	for (var i=0; i<n; ++i) {
		var slink = LinkstrForCIndex(iarr[i]);
		if (slink.length) { htmls.push(slink); }
	}
	return htmls; // .reverse();
}
function MiniLinkstrsForCArr(indexes)
{
	var htmls = [];
	var iarr = NumSortArr(indexes);
	var n = iarr.length;
	for (var i=0; i<n; ++i) {
		var slink = MiniLinkstrForCIndex(iarr[i]);
		if (slink.length) { htmls.push(slink); }
	}
	return htmls; // .reverse();
}

function AsLinkWithURLAndTitle( s, surl, sextra,stitle)
{
	if (s=='') { return ''; }
	var tmptitle = QuotedElUnlessEmpty('title',stitle );
	var tmphref = QuotedElUnlessEmpty('href',surl );

	var sret = (tmptitle=='' && tmphref=='')?  	s : 
		["<a", tmphref,  tmptitle, ' ', sextra, ">", s, "</a>"].join('');
	return sret;
}

function CommentAuthorHTML(ourCIndex,authindex,bIsRealName)
{
		var sclass = ClassnameFrAuthor(authindex);
		var sAuthname = Authname(authindex)
		var sSpanUs = AsSpanOfClassAndTitle(sclass,'',sAuthname);
		var sSpanForRealName = (bIsRealName)? AsSpanOfClassAndTitle('arealname','(real name)','*'): '';

		var sSpanForLabel = AsSpanOfClassAndTitle('labeling','',LabelFrAuthor(authindex,false));
		
		var sUs = 
			[
				"<span class='TBVciteB' id='",
					CommentIdentStr(sAuthname,ourCIndex),
						"'>",
					AsLinkWithURLAndTitle(sSpanUs, 
							"#auth" + authindex,
							"onclick='showIt(\"TBVAuthList\",true);'",
							"To other comments by " + sAuthname + ' on this page'),
					sSpanForRealName,
				"</span>",
				sSpanForLabel
				].join('');
	return sUs;
}
// ---------------------

function CreateFillAttachDoemallNode(irechots,iauthhots, doccomments)
{
	var doemallNode = CreateNodeOfClass('div', 'TBVDoemallBtns');


	var shotsCSV = irechots.join(',');
	var sDaHotRecs = (irechots.length==0)? '' : 
		NewButtonStr('TBVrechotsonly',
	//	'Click to shrink all except these (' + 	shotsCSV + ')',
			'Recs only',
		 'expandtheseonly([' + shotsCSV + ']);'
		);

	shotsCSV = iauthhots.join(',');
	var sDaHotAuths = (iauthhots.length==0)? '' : 
		NewButtonStr('TBViauthhotsonly',
			'Experts only',
//		'Click to shrink all except these (' + 	shotsCSV + ')',
		 'expandtheseonly([' + shotsCSV + ']);'
		 );

	doemallNode.innerHTML = [ "Resize comments:<br/>",
		NewButtonStr('TBVbtnupcbodyall','Enlarge All','cbresizeall(1);' ),
		NewButtonStr('TBVbtnupcbodyall','ReInit All','cbrestoresizeall();' ),
		NewButtonStr('TBVbtndncbodyall','Shrink All','cbresizeall(0);' ),
		sDaHotRecs,
		sDaHotAuths
		].join(' &nbsp; ');
	 var achildNode = doccomments.childNodes[2];
	 doccomments.insertBefore(doemallNode, achildNode);
	 doccomments.insertBefore(CreateNodeOfClass('div', 'TBVclearfloats'),achildNode);
}
function LinkToAnchorStr(slabel, sid)
{
	if (slabel.length && sid.length)
		return "<a href='" + gsOurBaseURL + "#" + sid + "'>" + slabel + "</a>";
	else return '';
}
function CreatePagenavNode()
{
	var jumpnode = CreateNodeOfClass('div', 'TBVToAnchors');
	jumpnode.innerHTML = [
		"Jump to...", 
		LinkToAnchorStr('pagetop', 'header'), 
		LinkToAnchorStr('commentstop', 'comments'),
		LinkToAnchorStr('commentform', 'respond')
	].join(' ');
	jumpnode.setAttribute('style','float:right;');
	return jumpnode;
}

function CreateAndFillDEControlsNode(ourCIndex,oktoshowrecs,authnamelc)
{
	var dcnode = CreateNodeOfClassAndId('div', 'TBVCommentBtns',ourCIndex);

	var tmpnode = CreateNodeOfClass('div', '');
	tmpnode.setAttribute('style','float:left;');
	var stitle = 'Compose a reply to ' + ReCapped(authnamelc);
	var sliid = LiIdStrFrIndex(ourCIndex);
	var sreplyaction = "Reply2Comment( %d,'" + sliid + "','" + stitle + "');";
	// tbvgmlog(sreplyaction);
	tmpnode.innerHTML = [
		NewButtonStr('TBVbtnreplyto%d','Reply',sreplyaction),
	].join('').replace(/%d/mg,ourCIndex);;
	dcnode.appendChild(tmpnode);

	var aiwnode = CreateNodeOfClass('div', 'TBVcbodyresizebtns'); // alice in wonderland toggle
	aiwnode.innerHTML = [
		NewButtonStr('TBVbtnupcbody%d','Enlarge','cbresize(%d,1);'),
		NewButtonStr('TBVbtndncbody%d','Shrink','cbresize(%d,0);')
	].join('').replace(/%d/mg,ourCIndex);
	dcnode.appendChild(aiwnode);
	
	if (oktoshowrecs) {
		var rnode = CreateNodeOfClass('div', 'TBVdorecbtns');
		rnode.innerHTML = ([ 
			NewButtonStr('TBVbtnunrec%d','Unrec','increcstatus(%d,-1);'),
			NewButtonStr('TBVbtnrec%d','REC','increcstatus(%d,1);')
				].join("\n")).replace(/%d/mg,ourCIndex);
		dcnode.appendChild(rnode);
	}
	dcnode.appendChild(CreatePagenavNode());

	dcnode.appendChild(CreateNodeOfClass('div', 'TBVclearfloats'));
	return dcnode;
}

function CreateAndFillThreadingNode(ourCIndex,bRealName)
{
	var ourcom = comTbl[ourCIndex];
	if (!ourcom)
		return null;
		
	var tnode = CreateNodeOfClass('div', 'TBVthreading');
	var sBegatIcon = AsSpanOfClassAndTitle('TBVbegaticon', '... called forth a reply by...',"-&gt;");

	// rming this, since it breaks even when it shouldn't.
	var sNewlineSpan = ''; AsSpanOfClassAndId('','TBVbreakwpast'+ourCIndex, "<br/>");
		
		var sAncestors = (ourcom.upstrs.length==0)? '' : 
			AsSpanOfClassAndTitle('TBVreplyingto','(reply to previous comments)',
				AsNamesSpan(LinkstrsForCArr(ourcom.upstrs).join(', ')) + sBegatIcon);
		if (sAncestors!='') {
			sAncestors += sNewlineSpan; // rm if shrinking
		}
		var sDescendants = (ourcom.dnstrs.length==0)? '' : 
			AsSpanOfClassAndTitle('TBVrepliedtoby','replies to this comment:',
				sBegatIcon + AsNamesSpan(LinkstrsForCArr(ourcom.dnstrs).join(', ')));
		
		var sUs = CommentAuthorHTML(ourCIndex,ourcom.authindex,bRealName);
		
		tnode.innerHTML = [ ' ', sAncestors, sUs, sDescendants].join(" ");
		return tnode;
}
// ---------------------
function iconCountStr(n)
{
	var s='******************************************************************';
	return (n>40)? n : s.slice(0,n);
}

function AsRecSpan(sclass,siconclass,stitle,shtmls)
{
	var nrecs = shtmls.length;
	var ret = (nrecs==0)? '' :
		[
				"<div class='",
			sclass,
				"' style='border:1px'>\n",
			AsSpanOfClassAndTitle(siconclass, '', iconCountStr(nrecs)),
			' <small>',
			stitle,
			'</small> ',
			AsNamesSpan(shtmls.join(', ')),
				"</div>\n" ].join('');
	// tbvgmlog(ret);
	return ret;
}
// ---------
function CreateAndFillRatersNode(ourCIndex)
{
	var ourcom = comTbl[ourCIndex];
	
	if (!ourcom || (!ourcom.reccers.length && !ourcom.unreccers.length))
		return null;
		
	var rnode = CreateNodeOfClass('div', 'TBVraters');
	rnode.setAttribute('id', 'TBVrating'+ourCIndex);
	
	rnode.innerHTML = 
		AsRecSpan('recs','TBVrecicon','Recommended by',
			LinkstrsForCArr(ourcom.reccers))
		+ AsRecSpan('unrecs','TBVunrecicon','thumbs down by',
			LinkstrsForCArr(ourcom.unreccers));
	return rnode;
}
// -----------------------------------------------------

function CreateAndFillMetaNode(sid, hmeta)
{
		var mNode = CreateNodeOfClass("span", "TBVmeta");
		mNode.setAttribute('style', "display:none");

		mNode.setAttribute('id', sid);
		mNode.innerHTML = HashToStr(hmeta);
		return mNode;
}
// --------------
function CreateAndFillNodeOfClassFrHash(snodetype, sclass,slabel, ahash) 
{
		return CreateNodeOfClassAndFillWithText(snodetype, sclass,StrFrHash(slabel, ahash));
}
// --------------
function CreateAndFillTBVSettingsNode(sid)
{
 var sAddenda = (!gbItsDED)? '\n(fyi, the filter/label/shrink settings shouldnt be relevant  on this blog.)' : [
 		"\nI said 'most'; alas as of v3.0, the only way you can change the  ",
 		"'precategorized authors' lists  is to hand-edit them in the script. ",
 		"\n(to hand edit: Tools - Greasemonkey - ManageUserScripts, then",
 		"select '" + 	gsScriptname + "' and hit Edit;",
		"the lists are up near the top.  Once you've saved your edits (and beware of quotation marks!) the script will run with your changes, until you uninstall it and reinstall the same or another version. ))" 
		].join(' ');

 var smostof = (!gbItsDED)? '' : "_most_ of the";
 
	var sInstructionsToModify = [
		"You can **edit**", 
		smostof,
		gsScriptname, 
		" settings from Firefox's Tools - Greasemonkey - UserScriptCommands. ",
		"\n (then refresh the page! otherwise it probably runs with the old settings.) ",
		sAddenda
		].join(' ');
		
	var retnode = CreateNodeOfClass('div', sid);

	var sdatanode = "TBVSettingsData";
	var showhidenode = CreateNodeOfClass('div', 'showhidebtns'); 
	showhidenode.innerHTML = [
		NewButtonStr('show' + sdatanode,
			'Show my ' + gsScriptname + ' Settings',
			"showIt('" + sdatanode + "',true);"),
		NewButtonStr('hide' + sdatanode,
			'Hide',
			"showIt('" + sdatanode + "',false);" ,  true)
	].join('');
	showhidenode.setAttribute('style',"background:#CFC;"); // diff from tbv's default css

	var datanode = 	CreateNodeOfClassAndId('div', sdatanode,''); 
	datanode.appendChild(document.createTextNode(sInstructionsToModify));
	datanode.appendChild(CreateAndFillNodeOfClassFrHash('div','TBVSettings', 	
		"Script settings",TBVSettings));
	datanode.appendChild(document.createElement('p')); // spacing
	// lists of known authors, by category
	
	if (gbItsDED) {
		datanode.appendChild(CreateAndFillNodeOfClassFrHash('div','TBVKnownAuths', 
		"Known (Categorized) commenters", authnamesByClassTbl));
	}
	datanode.style.display = 'none';
	showhidenode.appendChild(datanode);

	retnode.appendChild(showhidenode);

	return retnode;
}
// --------------
function GoogleSearchURLFrName(sname)
{
	return "http://www.google.com/search?hl=en&q=%22"
		+ sname.replace(/\s+/g, "+")
		+ "%22+site%3A" 
		+ gsBlogname 
		+ ".blogs.nytimes.com";
}
// --------------
function BlogGoogleLink4Name(sname,sinner)
{
		return [
			"<a href='",
			GoogleSearchURLFrName(sname),
			"' title='Google for all " + gsBlogname + " comments by " + sname,
			" (in new window)",
			"' target='GoogleFor" + sname.replace(/\s/g,''),
			"' onclick='openRequestedPopup(this.href, this.target); return false;'" ,
			">",
			sinner,
			"</a>"
			].join('');
}
// --------------
function CreateAndFillAuthorlistNode() // which goes above comments
{
	tbvgmlog('CreateAndFillAuthorlistNode');
		var mNode = CreateNodeOfClassAndId("span", "TBVAuthList",'');
		authlinksArr = [];
		for (var iauth in authTbl) {
			var carr = NumSortArr(authTbl[iauth].comments);
			// if (iauth==28) tbvgmlog(carr.join('-'));
			if (carr.length) {
				var sclass = ClassnameFrAuthor(iauth);
				var snamelc =authTbl[iauth].namelc;
				authlinksArr.push(
					[
					"<span class='",
					sclass,
					"' id='auth",
					iauth, 
					"'>",
					snamelc, // not BlogGoogleLink4Name(snamelc),
					"#" ,
					MiniLinkstrsForCArr(carr).join(','), // 1 per comment by this auth
					'(',
					BlogGoogleLink4Name(snamelc,'G'),
					')</span>'
					].join('')
					);
			}
		}
		mNode.innerHTML = (!authlinksArr.length)? '' : '<br/>' + authlinksArr.sort().join(' '); 
//		tbvgmlog(mNode.innerHTML);
		return mNode;
}
// -----------------------------------------------------
// in development, alas.   Can't get the button over to the right!
// ----------------
function HideEndIfLongComment(cNode,ourCIndex)
{
	vtbvgmlog('HideEndIfLongComment');
	var sinner = cNode.innerHTML;
	var ntotal = sinner.length;
	if (ntotal < 2000) return;
	
	if (gsBlogHostname.length && sinner.indexOf('[' + gsBlogHostname)>=0) { 
		// bloghost responded; dont truncate visible comment
		return;
	}
	var nmaxvis=700;
	var nkids = cNode.childNodes.length -1;  // p cite
//	var zelast = cNode.lastNode; // p-cite, most likely
	var i=0; 
	var imore=nkids+1;
	for (i=n=0; i<nkids && n<nmaxvis; ++i) {
		if (cNode.childNodes[i].nodeName=='P')
			n += cNode.childNodes[i].innerHTML.length;
	}
	imore=i;
	
	if ((ntotal - n) < 700) { // feh. not enough left that it's worth bothering to do this
		return;
	}

	var moreTextNode = CreateNodeOfClassAndId('div','moretext',ourCIndex);
	for (i=imore; i<nkids; ++i) {
		moreTextNode.appendChild(cNode.removeChild(cNode.childNodes[imore]));
	}		

	var sdatanode = "moretext"+ourCIndex;
	var shwrapper = CreateNodeOfClass('div', 'btnwrapper'); 
	var showhidenode = CreateNodeOfClass('div', 'morelessbtns'); 
	showhidenode.innerHTML = [ " ",
		NewButtonStr('show' + sdatanode,
			' (more) ',
			"showIt('" + sdatanode + "',true);"),
		NewButtonStr('hide' + sdatanode,
			' (less) ',
			"showIt('" + sdatanode + "',false);" ,  true)
	].join('');
	moreTextNode.style.display = 'none';
	shwrapper.appendChild(showhidenode);
	cNode.appendChild(shwrapper);
	cNode.appendChild(moreTextNode);
}
// -----------------------------------------------------


function HashFrStr(s) {				
	var h=[];
	var sarr = s.replace(/;\\s*/mg,';').split(';'); var n = sarr.length;
	for( var i =0; i<n; ++i) {
		var kv = sarr[i].split(':');
		h[kv[0]] = kv[1];
	}	return h;
}
function Recstr(slabel, scsv)
{
// dont put a # infront, since script will then interpret it as a replyto
	return (!scsv || !scsv.length)? '' : slabel + ' ' + MiniLinkstrsForCArr(scsv.split(',')) + '. ';
}
function OurURLAnchorIdent()
{
	var ourURL = window.location.href; // does it have an anchor?
	var ourURLEnd = ourURL.replace(/.*\//,''); // now we're just a filename plus perhaps anchor
	var re = /\#/;
	return (!re.test(ourURLEnd))? '' : ourURLEnd.replace(/.*\#/,'#');
	// i.e. '#comment-66113' or ''
}

// -------------------------------------------------------------------
// -------------------------------------------------------------------

// -----------------------------------------------------
//	con permiso II
// --------------------------

function StatsArchiveLinks()
{
	var monthlinks = xpath("//link[@rel='archives'].href");
	var n = monthlinks.snapshotLength;
	for (var i = 0; i < n; ++i) {
		var molink = monthlinks.snapshotItem(i);
	}
}

// for link, we're scraping this:
// 	<h2 class="post-title2"><a href="http://dotearth.blogs.nytimes.com/2008/06/02/nasa-investigation-press-office-mischaracterized-and-limited-flow-of-findings-on-climate-science/" rel="bookmark" title="Permanent Link: Investigators: NASA Officials &#8216;Mischaracterized&#8217; and Limited Flow of Findings on Climate">Investigators: NASA Officials &#8216;Mischaracterized&#8217; and Limited Flow of Findings on Climate</a></h2>
// for #comments, this:
// <li class="comments-tool"><a href="http://dotearth.blogs.nytimes.com/2008/06/02/nasa-investigation-press-office-mischaracterized-and-limited-flow-of-findings-on-climate-science/#comments" class="post-comment" title="Comment on Investigators: NASA Officials 'Mischaracterized' and Limited Flow of Findings on Climate">Comments (125)</a></li>


function MinOf(a,b)	
{
	return (a<b)? a : b;
}

function FillRecentPostsNode(sOurURL,archNode)
{
	archNode.appendChild(document.createTextNode(ReCapped(gsBlogname) + " Recent Posts"));
	
	var fpcomments = xpath("//li[@class='comment-link']"); // was li[@class='comments-tool']");	
	var n = fpcomments.snapshotLength;
	tbvgmlog(n + ' posts');
	for (var i=0; i<n; ++i) {
		var fplinode = fpcomments.snapshotItem(i);
		var clink = fplinode.firstChild;
		var plink = document.createElement('a');
		// tbvgmlog(plink.innerHTML);
		plink.setAttribute('href', clink.getAttribute('href').replace(/#comments/,''));
		var stitle = clink.getAttribute('title').replace(/^Comment on /,'');
		// plink.setAttribute('title', null);
		var ncomments = clink.innerHTML.replace(/[^\d]/g, '');
		if (!ncomments.length) {
			ncomments=0;
		}
		plink.innerHTML = ReCapFirstLetter(stitle) + ' (<b>' + ncomments + '</b>)';
		archNode.appendChild(document.createElement('br'));
		archNode.appendChild(plink);
	}

}
function AsList(sarr)
{
	return (!sarr.length)? '' :  "<ul><li>" + sarr.join('</li><li>') + "</li></ul>";
}
function AddRecentPostsToSidebar(sOurURL)
{
				tbvgmlog("AddRecentPostsToSidebar");
	var recentsNode = CreateNodeOfClassAndId('div','TBVrecent','');
	var sidediv = document.getElementById('cCol'); // blog_sidebar');
	sidediv.insertBefore(recentsNode, sidediv.firstChild);
	FillRecentPostsNode(sOurURL,recentsNode); // here a miracle occurs
	var sstyle = 'background:#CFC; opacity:.85; font-size:110%;';
	recentsNode.setAttribute('style',sstyle);
}



function WeAreTheFrontPage(sURL)
{
	var stmp = sURL.replace(/.*blogs.nytimes.com\//,'').replace(/[\#\?].*/,''); // anything left?
	return (trimmed(stmp).length==0);
}
function TotalNumComments()
{
	var doccomments  = document.getElementById('comments')
	var sofarNode = doccomments.childNodes[1]; // 'of n comments'
	return parseInt(sofarNode.innerHTML.replace(/.* of\s+/,'').replace(/ .*/,''));
}


var sbase  = GetBaseURL();
var	pullpage =0;
var numSubpagesRxdAndParsed=0;
var	lastPageToPull = -1;
// var requestIntervalId = 0;
//var comParentNode = null;
var subpagesCommentsStorageNode = CreateNodeOfClass('div',''); // 1 childnode per subpage, not counting p1


// -------------------------------------------------------------------------------------
//	Once the page has loaded, we can start here...
// --------------------------------------------------
window.addEventListener("load", function(e) {
	GM_log('onload...');

	var bAttemptToGlueIfPossible = false; // alas, 'true' doesn't work, since the subpages get parsed differently and act like misfits forever more.
	
	var sOurURL = window.location.href.toString();
	
//	http://dotearth.blogs.nytimes.com/2008/09/06/confirmation-of-open-water-circling-north-pole/?apage=2#comments
	GM_log(sOurURL);
	if (WeAreTheFrontPage(sOurURL)) {
			GM_log("we are the front page - assemble recent posts");
			AddSomePageNavLinksEtc(true);
			AddRecentPostsToSidebar(sOurURL.replace(/\?.*/,'')); // rm the 'options' crap from our url
			return;
	}
	tbvgmlog("not front page");
	
	var doccomments =	document.getElementById('comments');
	tbvgmlognode('doccomments', doccomments);


	var ucommentdiv = document.getElementById('respond');


	var pglinksets = xpath("//div[@class='pages']");
	var bIsMultipagePost = (pglinksets.snapshotLength> 0);

	var bIsFirstOfMultipage = false;
	if (bIsMultipagePost) {
		if (sOurURL.match(/\?apage=(\d+)/)) {
			var pageno = parseInt(RegExp.$1);
			bIsFirstOfMultipage = (pageno == 1);
			ourMinCommentIndex	= (pageno-1) * maxCommentsPerPage;
		} else {
			bIsFirstOfMultipage = true;
		}
	}

	var bRunDEDOnThisPage = (!bIsMultipagePost || bIsFirstOfMultipage || !bAttemptToGlueIfPossible);
	
	if (!bRunDEDOnThisPage) {
		GM_log("'" + sOurURL + "' not a page for " + gsScriptname + " (it's not the front page, or isn't 1st pg of comments)");
		if (bIsMultipagePost && !bIsFirstOfMultipage)	{
				GM_log("(not first page of comments)");
		}
		return;
	}

// --------------------------------------
	GM_log(gsScriptname + " is running, on " +  sOurURL);
	
	
//	PullCommentsFromPagetext2(document.firstChild.innerHTML())
// return;

	ucommentdiv.style.display = 'none';
	if (doccomments) {
		doccomments.style.display = 'none'; // its gonna be ugly
	}

	var npagestoget = 0;
	if (!bIsFirstOfMultipage || !bAttemptToGlueIfPossible) {
		DigestOurComments(); // do it directly, dont grab more pages
	}
	else {
			GM_log("bIsFirstOfMultipage");
			// hide links to pages you won't need to visit
			for (var i=0; i< pglinksets.snapshotLength; ++i) {
				// pglinksets.snapshotItem(i).style.display='none';
			}
				tbvgmlognode('5', doccomments.childNodes[5]); // ol
			var olnode  = doccomments.childNodes[5];
			
			GM_log("olnode has #eleements:" + olnode.childNodes.length);
			for (var i=0; i<0; i) {
				tbvgmlognode('s' + i, olnode.childNodes[i]);
			}

			// now we grab them for you
			var lastpg = 1+ parseInt(TotalNumComments()/maxCommentsPerPage);
			tbvgmlog(lastpg + ' lastpg');
			npagestoget = lastpg-1;
			pullpage =2;
			lastPageToPull = 2; // lastpg;

			numSubpagesRxdAndParsed=0;
			PullAndAppendCommentsFrNextPageOfGrp();
			var timeoutId = setTimeout(TimeoutGettingPages, 4000);
	}
	
	function TimeoutGettingPages()
	{
		lastPageToPull = -1;
		GM_log("Timeout getting pages, after we successfully rxd #pages=" + numSubpagesRxdAndParsed);
		if (numSubpagesRxdAndParsed>0) {
			AppendOtherPagesComments();
		}
		DigestOurComments();
	}
	function AppendOtherPagesComments() 
	{
		GM_log('AppendOtherPagesComments - olnode.childNodes' +olnode.childNodes.length);
		// GM_log('numpgsrxd=' + numSubpagesRxdAndParsed);
		for (var i=0; i<subpagesCommentsStorageNode.childNodes.length; ++i) {
			var comsolnode = subpagesCommentsStorageNode.childNodes[i];
			if (comsolnode) {
				// GM_log('comsolnode.innerHTML' + comsolnode.childNodes.length);
				for (var cn=0; cn<comsolnode.childNodes.length; ++cn) {
					// GM_log('appendit');
					olnode.appendChild(comsolnode.childNodes[cn]);
				}
			}
		}
	}

	function PullAndAppendCommentsFrNextPageOfGrp()
	{
		if (lastPageToPull<1) {
			return; // cancelled
		}
		
		GM_log("PullAndAppendCommentsFrNextPageOfGrp");
		if (pullpage>lastPageToPull) {
			GM_log("done pulling pages");
			if (timeoutId) clearTimeout(timeoutId);
			lastPageToPull = 0;
			
			AppendOtherPagesComments();
			DigestOurComments();
			return;
		}

		var sURL = sbase + '/?apage=' + pullpage;
		tbvgmlog("PullCommentsFromURL " + sURL);
		pullpage = pullpage+1;

		GM_xmlhttpRequest({
			method:"GET",
			url: sURL,
			headers: {
					'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey/0.8',
					'Accept': 'application/atom+xml,application/xml,text/xml',
			},
			onload:function(result) {
				if (lastPageToPull<1) return; // cancelled
				
				GM_log('got page');
				
				var s1 =result.responseText.substr( result.responseText.indexOf('<li class="alt" id="comment-'));
				var s2 = ['<ol id="olist4pg">', s1.substr( 0, s1.indexOf('</ol>') ), '</ol>'].join('') ;
				var s3= s2.split("<br />").join("<p></p>").split("<br>").join("<p></p>");
				var commentsText = s3.split("<strong><\/p>\n<p>").join("</p>\n<p><strong>");
				
				// GM_log('pg commentsText=' + commentsText);
				GM_log("parsing...");
				var parser = new DOMParser();

				var respxmlDoc = parser.parseFromString(commentsText,"application/xml");
				GM_log("...parsed.");
				var entries = respxmlDoc.getElementsByTagName('li');
				if (entries.length) {
					var pageOlNode = entries[0].parentNode;
					GM_log('pg olnode #childNodes=' + pageOlNode.childNodes.length);
				}
				for (var i = 0; i < 0 && i< entries.length; i++) {
				            alert(entries[i].getAttribute('id'));
					var pageOlNode = respxmlDoc.getElementById('olist4pg');
					GM_log('pg olnode #childNodes=' + pageOlNode.childNodes.length);
				}

				GM_log('importingand appending...');
				subpagesCommentsStorageNode.appendChild(document.importNode(pageOlNode,true));
				GM_log('done importingand appending.');
				++numSubpagesRxdAndParsed;
				
				PullAndAppendCommentsFrNextPageOfGrp();
			}
		});
	}
	
	function DigestOurComments()
	{
		GM_log("DigestOurComments");
		
		var doccomments =	document.getElementById('comments');
		var olnode  = doccomments.childNodes[5];
		
		GM_log("olnode for allourcomments has #elments:" + olnode.childNodes.length);

		var ucommentdiv = document.getElementById('respond');
		var sOurURL = window.location.href.toString();
	
		var ouranchorIdent = OurURLAnchorIdent(); 	// i.e. '#comment-66113' or ''
		
		InitDataTbls(gbFilterMode);
		ModifyCommentForm();
		
	
		SetTBVStyles();
		AddClientJS();
		
	// entry-response
	
		var citeNodes = xpath("//cite");
		var lastindex = 0;
		
		// GM_log('doccomments commentsText=' +doccomments.innerHTML);
	
		// get #comments (some of which might be gaps?? how is this handled?
		var sofarNode = doccomments.childNodes[1]; // 'of n comments'
		var numcommentsSoFar = parseInt(sofarNode.innerHTML.replace(/.* of\s+/,'').replace(/ .*/,''));
		GM_log( numcommentsSoFar + ' comments so far');
	
	
		AddSomePageNavLinksEtc(false);

		// ------------------------------------
		// First pass, init the Comment tbl with just  indexes & authnames
		// ------------------------------------
		var ncomments = citeNodes.snapshotLength;
		tbvgmlog(ncomments + ' comments');
		for (var iC = 0; iC < ncomments; ++iC) {
			var citeNode = citeNodes.snapshotItem(iC);	// cite (author)
			var ccontentNode = CContentNodeFrCiteNode(citeNode);
	//		citeNode.parentNode.parentNode; 	// div class=comment
			var liNode = ccontentNode.parentNode;			// id="comment-44976"
			var sliid = liNode.getAttribute('id'); // visitor-usable links will use this id
			// tbvgmlog('id='+sliid);
			// tbvgmlognode("citeNode",citeNode);
			// GM_log(citeNode.parentNode.innerHTML);
			var ourCIndex = CommentIndexFrCiteNode(citeNode); // start at 1.
		//	GM_log(ourCIndex);
			var authnamelc = SafeAuthnameFrCiteNode(citeNode).toLowerCase();
			//	tbvgmlog('authnamelc='+authnamelc + '.');
			if (!ourCIndex) {
				tbvgmlog("error- cindex is 0");
			}
			AddCommentToTbl(ourCIndex,sliid,authnamelc)
			sliid='';
		}
		// DumpTables();
		var iauthhots = []; // comments by hot authors
		var irechots = [];	// comments with hot recommends
	
		// ------------------------------------
		// Next pass, populate the comments tbl etc
		// (in reverse order, so recs/ratings for the comment will already be known)
		// ------------------------------------
	
			GM_log("Now traverse comments in reverse order...");
		// tbvgmlog("ClassnameFrAuthor(2)" + ClassnameFrAuthor(2));
		for (var iC = ncomments-1; iC >=0; iC--) {
			var citeNode = citeNodes.snapshotItem(iC);	// cite (author)
			var ccontentNode = CContentNodeFrCiteNode(citeNode); 	
			var liNode = LiNodeFrCContentNode(ccontentNode);			// id="comment-44976"
			var indexNode = IndexNodeFrCiteNode(citeNode);
			var ourCIndex = CommentIndexFrIndexNode(indexNode);
			var cmetaNode = liNode.childNodes[1];
			vtbvgmlog("ourCIndex=" + ourCIndex);
			vtbvgmlog("cMetaNode=" + cmetaNode.nodeName);
	
			var sliid = liNode.getAttribute('id'); // true permalinks will use this id - 'ourCIndex' will change if an earlier comment gets deleted.
		//	vtbvgmlog("sliid=" + sliid);
			var iauth = AuthorFrComment(ourCIndex);
			var authnamelc = AuthnameLC(iauth);
	
			indexNode.setAttribute('id','index' + ourCIndex); // so we can link via cindex to the comment
			
			
			if (AuthorIsHot(iauth)) {
				tbvgmlog("hot");
				iauthhots.push(ourCIndex);
			}
			if (RecsOfThisCommentAreHot(ourCIndex)) {
				tbvgmlog("hot");
				irechots.push(ourCIndex);
			}
			vtbvgmlog("donehots");
			
			var isTrusted = AuthorIsTrusted(iauth);
			var isDistrusted = isTrusted? false : AuthorIsDistrusted(iauth);
			vtbvgmlog("donetruste");
	
			ccontentNode.setAttribute('id','TBVcbody'+ourCIndex); // for expand/contract
	
			var ourTBVHash = []; // this comment's TBV-related info if any, from w/in the squaretags
			var sTBV = TBVStuffFrText(ccontentNode.innerHTML);
			
			if (ourCIndex==993) { tbvgmlog("stbv=" + sTBV); } // debug crap
			
			var ourTBVrecstr ='';
			if (sTBV && sTBV.length>0) {	
					tbvgmlog("sTBV:" + sTBV);
	
				// this is very hackish since I don't know how to neatly 'enclose' it on the fly.
				ourTBVrecstr = TBVIrecStrFrText(sTBV);
	
				if (ourCIndex==993) { tbvgmlog("ourTBVrecstr=" + ourTBVrecstr); }
				var spanstr = AsSpanOfClassAndId('shrinkme', 'tbvinfo'+ ourCIndex, 
					sTBV.replace(/I recommend.*\./,''));
					
				if (ourCIndex==993) {	tbvgmlog("spanstr=" + spanstr);	}
				ourTBVHash = HashFrStr(TBVHashStrFrText(sTBV));
				// ReportHash('#' + ourCIndex + "'s TBVHash", ourTBVHash);
	
				// ReportHash('#' + ourCIndex + "'s TBVHash", ourTBVHash);
				var reccsv = ourTBVHash['r'];
				var unreccsv = ourTBVHash['u'];
	
				if (OKToRec(isTrusted))		{
					DigestRecsAndUnrecs(ourCIndex,reccsv,unreccsv);
				}
				if (ourTBVrecstr.length) {
				 	ourTBVrecstr = [
						'<i>',
						Firstname(authnamelc),
						' recommends ',
						ourTBVrecstr,
						'</i>'
						].join(''); 
					// if (ourCIndex==993) { tbvgmlog("ourTBVrecstr=" + ourTBVrecstr); }
				}
				// ccontentNode.innerHTML = ourTBVrecstr + WithAllTBVStuffRemoved(ccontentNode.innerHTML) + spanstr;
				if (ourCIndex==993) {
					tbvgmlog("ccontentNode.innerHTML=" + ccontentNode.innerHTML);
					tbvgmlog("notbvstuff=" + WithAllTBVStuffRemoved(ccontentNode.innerHTML));
				}
				ccontentNode.innerHTML = WithAllTBVStuffRemoved(ccontentNode.innerHTML) + spanstr;
					vtbvgmlog("done ccontentNode.innerHTML");
			}
					vtbvgmlog("metahash");
			var metahash={}; // stuff that clientjs will need access to
			metahash['linkstr'] = LinkstrForCIndex(ourCIndex); // stash it where clientjs can easily find it
			metahash['sliid'] = sliid; // "comment-44976"
			metahash['auth'] = iauth;
			metahash['authname'] = authnamelc;
	
			var bodyvisible = 1;
			if (gbShrinkDistrusted || OKToShowRecs()) {
				if (isDistrusted || IsUnreccedByNTrustedUsers(ourCIndex,2)) {
					ccontentNode.style.fontSize='2pt';
					bodyvisible = 0;
				}
			}
			metahash['origvis'] = bodyvisible; // to be accessed as TBVMetaVal('origvis')
	
					vtbvgmlog("done metahash");
			var sinnerHTML = ccontentNode.innerHTML;
			// fix "reply to comment" format outliers
			if (authnamelc=='david b. benson') { 
				sinnerHTML =sinnerHTML.replace(/\((\d+)\) /mg,"\#$1 ");
			}
			sinnerHTML = sinnerHTML.replace(/#\s*/mg,'#');
	
			// MarkBlogHostResponses
			if (gsBlogHostname.length) {
				var re = new RegExp('(\\[' + gsBlogHostname + '.*?\\])','mg')
				sinnerHTML=sinnerHTML.replace(re, "<span class='bloghost'>$1</span>");
			}
			ccontentNode.innerHTML = sinnerHTML;
					vtbvgmlog("done sinnerHTML");
	
			var maxrepliesby1comment = 5;
			var ancestralCIndexes = ArrOfMaxSizeFrCSV(pullCSVRepliesFromComment(ccontentNode,authnamelc),maxrepliesby1comment);
			// needed to do this -before- we get linkified
	
			// pull who(s) we're replying to, out of our comment
			AddIndexAsReplierToComments(ourCIndex,ancestralCIndexes);
					vtbvgmlog("done AddIndexAsReplierToComments");
	
			// linkify in-comment comment#s _IFF_ they aren't already linkified:
			var re = /<\/a>/;
			
			// look for e.g #32 plus the closing-tag that follows it, if any
			sinnerHTML = sinnerHTML.replace(/#\d+(?:<\/.*?>)?/mg, function(smatch) { 
				if (re.test(smatch)) {	
					return smatch; // it's followed by </a>, so dont change it
				} else {
					var thecindex = parseInt(smatch.replace(/[^\d]/g,'')); // rm nondigits
					if (thecindex>ourMinCommentIndex && thecindex < ourCIndex) {
						return SimplyLinkified(thecindex)	+ smatch.replace(/#\d+/,''); // the end tag, if any
					} else { return smatch };
				}
			});
			ccontentNode.innerHTML = ourTBVrecstr + sinnerHTML;
					vtbvgmlog("done sinnerHTML2");
			
			 // per-comment buttons
			var xrefNode = CreateNodeOfClassAndId('div','TBVxref',ourCIndex);
	
			var oktoshowrecs = OKToShowRecs();
			var dcNode = CreateAndFillDEControlsNode(ourCIndex,oktoshowrecs, authnamelc);
			
			if (dcNode) {
				xrefNode.appendChild(dcNode);
			}
					vtbvgmlog("done dcNode");
			var bRealName = ourTBVHash['rn']=='y';
			var thNode = CreateAndFillThreadingNode(ourCIndex,bRealName);
			if (thNode) {
				xrefNode.appendChild(thNode);
			}
			// stash this-comment's-info for later use by clientside js
			var tbvmetaNode = CreateAndFillMetaNode('TBVmeta' + ourCIndex,metahash);
			xrefNode.appendChild(tbvmetaNode);
	
					vtbvgmlog("done xrefNode");
	//		var cmetaNode = liNode.metanodedfdfaf('comment-meta');
			// var cmetaNode = CreateNodeOfClassAndId('div','commentmeta',ourCIndex);
			cmetaNode.appendChild(xrefNode);
			
			// hre...
			
	//		for (var inode=3; inode>=0; --inode) { // move 1st 3 comment nodes to under it
	//			cmetaNode.insertBefore(liNode.removeChild(liNode.childNodes[inode]), cmetaNode.firstChild);
	//		}
			if (oktoshowrecs) {	
				var recstatnode =	CreateNodeOfClassAndId('span','TBVrecstatus',ourCIndex);
				recstatnode.innerHTML = '';
				cmetaNode.appendChild(recstatnode);
				var rNode = CreateAndFillRatersNode(ourCIndex);
				if (rNode) {
					cmetaNode.appendChild(rNode);
				}
			}
		 	cmetaNode.appendChild(CreateNodeOfClass('div', 'TBVclearfloats'));
					vtbvgmlog("done cmetaNode");
					vtbvgmlog(ourCIndex);
		//	if (ourCIndex==17) {
					vtbvgmlog("ccontentNode" + ccontentNode.innerHTML);
					vtbvgmlog("cmetaNode" + cmetaNode.innerHTML);
					vtbvgmlog("liNode" + liNode.innerHTML);
	//		}
			liNode.insertBefore(cmetaNode, ccontentNode);
					vtbvgmlog("done cmetaNode2");
					
					
			document.getElementById('TBVbtnupcbody'+ourCIndex).style.display = (bodyvisible)? 'none' : 'block';
					vtbvgmlog("done TBVbtnupcbody");
			document.getElementById('TBVbtndncbody'+ourCIndex).style.display = (bodyvisible)? 'block' :'none';
			
					vtbvgmlog("done TBVbtndncbody");
			HideEndIfLongComment(ccontentNode,ourCIndex);
			vtbvgmlog("done thiscomment");
		}
		
		// stash whole-page-level info for later use by clientside js
		var metahashall={}; // stuff that clientjs will need access to
	
		metahashall['numcomments'] = numcommentsSoFar; 
		var metaNodeAll =		CreateAndFillMetaNode('TBVmetaall',metahashall);
		if (metahashall['numcomments']>0) {
			var showhidenode = CreateNodeOfClass('div', 'showhidebtns'); 
			showhidenode.innerHTML = [
				NewButtonStr('showTBVAuthList','Show who has commented',"showIt('TBVAuthList',true);"),
				NewButtonStr('hideTBVAuthList','Hide',"showIt('TBVAuthList',false);", true)
			].join('');
			var authlistnode = CreateAndFillAuthorlistNode();
			authlistnode.style.display='none';
			showhidenode.appendChild(authlistnode);
			sofarNode.appendChild(showhidenode);
	
			FillInClassOfAllRecLinks(); // ...once we know everything.
			CreateFillAttachDoemallNode(irechots.reverse(),iauthhots.reverse(),doccomments); // likewise - btns to resize everything
		}
		vtbvgmlog("done metaNodeAll");
		sofarNode.parentNode.appendChild(metaNodeAll);
	
		var tbvSettingsNode= CreateAndFillTBVSettingsNode('viewTBVSettings'); // view preferences
		sofarNode.parentNode.insertBefore(tbvSettingsNode,sofarNode);
	
		vtbvgmlog("done sofarNodeAll");
		if (ouranchorIdent.length) {
			// tbvgmlog('at end, jump to ' + ouranchorIdent);
			window.location.href = ouranchorIdent;
		}
		// scriptstatusdiv.style.display = 'none';
		if (doccomments) {
			doccomments.appendChild(CreatePagenavNode());
			doccomments.style.display = 'block';
		}
		ucommentdiv.style.display = 'block';
	//	colsparent.style.display='block';
					tbvgmlog("done loadAll");
	
	} // digestourcomments

	
	
}, false);
	

