// ==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 = '3.1.1' //  for recent version history, see below after 'most useful features'

var gsBlogname = BlognameFrURL(window.location.href.toString());
var gbItsDED = (gsBlogname=='dotearth');

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/';

/* 

-----------------------------------------------------
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 blog archive; about a week of posts
	* 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)
-----------------------------------

v3.1.1: 
	* oops - the initial prompt popup's text was too long.
v3.1.0: 
	* User-friendly archive - 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 smith', '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','bud ward', 'dano', 'david b. benson', 'david goldston','dick purcell', 'hank roberts', 'joe romm', 'john mashey', 'lee schipper', 'mike roddy','steve bloom', 'wayne hamilton'
	];
	
	// others	
var regularNames = [
		'alex washburne', 'anna haynes', 'charles kay','danny bloom', 'david lewis','david lews','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', 'spencer', 'steve bolger', 'steven earl salmony', 'tenney naumer', 'trinifar',  'wang suya', 'chris colose'
	];
	
// 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','greg burton', 'jamesg','jepe','jerry magnan', 'jeff martin', 'julian flood','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','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);
// GM_log(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
// GM_log(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 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;
	}
	// GM_log("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;
	}
	// GM_log('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;
	}
	// GM_log("Done with PopulateAuthnamesByClassTbl");
}
function AddAuthorOfClass(namelc, iclass)
{
	var iauth = authTbl.push(	{ 
			"namelc": namelc,
			"class": iclass,
			"comments": []
				} )
				-1 // empirical
				;
				// GM_log('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
		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);
			// GM_log("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;
	// GM_log('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 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)
{
		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);
	}
	// GM_log("(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)
	//	GM_log(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];
		// GM_log(anode.nodeName);
		return anode.innerHTML.replace(/\./,'');	
	}
	// rm the . of 125.
}
// -----------------
function CIndexFrSlink(slink)
{
	if (slink.match(/href=(.)#(.*?)\1/)) {
			// GM_log('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>0 && 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>0 && 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>0 && 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).replace(/\s+/mg,' ').replace(/.* by */,'');
		return (SanitizedAuthorName(sauth));
}
// ---------
function IndexNodeFrCiteNode(citeNode)
{
		var authNode = citeNode.parentNode;		// p/cite
		var gpNode = authNode.parentNode; 		// div class=comment
		var liNode = gpNode.parentNode;			// id="comment-44976"
		return liNode.childNodes[1];
}
function CommentIndexFrIndexNode(indexNode)
{
	return (!indexNode)? 0 : parseInt( indexNode.innerHTML.replace(/\./,'') );
}
function CommentIndexFrCiteNode(citeNode)
{
	var indexNode = IndexNodeFrCiteNode(citeNode);
	return CommentIndexFrIndexNode(indexNode);
	
}
		
// ---------------------------
// 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)
{
	// GM_log(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; }");

		var otherstylesArr = [
			// original elements
			
			'div#shell div#page div#navigation div#subNavigation ul, div#shell div#page div#navigation ul.tabs li.selected ul {	width:1200px;		} ',
			'#blog_container {width:1200px; } ',	
			'#blog_wrapper {width:1200px; background-image:url();} ',	
			'#blog_content { float:left; width:60%; margin:10px;} ',
			'#blog_comments a {  color: #004276; text-decoration: underline !important;}',			
			'#blog_comments .commentmetadata { margin:2px; float:left;}',
			'#blog_comments .index { font-size:120%; font-weight:bold; padding:4px; margin:8px; }',
			'#blog_comments cite { display:none; }',
			'#add_comments { border:2px solid orange} ',			
			'#commentform #comment{	width:70%; margin-top: 30px; margin-left:-140px !important; }', 
			'#commentform label span { width:140px; } ',
			'#blog_comments .comment { float:right; width:90%; } ',

			// script-added elements
		// ineffectual	'#TBVarchives { background:#EEF !important;padding:3px; margin:5px;  } ',
		//	'#tbvpagebrand { background:#CFC; opacity:.40; font-weight:italic; margin-top:20px; margin-bottom:6px; }', // top is for krugman spacing
		
			'#blog_comments div.TBVxref {width:80%; float:left;}',
			'#blog_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:500px; padding:3px;margin:3px; clear:both; } ',
			'#blog_comments div.morelessbtns { padding:3px; margin:5px; float:right; } ',

			'#blog_comments .TBVAuthList { font-size:90%; opacity:.60;  }',
			'#blog_comments .bloghost { background:#FFD700; }',
			'#blog_comments .arealname { font-weight:bold; color:green; }',
			'#blog_comments .labeling { opacity:.60; }',
			
			
			
			'#blog_comments .viewTBVSettings {  font-size:90%;  font-weight:normal;}',
			'div.TBVSettingsdata {  }',
			
			'#blog_comments .TBVDoemallBtns { float:right; opacity:.70;}',
			'#blog_comments .TBVsmallbutton {  height:20px; opacity:.80;}',
			'#blog_comments .TBVreplyingto { white-space: nowrap;}',
			'#blog_comments .TBVCommentBtns { float:left; width:100%; height:5px; opacity:.80; padding:2px; font-size:60%; display:block;white-space: nowrap; margin-bottom:15px; }', 
//			'#blog_comments div.floatleft { float:left;}',
			'#blog_comments div.TBVcbodyresizebtns { float:left;}',
			'#blog_comments div.TBVdorecbtns { float:left; }',
			'#blog_comments .TBVclearfloats { clear:both; opacity:.0;}',
			'#blog_comments .TBVToAnchors { font-size:130%;}',
			
			'#blog_comments .TBVrecstatus { display:block; float:left; padding:3px; margin-left,margin-right:5px; opacity:1.0;}',
			'#blog_comments div.TBVthreading {	clear:left; background: #EAEAEA; white-space: nowrap; }',
			'#blog_comments .TBVciteB { border: 2px solid #CCC; padding: 1px; font-size: 120%; font-weight:bold; white-space:nowrap; }',
			'#blog_comments .TBVbegaticon { font-family: monospace; padding: 0px; margin: 2px; }',
			'#blog_comments .TBVraters { margin-bottom: 5px; float:left; }',
			'#blog_comments .TBVrecicon { border: 2px solid #0D0; color: #0D0; background: #FFF; font-size: 120%; font-weight: bold; padding: 1px; margin: 2px; }',
			'#blog_comments .TBVunrecicon { border: 2px solid #999; color: #000; background: #EEE; font-size: 120%; font-weight: bold; padding: 1px; margin: 2px; }',
			'#blog_comments .TBVnames {	font-size:80%;	opacity:.60;}',
			'#blog_comments .shrinkme {	font-size:60%;	opacity:.60;}',
			
			'#add_comments .sidenode {	margin-top: 30px; margin-left:5px; width:130px; float:left; padding:3px; }',
			
			'#add_comments #TBVbtnpreview{ margin-top:10px; float:left;}',
			'#add_comments #submit { float:right; margin-left:180px; margin-right:100px; margin-bottom:0px; }',
			
			'#add_comments .returntos { min-height:130px; border:1px dashed orange; padding:3px; }',
			'#blog_comments .recsby {	opacity:.60;}'
			];
	 addGlobalStyle(otherstylesArr.join("\n"));
}
// -----------------------------------------
function addGlobalJS(sscript) {

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

		// GM_log('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) + "\"; ",

"		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='#\" + 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 = ' excerpt of comment here '; }",
"				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=\\'#', 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 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) {					GM_log("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 GM_lognode(s,anode)
{
	GM_log(s + anode.nodeName + anode.className);
}

// --------------------------------------------
function AddSomePageNavLinksEtc(bPageWillHaveArchives) // mostly green-highlighted stuff
{
	var nodeToModify = document.getElementById('frontpage_link');
	var parNode = document.getElementById('blog_content');
	
	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 shomeHref=  nodeToModify.firstChild.getAttribute('href');
	
		var sarchlink =  [ 
				" . . . or (in new window) to ",
				"<a href='",
					shomeHref,
					"?TBVarchives=1",
					"' target='",
					gsBlogname, 
					"  front Page with Archives",
					"' onclick='openRequestedPopup(this.href, this.target); return false;'" ,
					">front page, with Archives</a> (slow) "
					].join('');
		nodeToModify.innerHTML += [ 
			sarchlink,
			"<br/>or to ",
			gsBlogname,
			' blog ',
			LinkToAnchorStr('sidebar','side_search'),
			" or ",
			LinkToAnchorStr('COMMENTS','blog_comments')
			].join('');
//		GM_log(nodeToModify.innerHTML);

		var sidebarnode = document.getElementById('side_search');
		sidebarnode.innerHTML += "<br/>" + LinkToAnchorStr('to page start','frontpage_link');
	}

	// 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);
		
	parNode.insertBefore(tbvbranddiv, parNode.childNodes[2]); // but after child 0,1
}	

// --------------------------------------------
function ModifyCommentForm()	
{
	var formnode = document.getElementById('commentform');
	var sformOnsubmitAction = formnode.getAttribute("onsubmit"); // get this asap before we muck with it - "return CommentsValidator.validateForm()"
	
	// this will be the new Submit action
	var sTBVHandleSubmit = [
		"		function TBVHandleSubmit() {		",
		"			AppendTBVInfoToComment();		",
//		"			alert('Submitting...'); ",
					sformOnsubmitAction + ";" ,
		"		}		"
		].join("\n");
	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
		GM_log("diffnames -" + TBVSettings['myname'] + "vs" + authfield.value);
	}
	var schecked = brealname? 'checked' : 'unchecked';
	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)


	var newCommSideNode = CreateNodeOfClassAndId('div','sidenode','');
	var backlinksNode = CreateNodeOfClassAndId('div','returntos','');
	newCommSideNode.appendChild(backlinksNode);
	var sTBVbtnpreview = NewButtonStr('TBVbtnpreview','Preview (below)', 'PreviewComment()');
	newCommSideNode.appendChild(CreateSpanContaining(sTBVbtnpreview));

	formnode.insertBefore(CreateNodeOfClass('div', 'TBVclearfloats'),commentfor);
	formnode.insertBefore(newCommSideNode,commentfor);

	var button4Submit = document.getElementById('submit');
	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");
	var previewNode = CreateSpanContaining(spreviewpane);
	formnode.insertBefore(previewNode,button4Submit);

	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);
	}

	button4Submit.value = button4Submit.value.replace(/ubmit C/,'UBMIT c') + ' (but copy and save it somewhere safe first!)';

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

// -----------------------------------------------------
// generating html
// ---------------------
function AsNamesSpan(s) {
	if (!s) { GM_log('asnamesspan s null '); }
	var ret= "<span class='TBVnames'>" + s + "</span>"
	// GM_log(ret);
	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('');
		// GM_log(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('');
	// GM_log(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('');
	// GM_log(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='#",
//		"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=='')
	{
		GM_log('no authorlc for #' + cindex );
		return 'unknown';
	}

	return	["<a href='#",
	//		"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='#" + 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)
{
	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 parNode = document.getElementById('comments');
	 parNode.appendChild(doemallNode);
	 parNode.appendChild(CreateNodeOfClass('div', 'TBVclearfloats'));
}
function LinkToAnchorStr(slabel, sid)
{
	if (slabel.length && sid.length)
		return "<a href='#" + sid + "'>" + slabel + "</a>";
	else return '';
}
function CreatePagenavNode()
{
	var jumpnode = CreateNodeOfClass('div', 'TBVToAnchors');
	jumpnode.innerHTML = [
		"Jump to...", 
		LinkToAnchorStr('pagetop', 'blog_content'), 
		LinkToAnchorStr('commentstop', 'blog_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 + "');";
	// GM_log(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('');
	// GM_log(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
{
		var mNode = CreateNodeOfClassAndId("span", "TBVAuthList",'');
		authlinksArr = [];
		for (var iauth in authTbl) {
			var carr = NumSortArr(authTbl[iauth].comments);
			// if (iauth==28) GM_log(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(' '); 
//		GM_log(mNode.innerHTML);
		return mNode;
}
// -----------------------------------------------------
// in development, alas.   Can't get the button over to the right!
// ----------------
function HideEndIfLongComment(cNode,ourCIndex)
{
	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 = [ "... &nbsp; ",
		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 InsertPostLinks(spagetext0) // this is ugly as sin; scrapes a page like 2008/06/02
{
	var arr=[];

	try {
		var re = /<h2 class=.post-title2.>.*?<\/h2>/g;
		var spagetext = spagetext0.replace(/\\n/mg,' '); // just in case newlines are a problem
		
		// links to posts
		var matches = spagetext.match(re);
		var n = matches.length;

		// #comments
		var cre = /<li class=.comments-tool..*?<.li>/g;
		var cmatches = spagetext.match(cre);
		var bAppendNumComments = (n==cmatches.length); // else we're dazed and confused and not a reliable source

		var sdate = 'unk';
		for (var i = 0; i < n; ++i) {
			// GM_log('match:' + matches[i]);
			var slink = matches[i].replace(/<h2 class=.post-title2.>/,'').replace(/<\/h2>/,'');
			
			if (i==0) {
				sdate =	slink.replace(/.*20\d\d\/(\d\d\/\d\d)\/.*/,"$1"); // yielding something like 07/31
			}
			if (sdate.length!=5) { // oops
				GM_log("bad date - " + sdate + " - in InsertPostLinks");
				return;
			}
			var snumcomments = '';
			if (bAppendNumComments) {
				var snumtext = cmatches[i].replace(/.*\((.*?)\).*/, "$1"); // 46
				if (snumtext.length>6) { // 'Add a comment'
					snumtext = 0;
				}
				snumcomments = '(<b>' + snumtext + '</b>)'; 
			}
			arr.push( sdate  + ' ' + slink + ' ' + snumcomments);
		}
		// GM_log(sdate);
		document.getElementById(sdate).innerHTML = arr.join('<br/>');
	}
	catch(e) { GM_log('Parse error: ' + e.message); }
}
function MinOf(a,b)	
{
	return (a<b)? a : b;
}
function FillArchivesNode(sOurURL,archNode)
{
	archNode.appendChild(document.createTextNode(ReCapped(gsBlogname) + " Blog Archives"));
	var caltables = xpath("//table[starts-with(@id, 'cal_20')]"); // cal_200804
	var ndaysdone = 0;
	var maxdaysdone = MinOf(7,gsMaxArchiveDays);

	for (var mo=0; mo<3  && mo<caltables.snapshotLength; ++mo) {
		var id = caltables.snapshotItem(mo).getAttribute("id"); 
		var s = "//table[@id='" + id + "']//a";

		var caldatelinks = xpath(s);
		var ndateswithposts = caldatelinks.snapshotLength;
		// GM_log(n + ' caldatelinks');
		var shref0 = sOurURL.replace(/\/$/,''); // rm final slash
		var shref1s=[];
		for (var j = 0; j < ndateswithposts && ndaysdone<maxdaysdone; ++j, ++ndaysdone) {
			var i = ndateswithposts-j-1; // go backwards
			
			var datelink = caldatelinks.snapshotItem(i);
			var shref1 = datelink.getAttribute("href");

			if (!shref1.length)
				continue;

			var dateNode = CreateNodeOfClass('div','links4date');
			var sdate = shref1.replace(/.*20\d\d.(\d\d\/\d\d).*/,"$1"); // 07/31
			dateNode.setAttribute('id', sdate);
			archNode.appendChild(dateNode);

			// GM_log('get ' + shref0 + shref1 + dateNode.innerHTML);
			GM_xmlhttpRequest({
				method:"GET",
				url: shref0 + shref1,
				onload:function(result) {
					InsertPostLinks(result.responseText); // it can figure out which datenode, from the url
				}
			});
			
		}
		if (ndaysdone>=maxdaysdone)
			break;
	}
}
function AsList(sarr)
{
	return (!sarr.length)? '' :  "<ul><li>" + sarr.join('</li><li>') + "</li></ul>";
}
function AddArchivesToSidebar(sOurURL,num)
{
	var archivesNode = CreateNodeOfClassAndId('div','TBVarchives','');
	var sidediv = document.getElementById('blog_sidebar');
	sidediv.insertBefore(archivesNode, sidediv.firstChild);
	FillArchivesNode(sOurURL,archivesNode); // here a miracle occurs
	var sstyle = 'background:#CFC; opacity:.60;';
	archivesNode.setAttribute('style',sstyle);

}
function WeAreTheFrontPage(sURL)
{
	var stmp = sURL.replace(/.*blogs.nytimes.com\//,'').replace(/[\#\?].*/,''); // anything left?
	return (trimmed(stmp).length==0);
}
// -------------------------------------------------------------------------------------
//	Once the page has loaded, we can start here...
// --------------------------------------------------
window.addEventListener("load", function(e) {
	var commentsdiv = document.getElementById('add_comments');
	var sOurURL = window.location.href.toString();
	var bRunOnThisPage = false;
	if (!commentsdiv) {
		if (WeAreTheFrontPage(sOurURL)) {
				var bAssembleArchive = sOurURL.match(/\?TBVarchives=(\d*)/);
				if (bAssembleArchive) {
					var num = RegExp.$1;
					if (num>0) {
						GM_log("'" + sOurURL + "' is front page - add archives");
						bRunOnThisPage = true;
						AddSomePageNavLinksEtc(true);
						AddArchivesToSidebar(sOurURL.replace(/\?.*/,''),num); // rm the 'options' crap from our url
					}
				}
		}
		if (!bRunOnThisPage) {
			GM_log("'" + sOurURL + "' not a page for " + gsScriptname + " (it has no comments section and archives werent requested)");
		}
		return;
	}
 	GM_log(gsScriptname + " is running, on " +  sOurURL);
	
	var ouranchorIdent = OurURLAnchorIdent(); 	// i.e. '#comment-66113' or ''
	
	InitDataTbls(gbFilterMode);
	
	//  likely it's faster to modify it, if we don't have to update the display as we go along:
	commentsdiv.style.display = 'none';

	SetTBVStyles();
	AddClientJS();
	
	ModifyCommentForm();
	AddSomePageNavLinksEtc(false);

	var citeNodes = xpath("//cite");
	var lastindex = 0;

	// get #comments (some of which might be gaps?? how is this handled?
	var sofarNode = document.getElementById('comments'); // 'n comments so far'
	var numcommentsSoFar = parseInt(sofarNode.innerHTML.replace(/\s.*/,''));

	// ------------------------------------
	// First pass, init the Comment tbl with just  indexes & authnames
	// ------------------------------------
	var ncomments = citeNodes.snapshotLength;
	for (var iC = 0; iC < ncomments; ++iC) {
		var citeNode = citeNodes.snapshotItem(iC);	// cite (author)
		var gpNode = citeNode.parentNode.parentNode; 	// div class=comment
		var liNode = gpNode.parentNode;			// id="comment-44976"
		var sliid = liNode.getAttribute('id'); // visitor-usable links will use this id

		var ourCIndex = CommentIndexFrCiteNode(citeNode); // start at 1.
		var authnamelc = SafeAuthnameFrCiteNode(citeNode).toLowerCase();
		if (!ourCIndex) {
			GM_log("error- cindex is 0");
		}
		AddCommentToTbl(ourCIndex,sliid,authnamelc)
	}

	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)
	// ------------------------------------

	for (var iC = ncomments-1; iC >=0; iC--) {
		var citeNode = citeNodes.snapshotItem(iC);	// cite (author)
		var gpNode = citeNode.parentNode.parentNode; 	// div class=comment
		var liNode = gpNode.parentNode;			// id="comment-44976"
		var indexNode = IndexNodeFrCiteNode(citeNode);
		var ourCIndex = CommentIndexFrIndexNode(indexNode);

		var sliid = liNode.getAttribute('id'); // true permalinks will use this id - 'ourCIndex' will change if an earlier comment gets deleted.
		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)) {
			iauthhots.push(ourCIndex);
		}
		if (RecsOfThisCommentAreHot(ourCIndex)) {
			irechots.push(ourCIndex);
		}
		
		var isTrusted = AuthorIsTrusted(iauth);
		var isDistrusted = isTrusted? false : AuthorIsDistrusted(iauth);

		gpNode.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(gpNode.innerHTML);
		
		if (ourCIndex==993) { GM_log("stbv=" + sTBV); } // debug crap
		
		var ourTBVrecstr ='';
		if (sTBV && sTBV.length>0) {		
			// this is very hackish since I don't know how to neatly 'enclose' it on the fly.
			ourTBVrecstr = TBVIrecStrFrText(sTBV);

			if (ourCIndex==993) { GM_log("ourTBVrecstr=" + ourTBVrecstr); }
			var spanstr = AsSpanOfClassAndId('shrinkme', 'tbvinfo'+ ourCIndex, 
				sTBV.replace(/I recommend.*\./,''));
				
			if (ourCIndex==993) {	GM_log("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) { GM_log("ourTBVrecstr=" + ourTBVrecstr); }
			}
			// gpNode.innerHTML = ourTBVrecstr + WithAllTBVStuffRemoved(gpNode.innerHTML) + spanstr;
			if (ourCIndex==993) {
				GM_log("gpNode.innerHTML=" + gpNode.innerHTML);
				GM_log("notbvstuff=" + WithAllTBVStuffRemoved(gpNode.innerHTML));
			}
			gpNode.innerHTML = WithAllTBVStuffRemoved(gpNode.innerHTML) + spanstr;
		}
		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)) {
				gpNode.style.fontSize='2pt';
				bodyvisible = 0;
			}
		}
		metahash['origvis'] = bodyvisible; // to be accessed as TBVMetaVal('origvis')

		var sinnerHTML = gpNode.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>");
		}
		gpNode.innerHTML = sinnerHTML;

		var maxrepliesby1comment = 5;
		var ancestralCIndexes = ArrOfMaxSizeFrCSV(pullCSVRepliesFromComment(gpNode,authnamelc),maxrepliesby1comment);
		// needed to do this -before- we get linkified

		// pull who(s) we're replying to, out of our comment
		AddIndexAsReplierToComments(ourCIndex,ancestralCIndexes);

		// 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,''));
				if (thecindex>0 && thecindex < ourCIndex) {
					return SimplyLinkified(thecindex)	+ smatch.replace(/#\d+/,''); // the end tag, if any
				} else { return smatch };
			}
		});
		gpNode.innerHTML = ourTBVrecstr + sinnerHTML;
		
		 // per-comment buttons
		var xrefNode = CreateNodeOfClassAndId('div','TBVxref',ourCIndex);

		var oktoshowrecs = OKToShowRecs();
		var dcNode = CreateAndFillDEControlsNode(ourCIndex,oktoshowrecs, authnamelc);
		
		if (dcNode) {
			xrefNode.appendChild(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 metaNode = CreateAndFillMetaNode('TBVmeta' + ourCIndex,metahash);
		xrefNode.appendChild(metaNode);

		var cmetaNode = CreateNodeOfClassAndId('div','commentmeta',ourCIndex);
		cmetaNode.appendChild(xrefNode);
		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'));
		
		liNode.insertBefore(cmetaNode, gpNode);
		document.getElementById('TBVbtnupcbody'+ourCIndex).style.display = (bodyvisible)? 'none' : 'block';
		document.getElementById('TBVbtndncbody'+ourCIndex).style.display = (bodyvisible)? 'block' :'none';
		
		HideEndIfLongComment(gpNode,ourCIndex);
	}
	
	// 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()); // likewise - btns to resize everything
	}
	sofarNode.parentNode.appendChild(metaNodeAll);

 var tbvSettingsNode= CreateAndFillTBVSettingsNode('viewTBVSettings'); // view preferences
 sofarNode.parentNode.insertBefore(tbvSettingsNode,sofarNode);

	if (ouranchorIdent.length) {
		// GM_log('at end, jump to ' + ouranchorIdent);
		window.location.href = ouranchorIdent;
	}
	// scriptstatusdiv.style.display = 'none';
	commentsdiv.style.display = 'block';

}, false);
// -------------------------------------------------------------------

