// ThoughtMesh.js by Jon Ippolito.
// Requires ExpandingMenu.js, telamon.js
// Version 3.9, modified by Jon Ippolito to enable fading of tags corresponding to valid outside lexias.
// Telamon and search-as-you-type currently works in FF and IE, not Safari.
/************************* Create ThoughtMesh.*************************/
// UPGRADE: make globals properties of ThoughtMesh object.
thoughtMesh = new Object() ;
thoughtMesh.defaultOutsideLexiasScript = "http://vectors.usc.edu/thoughtmesh/json/outsideLexias.json.php" ; // Eventually can be localized for different meshes. 
function loadTagMesh( argObj ) 
{ // argObj looks like { outsideLexiasUrl: "http://mysite.com/outsideLexias.json.php" , documentId : 5, method: "standalone"/"telamon"/"custom", autogenerateInlineNavigation: true/false }
	debug("'Start loadTagMesh'")
	/*________________________ Check browser compatibility.________________________*/
	// Check for browser compliance. If stale browser, go to old browser page. (Safari works for the most part, as is explained in the stale browser page.)
	if ( browsers.ns4 || browsers.ie4 || browsers.ie5 || browsers.ie6) { // Explicit numbers is more future-proof.
		if ( !confirm("Your browser doesn't appear to support all ThoughtMesh features.\n\nClick Cancel to learn more, or\n\nOK to proceed anyway.") ) {
			document.location="recommended_thoughtmesh_browsers.html";
		}
	}
	// Warn Safari browsers about search-as-you-type. Lexia warning appears elsewhere, because it's dynamically rewritten when tags are chosen.
	if ( browsers.saf) {
		var browserWarning = "<span class='warning'>" ;
		browserWarning += "Your browser does not appear to support this feature. " ;
		browserWarning += "<a href='thoughtmesh/recommended_thoughtmesh_browsers.html'>Learn more.</a>" ;	
		document.getElementById("search-here").innerHTML = browserWarning ;
	}
	/*________________________ Decide which ThoughtMesh method to implement.________________________*/
	thoughtMesh.method = (typeof argObj.method == "undefined")? "telamon" : argObj.method ;
	if (argObj.method == "custom") {
		// Shouldn't have to do anything, because this option means the author will hard-code a custom json file.
	}
	if (argObj.method == "standalone") {
		// Prevent JSON errors even though there are no outside lexias for this page.
		document.getElementById("tab-out").style.display = "none" ;
		document.getElementById("tab-trace").style.display = "none" ;
	}
	else { // Telamon.
		// If stale browser, go to old browser page. (Safari works for non-Telamon methods, as is explained in the stale browser page.)
		if (browsers.saf) { // Explicit numbers is more future-proof.
			if ( !confirm("Your browser doesn't appear to support all ThoughtMesh features.\n\nClick Cancel to learn more, or\n\nOK to proceed anyway.") ) {
				document.location="recommended_thoughtmesh_browsers.html";
			}
		}
		// Load telamon parameters.
		thoughtMesh.id = ( typeof argObj.documentId != "undefined" )? argObj.documentId :  "[No document id specified!]" ; 
		thoughtMesh.outsideLexiasUrl = ( typeof argObj.outsideLexiasUrl != "undefined" )? argObj.outsideLexiasUrl : thoughtMesh.defaultOutsideLexiasScript ; 
		thoughtMesh.outsideLexiasUrl = encodeURI(thoughtMesh.outsideLexiasUrl) ; // NEC? JB says no.
		debugLoadMessage = 'scripts have been loaded via telamon.include!' ;
		telamon.include( [thoughtMesh.outsideLexiasUrl], "debug('debugLoadMessage')" ) ;
	}
	/*________________________ Prepare to auto-generate tag and "next" links in content if requested.________________________*/
	thoughtMesh.autogenerateInlineNavigation = (typeof argObj.autogenerateInlineNavigation == "undefined")? false : argObj.autogenerateInlineNavigation ;
	// Actual generation takes place in registerTags() so to make use of predefined tag arrays.
	/*________________________ Initialize tags.________________________*/
	debug("'call registerTags() from loadTagMesh'")
	registerTags();
	/*________________________ Initialize tabs.________________________*/
	respondToTab( document.getElementById("tab-here") );
	debug("'End loadTagMesh.'")
}
function setTagSize( argObj ) {
	// Don't use cloudTagObjs.length, because you want the final one, not the length at the time each tag is added. Use thoughtMesh.uniqueTags.length instead.
	// Jon used square root to give small numbers a fighting chance. In ems.
	// It doesn't matter how many there are, it matters how big the max is.
	// Goal is for the maximum size to be 4em, minimum .8em. So I want a square root function with f(argObj.maximum) = 4 and f(argObj.minimum) = .8.
	var tagSize = 4 * (   Math.sqrt( .5 * (argObj.frequency/argObj.maximum) )   );
	if (tagSize < 1 ) tagSize = 1 ;
	return tagSize ;
}
function sortTagsNumerically(a,b) {
	return thoughtMesh.itemsFromTag[ a ].length - thoughtMesh.itemsFromTag[ b ].length ;
}
String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}
function sortAlphabeticallyWithoutCase(a,b) {
	try {
		a = a.toLowerCase(); b = b.toLowerCase();
		if (a>b) return 1 ;
		if (a <b) return -1 ;
		return 0 ;
	}
	catch (e){ // This helps IE handle blanks.
		return 0 ;
	}
}
cloudTagObjs = new Array() ;
function CloudTag(argObj) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? document.body : argObj.containerEle ;
	this.tag = (typeof argObj.tag == "undefined")? "[no tag defined!]" : argObj.tag ;
	this.frequency = (typeof argObj.frequency == "undefined")? "[no frequency defined!]" : argObj.frequency ; // Not needed immediately, but could be useful later.
	this.size = (typeof argObj.size == "undefined")? "[no size defined!]" : argObj.size ;
	// Note: textNodes are weird, don't seem to afford same handles as elements. Hence the element repetition instead of merely replacing textNode + with -.
	/* _______ Create a box for holding +,-, and tag. _______*/
	this.tagBox = document.createElement("span") ;
	this.tagBox.className = "tag-box" ;
	this.containerEle.appendChild( this.tagBox ) ;
	/* _______ Create a blank space-holder for plus and minus links. _______*/
	this.blankLink = document.createElement("span") ;
	this.blankLink.className = "blank-link" ;
	this.blankLink.innerHTML = "+" ;
	this.blankLink.style.fontSize = this.size + "em" ;
	this.tagBox.appendChild( this.blankLink ) ;
	/* _______ Create a link for adding a tag. _______*/
	this.plusLink = document.createElement("a") ;
	this.plusLink.href = "#" ;
	this.plusLink.style.fontSize = this.size + "em" ;
	this.plusLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "plus"}) } ;
	this.plusLink.className = "cloud-tag-hidden" ;
	this.plusText =  document.createTextNode("+") ;
	// Add a means of recovering the corresponding tag from the link, for use in the onclick function.
	this.plusLink.cloudTagObj = this ;
	// Add plus to cloud.
	this.tagBox.appendChild( this.plusLink ) ;
	this.plusLink.appendChild( this.plusText ) ;
	/* _______ Create a link for subtracting a tag. _______*/
	this.minusLink = document.createElement("a") ;
	this.minusLink.href = "#" ;
	this.minusLink.style.fontSize = this.size + "em" ;
	this.minusLink.className = "cloud-tag-hidden" ; // Initially hidden.
	this.minusLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "minus"}) } ;
	this.minusText = document.createTextNode("-") ;
	// Add a means of recovering the corresponding tag from the link, for use in the onclick function.
	this.minusLink.cloudTagObj = this ;
	// Add minus to cloud.
	this.tagBox.appendChild( this.minusLink ) ;
	this.minusLink.appendChild( this.minusText ) ;
	/* _______ Create a link for replacing a tag. _______*/
	this.replaceLink = document.createElement("a") ;
	this.replaceLink.href = "#" ;
	this.replaceLink.style.fontSize = this.size + "em" ;
	this.replaceLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "replace"}) } ;
	this.replaceLink.className = "cloud-tag" ;
	this.replaceText =  document.createTextNode( this.tag ) ;
	// Enable recovery of cloudTagObj from its link, for use in the onclick function.
	this.replaceLink.cloudTagObj = this ;
	// Add replace link to cloud.
	this.tagBox.appendChild(this.replaceLink) ;
	this.replaceLink.appendChild( this.replaceText ) ;
	/* _______ Add a space to separate tags. _______*/
	this.containerEle.appendChild(document.createTextNode(" ")) ;
}
function registerTags() {
	debug("'register tags'")
	// Add tag properties to dynamic menu.
	/* The tag manager should look like this:
		thoughtMesh.tagsFromItem["print-gift"]: an array of tags associated with that menuItem.
		thoughtMesh.itemsFromTag["art"].menuItems: an array of menuItems tagged with "art".
		thoughtMesh.itemsFromTag["art"].menuItems.length: the number of menuItems tagged with "art".
	Don't make it a property of the menu, so that in the future (?) you may operate it from multiple menus.
	*/
	thoughtMesh.selectedTags = new Array() ; // Used after clicking on a tag, this is a manager of objects with integer index and tag name properties.
	thoughtMesh.tagsFromItem = new Object() ; // This cannot be accessed as an array.
	thoughtMesh.itemsFromTag = new Object() ; // Ditto.
	thoughtMesh.uniqueTags = new Array() ;
	ut = thoughtMesh.uniqueTags; debug("ut")
	// MENU LOOP. Loop through all items in menu.
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		// Check whether this item has any tags. Introduction, notes, or superlinks may not.
		if ( homeMenu.itemManager[itemCounter].htmlElement.getAttribute("tags") != null) {
			thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ] = homeMenu.itemManager[itemCounter].htmlElement.getAttribute("tags").split(",") ;
			// ITEM LOOP. Loop through all tags for that item.
			for (var newTagCounter=0; newTagCounter < thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ].length; newTagCounter++) {
				var thisTag = thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ][newTagCounter].trim() ; // Tag as string, with whitespace removed.
				thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ][newTagCounter] = thisTag ; // Add trimmed tag back into tagsFromItem array for future use.
				if ( typeof thoughtMesh.itemsFromTag[ thisTag ] == "undefined") { // This tag has not yet been registered.
					thoughtMesh.itemsFromTag[ thisTag ] = new Array() ;
					thoughtMesh.uniqueTags.push( thisTag ) ;
				}
				thoughtMesh.itemsFromTag[ thisTag ].push( homeMenu.itemManager[itemCounter] ) ;
				// DEPRECATED: thoughtMesh.tagOccurrences.push( thoughtMesh.itemsFromTag[ thisTag ].length ) ;
			}; // End loop through tags for that item.
			// Add navigation to top and bottom of content div if requested.
			if ( thoughtMesh.autogenerateInlineNavigation ) {
				addInlineNavigation({ menuItem: homeMenu.itemManager[itemCounter] , hasTags: true }) ;
			}
		} // End check for tag existence.
		else {
			 // If there are no tags, give it a default (empty) array; it will report length = 0.
			thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ] = new Array() ;
			// Add navigation to top and bottom of content div if requested.
			if ( thoughtMesh.autogenerateInlineNavigation ) {
				addInlineNavigation({ menuItem: homeMenu.itemManager[itemCounter] , hasTags: false }) ;
			}
		}
	}; // End loop through items.
	// First sort unique tags by frequency to get max and min values for normalizing tag size.
	thoughtMesh.uniqueTags.sort( sortTagsNumerically ) ;
	thoughtMesh.tagFrequencyMin = thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[0] ].length ;
	thoughtMesh.tagFrequencyMax = thoughtMesh.itemsFromTag[  thoughtMesh.uniqueTags[ thoughtMesh.uniqueTags.length-1 ]  ].length ;
	// Now sort unique tags alphabetically (default).
	thoughtMesh.uniqueTags.sort( sortAlphabeticallyWithoutCase ) ;
	// Now add tags to cloud.
	len = thoughtMesh.uniqueTags.length; debug("len", 6)
	for (var tagCounter=0; tagCounter < thoughtMesh.uniqueTags.length; tagCounter++) {
		var thisTagFrequency = thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[tagCounter] ].length ;
		var thisCloudTag = new CloudTag({
			containerEle: document.getElementById("home-cloud"),
			tag: thoughtMesh.uniqueTags[tagCounter],
			frequency: thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[tagCounter] ].length,
			size: setTagSize( {
				frequency:thisTagFrequency, 
				minimum:thoughtMesh.tagFrequencyMin, 
				maximum:thoughtMesh.tagFrequencyMax
			})
		}) ;
		cloudTagObjs.push( thisCloudTag ) ;
		// Enable recovery of cloudTagObj from its name, for use in style highlighting--eg, cloudTagObjs["technology"].
		cloudTagObjs[ thoughtMesh.uniqueTags[tagCounter] ] = thisCloudTag ;
	}
}
function addInlineNavigation(argObj) {
	// Auto-generates tag and "next" links in content.
	/*________________________ Determine base url for permalink.________________________*/
	var permalinkURL = location.href.substring( 0, location.href.indexOf("#") ) ;
	permalinkURL += '#' + argObj.menuItem.htmlElement.id ;
	// Add permalink and "next" link top of content div.
	var topNavigationEle = document.createElement("div") ;
	topNavigationEle.paragraph = document.createElement("p") ;
	topNavigationEle.paragraph.className = "next-lexia-top" ; // Note that customization of this class has to happen in the HTML doc because of weird cascading priorities.
	topNavigationEle.appendChild( topNavigationEle.paragraph ) ;

	topNavigationEle.paragraph.permalink = document.createElement("a") ;
	topNavigationEle.paragraph.permalink.href = permalinkURL ;
	topNavigationEle.paragraph.appendChild( topNavigationEle.paragraph.permalink ) ;

	topNavigationEle.paragraph.permalink.linkText = document.createTextNode("Permalink") ;
	topNavigationEle.paragraph.permalink.appendChild( topNavigationEle.paragraph.permalink.linkText ) ;

	topNavigationEle.paragraph.extraText = document.createTextNode(" for this section. . . . ") ;
	topNavigationEle.paragraph.appendChild( topNavigationEle.paragraph.extraText ) ;

	topNavigationEle.paragraph.nextLink = document.createElement("a") ;
	topNavigationEle.paragraph.nextLink.href = "#" ;
	topNavigationEle.paragraph.nextLink.onclick = function() { nextContent( {menuContentEle: this} ) } ;
	topNavigationEle.paragraph.appendChild( topNavigationEle.paragraph.nextLink ) ;

	topNavigationEle.paragraph.nextLink.linkText = document.createTextNode("Next >") ;
	topNavigationEle.paragraph.nextLink.appendChild( topNavigationEle.paragraph.nextLink.linkText ) ;

	argObj.menuItem.content.insertBefore( topNavigationEle, argObj.menuItem.content.firstChild ) ;
	/*________________________ Add tag and "next" links to bottom of content div.________________________*/
	if ( argObj.hasTags ) {
		var bottomNavigationEle = document.createElement("div") ;
		bottomNavigationEle.className = "next-lexia" ; // Note that customization of this class has to happen in the HTML doc because of weird cascading priorities.
		
		bottomNavigationEle.tagParagraph = document.createElement("p") ;
		bottomNavigationEle.appendChild( bottomNavigationEle.tagParagraph ) ;
		
		bottomNavigationEle.tagParagraph.initialText = document.createTextNode("Tags: ") ;
		bottomNavigationEle.tagParagraph.appendChild( bottomNavigationEle.tagParagraph.initialText ) ;
		// Add tag links.
		// thoughtMesh.tagsFromItem["print-gift"] is an array of tags associated with that menuItem.
		bottomNavigationEle.tagParagraph.tagLinks = [] ;
		for (var tagCounter=0; tagCounter < thoughtMesh.tagsFromItem[ argObj.menuItem.htmlElement.id ].length; tagCounter++) {
			// DEBUGGING tag = thoughtMesh.tagsFromItem[ argObj.menuItem.htmlElement.id ][tagCounter]; debug("tag")
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter] = document.createElement("a") ;
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter].href = "#" ;
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter].onclick = function() { respondToTagInContent(this) } ;
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter].linkText = document.createTextNode( thoughtMesh.tagsFromItem[ argObj.menuItem.htmlElement.id ][tagCounter] );
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter].appendChild( bottomNavigationEle.tagParagraph.tagLinks[tagCounter].linkText ) ;
			bottomNavigationEle.tagParagraph.tagLinks[tagCounter].inBetweenText = document.createTextNode(", ") ;
			bottomNavigationEle.tagParagraph.appendChild( bottomNavigationEle.tagParagraph.tagLinks[tagCounter] ) ;
			if ( tagCounter != thoughtMesh.tagsFromItem[ argObj.menuItem.htmlElement.id ].length -1 ) {
				// Add comma unless you're at the end of the tag list.
				bottomNavigationEle.tagParagraph.appendChild( bottomNavigationEle.tagParagraph.tagLinks[tagCounter].inBetweenText ) ;
			}
		}
		bottomNavigationEle.tagParagraph.nextLink = document.createElement("a") ;
		bottomNavigationEle.tagParagraph.nextLink.href = "#" ;
		bottomNavigationEle.tagParagraph.nextLink.onclick = function() { nextContent( {menuContentEle: this} ) } ;
		bottomNavigationEle.tagParagraph.appendChild( bottomNavigationEle.tagParagraph.nextLink ) ;

		// Note that there's already a "next" button on top--only tagged sections will be long enough to deserve a bottom one too.
		bottomNavigationEle.nextParagraph = document.createElement("p") ;
		bottomNavigationEle.appendChild( bottomNavigationEle.nextParagraph ) ;
	
		bottomNavigationEle.nextParagraph.nextLink = document.createElement("a") ;
		bottomNavigationEle.nextParagraph.nextLink.href = "#" ;
		bottomNavigationEle.nextParagraph.nextLink.onclick = function() { nextContent( {menuContentEle: this} ) } ;
		bottomNavigationEle.nextParagraph.appendChild( bottomNavigationEle.nextParagraph.nextLink ) ;
	
		bottomNavigationEle.nextParagraph.nextLink.linkText = document.createTextNode("Next >") ;
		bottomNavigationEle.nextParagraph.nextLink.appendChild( bottomNavigationEle.nextParagraph.nextLink.linkText ) ;
			
		argObj.menuItem.content.appendChild( bottomNavigationEle ) ;
	} ;
}
function DocumentCitation(argObj) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? "[no container Element defined]" : argObj.containerEle ;
	this.author = (typeof argObj.author == "undefined")? "[no author defined]" : argObj.author ;
	this.title = (typeof argObj.title == "undefined")? "[no title defined]" : argObj.title ;
	this.authorAndTitle = document.createElement("div") ;
	this.authorAndTitle.className = "author-and-title" ;
	this.authorAndTitle.innerHTML = this.author + ", &quot;" + this.title + "&quot; " ;
	this.containerEle.appendChild( this.authorAndTitle ) ;
}
function LexiaExcerpt( argObj ) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? "[no container Element defined]" : argObj.containerEle ;
	this.headingText =  (typeof argObj.headingText == "undefined")? "[no heading text defined]" : argObj.headingText ;
	this.isOutside = (typeof argObj.isOutside == "undefined" ||  !argObj.isOutside )? false : true ;
	this.itemEle = (typeof argObj.itemEle == "undefined")? "[no item element defined]" : argObj.itemEle ; // A kludge, but it should throw an illuminating error.
	this.lexiaText = (typeof argObj.lexiaText == "undefined")? "[no lexia text defined]" : argObj.lexiaText ;
	// Create the lexia div.
	this.excerptDiv = document.createElement("div") ;
	this.containerEle.appendChild( this.excerptDiv ) ;
	this.excerptDiv.className = "lexia-excerpt" ;
	// Add a means of recovering the corresponding menu item from the div, for use in the onclick function.
	this.excerptDiv.lexiaObj = this ;
	// Create the heading link.
	this.headingLink = document.createElement("a") ;
	this.excerptDiv.appendChild( this.headingLink ) ;
	this.headingLink.appendChild( document.createTextNode(this.headingText) ) ;
	if( this.isOutside ) {
		// Start with url passed by ajax data on related outside lexias.
		this.headingLink.referredUrl =  (typeof argObj.url == "undefined")? "[no url defined]" : argObj.url ;
		this.headingLink.referredAnchor =  (typeof argObj.anchor == "undefined")? "[no anchor defined]" : argObj.anchor ;
		// Then add any tags currently selected, so remote ThoughtMesh will activate them too.
		var selectedTagsVar = "" ;
		if ( thoughtMesh.selectedTags.length > 0 ) { // User has selected some tags.
			// thoughtMesh_selectedTags =			thoughtMesh.selectedTags[0].tag ; debug("thoughtMesh_selectedTags") ;
			selectedTagsVar += thoughtMesh.selectedTags[0].tag
			// _selectedTagsVar=selectedTagsVar; debug("_selectedTagsVar", 3)
			for (var tagObjCounter=1; tagObjCounter < thoughtMesh.selectedTags.length; tagObjCounter++) {
				selectedTagsVar += "&" + thoughtMesh.selectedTags[tagObjCounter].tag
			};
			this.headingLink.referredUrl += "?" + selectedTagsVar + "#" + this.headingLink.referredAnchor ;	
		};
		// _selectedTagsVar=selectedTagsVar; debug("_selectedTagsVar", 4)
		this.headingLink.href =  this.headingLink.referredUrl ; // UPGRADE: Check for existence?
	}
	else {
		this.headingLink.href = "#" ;
		this.headingLink.onclick = function() { respondAsIf( this.parentNode.lexiaObj.itemEle ) } ;
	}
	// Not using w3c method enables highlighting inside search returns :(
	// this.excerptDiv.appendChild( document.createTextNode(this.lexiaText) ) ;
	this.lexiaHTML = document.createElement("div") ;
	this.lexiaHTML.innerHTML = this.lexiaText ;
	this.excerptDiv.appendChild( this.lexiaHTML ) ;
}
lexiaExcerptObjs = [] ;
lexiaExcerptOutObjs = [] ;
tabs = [] ;
function Tab( argObj ) {
	// UPGRADE: build DOM tabs here too?
	tabs.push( argObj ) ;
	tabs[ argObj.id ] = argObj ;
}
new Tab( {id: "tab-here", presentationId: "lexias-here", isShowing: true } ) ;
new Tab( {id: "tab-out", presentationId: "lexias-out", isShowing: true } ) ;
new Tab( {id: "tab-search", presentationId: "search-here", isShowing: true } ) ;
new Tab( {id: "tab-trace", presentationId: "trace", isShowing: true } ) ;
/************************* Respond to user choices.*************************/
function respondToTag( argObj ) {
respondToTag_argObj =	argObj.linkType ; debug("respondToTag_argObj") ;
	/*________________________ Prepare.________________________*/
	// Register some variables.
	var tagArg =  (typeof argObj.tagArg == "undefined")? "[no tagArg passed!]" : argObj.tagArg ;
	var linkType = (typeof argObj.linkType == "undefined")? "[no linkType passed]" : argObj.linkType ;
	// Focus correct tab.
	if ( !tabs["tab-out"].isShowing ) {
		respondToTab( document.getElementById("tab-here") ) ;
	}
	// Clear out old lexias.
	document.getElementById("lexias-here").innerHTML = "" ;
	document.getElementById("lexias-out").innerHTML = "" ;
	lexiaExcerptObjs = [] ;
	/*________________________ Adjust tag styles.________________________*/
	switch (linkType) {
		case "replace": // Replacing old tags with a single new tag.
			// Reset all styles.
			for (var cloudTagCounter=0; cloudTagCounter < cloudTagObjs.length; cloudTagCounter++) {
				/*_cloudTagCounter =				cloudTagCounter ; debug("_cloudTagCounter") ;
				_cloudTag =				cloudTagObjs[cloudTagCounter].tag ; debug("_cloudTag") ;*/
				cloudTagObjs[cloudTagCounter].replaceLink.className = "cloud-tag" ;
				cloudTagObjs[cloudTagCounter].plusLink.style.display = "inline" ;
				cloudTagObjs[cloudTagCounter].minusLink.className = "cloud-tag" ;
				cloudTagObjs[cloudTagCounter].minusLink.style.display = "none" ;
				try {
				cloudTagObjs[cloudTagCounter].blankLink.style.display = "none" ;
				}
				catch(e) {
					_cloudTagCounter =				cloudTagCounter ; debug("_cloudTagCounter",8) ;
					_cloudTag =				cloudTagObjs[cloudTagCounter].tag ; debug("_cloudTag",8) ;
				}
			}
			// Style this tag.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].plusLink.style.display = "none" ;
			cloudTagObjs[tagArg].minusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			// If starting over from a new tag, wipe out the old one.
			thoughtMesh.selectedTags = [] ;
			thoughtMesh.selectedTags.push( {tag: tagArg, integer: 0} ) ; // thoughtMesh.selectedTags is a manager of objects with integer index and tag name properties.
			thoughtMesh.selectedTags[tagArg] = thoughtMesh.selectedTags[0] ;
			break ;
		case "plus": // Adding a tag in combination.
			// Register new tag. Currently no limitation on number, but they always intersect.
			thoughtMesh.selectedTags.push( {tag: tagArg, integer: thoughtMesh.selectedTags.length} ) ;
			// Enable recovery of array index from the tag name, for use in subtracting tag below.
			thoughtMesh.selectedTags[tagArg] = thoughtMesh.selectedTags[ thoughtMesh.selectedTags.length-1 ] ; // This is an object.
			// Style this tag.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].plusLink.style.display = "none" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].minusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].blankLink.style.display = "none" ;
			break ;
		case "minus": // Subtracting a tag in combination.
			// DEBUGGING for (var remainingTagCounter=0; remainingTagCounter < thoughtMesh.selectedTags.length; remainingTagCounter++) {
			// 	tagBefore = thoughtMesh.selectedTags[remainingTagCounter].tag ; debug("tagBefore",2)
			// 	tagPositionBefore = thoughtMesh.selectedTags[remainingTagCounter].integer ; debug("tagPositionBefore")
			// };			
			thoughtMesh.selectedTags.splice( thoughtMesh.selectedTags[tagArg].integer, 1 ) ;
			// The above changes the .integer property of each subsequent tag, so pop the rest of them up to compensate.
			for (var remainingTagCounter=thoughtMesh.selectedTags[tagArg].integer; remainingTagCounter < thoughtMesh.selectedTags.length; remainingTagCounter++) {
				thoughtMesh.selectedTags[remainingTagCounter].integer = remainingTagCounter ;
			};
			// DEBUGGING for (var remainingTagCounter=0; remainingTagCounter < thoughtMesh.selectedTags.length; remainingTagCounter++) {
			// 	tagAfter = thoughtMesh.selectedTags[remainingTagCounter].tag ; debug("tagAfter",3)
			// 	tagPositionAfter = thoughtMesh.selectedTags[remainingTagCounter].integer ; debug("tagPositionAfter")
			// };			
			// Reset this tag to normal styles.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag" ;
			cloudTagObjs[tagArg].plusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].minusLink.style.display = "none" ;
			cloudTagObjs[tagArg].blankLink.style.display = "none" ;
			break ;
	}
	// Hide content.
	document.getElementById('content').style.display="none" ;
	/*________________________ Identify and show matching lexias in.________________________*/
	// Loop through menu items.
	nextItemLabel:
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		var thisItemEle = homeMenu.itemManager[itemCounter].htmlElement ;
		if ( thoughtMesh.tagsFromItem[ thisItemEle.id ].length == 0 ) { // Eliminates menu items without tags.
			continue nextItemLabel ; // Skips to next menu item.
		}
		else {
			homeMenu.itemManager[itemCounter].selectedViaTags = false ; // Temporary assignment, usually contradicted below.
			// Loop through the selected tags in the cloud.
			for (var cloudTagCounter=0; cloudTagCounter < thoughtMesh.selectedTags.length; cloudTagCounter++) {
				homeMenu.itemManager[itemCounter].selectedViaTags = false ;
				// Loop through that item's tags.
				for (var itemTagCounter=0; itemTagCounter < thoughtMesh.tagsFromItem[ thisItemEle.id ].length; itemTagCounter++) {
					// If an item's tag does not match any one of the selected tags, mark it for non-highlight.
					if ( thoughtMesh.tagsFromItem[ thisItemEle.id ][itemTagCounter] == thoughtMesh.selectedTags[cloudTagCounter].tag ) {
						// A match!
						homeMenu.itemManager[itemCounter].selectedViaTags = true ;
					}
					else {
						// Continue with false.
					}
				}
				if ( !homeMenu.itemManager[itemCounter].selectedViaTags ) {
					continue nextItemLabel ;
				}
			}
			if (homeMenu.itemManager[itemCounter].selectedViaTags ) {
				// Strip out HTML.
				var lexiaTextClean = getInnerText( homeMenu.itemManager[itemCounter].content.innerHTML ) ;
				// Strip out "next Lexia" link.
				if (lexiaTextClean.indexOf("Next &gt;") != -1) {
					var startLexia =  lexiaTextClean.indexOf("Next &gt;") + 10 ; // Add number of characters necessary to go past initial link.
				}
				else {
					var startLexia =  0 ;
				}
				// JS trivia: substring takes end point as second argument; substr takes number of characters to include.
				lexiaTextClean = lexiaTextClean.substr( startLexia, 300 )  + "..." ;
				var lexiaExcerptObj = new LexiaExcerpt( {
					containerEle: document.getElementById("lexias-here"),
					headingText: thisItemEle.innerHTML,
					itemEle: thisItemEle,
					lexiaText: lexiaTextClean
				} ) ;
				// Add to excerpt manager.
				lexiaExcerptObjs.push(lexiaExcerptObj) ;
				// Create a handle that looks like lexiaExcerptsObj["introduction"].
				lexiaExcerptObjs[lexiaExcerptObj.itemEle.id] = lexiaExcerptObj ;
			}
		}					
	}
	if (lexiaExcerptObjs.length == 0 ) {
		warnNoMatch( { tagArray: thoughtMesh.selectedTags, presentationId: "lexias-here" } ) ;
	}
	/*________________________ Identify and show matching lexias out.________________________*/
	if ( typeof thoughtMesh.selectedTags[0] != "undefined") {
		thoughtMesh.tagList = thoughtMesh.selectedTags[0].tag ;
		for (var tagCounter=1; tagCounter < thoughtMesh.selectedTags.length; tagCounter++) {
			thoughtMesh.tagList += "," + thoughtMesh.selectedTags[tagCounter].tag ;
		};
		_thoughtMesh_tagList =	thoughtMesh.tagList ; debug("_thoughtMesh_tagList", 5) ;
		if( thoughtMesh.method == "telamon") {
			if (browsers.saf) {
				var browserWarning = "<span class='warning'>" ;
				browserWarning += "Your browser does not appear to support this feature. " ;
				browserWarning += "<a href='thoughtmesh/recommended_thoughtmesh_browsers.html'>Learn more.</a>" ;	
				document.getElementById("lexias-out").innerHTML = browserWarning ;
				return ;
			}
			telamon.get( thoughtMesh.outsideLexiasUrl, {tag: thoughtMesh.tagList, documentid : thoughtMesh.id }, "outsideLexiasFun(); showOutsideLexias();" ); // Passing an array.		
		}
		if( thoughtMesh.method == "custom") {
			outsideLexiasFun() ;
			showOutsideLexias() ;
		}
		if( thoughtMesh.method == "standalone") {
			document.getElementById("lexias-out").innerHTML = "This page has no outside lexias." ; // Default--move?
		}
	}
	else {
		document.getElementById("lexias-out").innerHTML = "Pick a tag to see related lexias." ; // Default--move?
		document.getElementById("lexias-here").innerHTML = "Pick a tag to see related lexias." ; // Default--move?
		// Make plusses go away.
		for (var cloudTag in cloudTagObjs) {
			cloudTagObjs[cloudTag].plusLink.style.display = "none" ;
			cloudTagObjs[cloudTag].blankLink.style.display = "inline" ;
		}
	}
};
function respondToTagInContent( linkEle ) {
	respondToTag({tagArg: linkEle.firstChild.nodeValue , linkType: 'replace'}); 
	hideCloud({ wantClosed: false }) ;
}
function hideCloud(cloudHeaderEle, wantClosed) { // DEPRECATED: cloudHeaderEle is no longer necessary; could be useful in future if more than one cloud?
	if (wantClosed) {
		document.getElementById("thoughtmesh-title-id").className = "thoughtmesh-title-closed" ;
		document.getElementById("cloud-container").style.display = "none" ;		
		cloudHeaderEle.onmouseover = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title'} ;
		cloudHeaderEle.onmouseout = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title-closed'} ;

	}
	else { // You want it open.
		document.getElementById("thoughtmesh-title-id").className = "thoughtmesh-title" ;
		document.getElementById("cloud-container").style.display = "block" ;
		cloudHeaderEle.onmouseover = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title-closed'} ;
		cloudHeaderEle.onmouseout = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title'} ;
	}
}
function toggleCloud(cloudHeaderEle) {
	if ( document.getElementById("cloud-container").style.display != "none" ) { // Cloud is open (true on load).
		hideCloud(cloudHeaderEle, true) ;
	}
	else { // Cloud is closed.
		hideCloud(cloudHeaderEle, false) ;
	}
}
function respondToTab(tabEle) {
	// Reset all tabs and content to "off".
	for (var tabCounter=0; tabCounter < document.getElementById("tabs").getElementsByTagName("div").length; tabCounter++) {
		document.getElementById("tabs").getElementsByTagName("div")[tabCounter].className = "tab" ; // Default
		document.getElementById("tabs").getElementsByTagName("div")[tabCounter].style.borderBottomWidth = "0px" ;
	};
	for (var tabCounter=0; tabCounter < tabs.length; tabCounter++) {
		tabs[tabCounter].isShowing = false ;
		document.getElementById( tabs[tabCounter].presentationId ).style.display = "none" ;		
	};
	// Highlight chosen content.
	tabEle.style.borderBottomWidth = "2px" ;
	tabby = tabs[tabEle.id].id; debug("tabby", 4)
	tabs[tabEle.id].isShowing = true ;
	document.getElementById( tabs[tabEle.id].presentationId ).style.display = "block" ;
	// For outside lexias, fade tags to show which tags actually correspond to outside lexias in the mesh.
	for (var i=0; i < thoughtMesh.uniqueTags.length; i++) {
		if (tabEle.id == "tab-out") { // User wants to see outside lexias.
			if ( typeof numTaggedWith != "undefined") { // Make sure user is online.
				if ( numTaggedWith.documents[ thoughtMesh.uniqueTags[i] ] > 1 ) { // There are other documents with that tag.
					// Leave opaque.
				}
				else {
					tagToHide=thoughtMesh.uniqueTags[i]; debug("tagToHide")
					cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.style.opacity = ".2" ;
					// cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.filters.item("DXImageTransform.Microsoft.Alpha").Opacity=20 ;
					// cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.filters["DXImageTransform.Microsoft.Alpha"].Opacity=20 ;
					if ( browsers.ie ) { // God-damned IE fails to style tagBox or apply opacity filter >:0
						cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontWeight = "normal" ;			
						cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontStyle = "italic" ;			
					};
				}
			}
		}
		else { // User chose a different tab than outside lexias.
			cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.style.opacity = "1" ;			
			if ( browsers.ie ) { // God-damned IE fails to style tagBox or apply opacity filter >:0
				cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontWeight = "bold" ; // KLUDGE. Damned IE!			
				cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontStyle = "normal" ; // KLUDGE.		
			};
		}
	};
	debug("'End respondToTab()'")
}
function showOutsideLexias() { // thoughtMesh.selectedTags is a manager with a "tag" property.
	debug("'showOutsideLexias'")
	if (thoughtMesh.selectedTags[0]) {
		// DEBUGGING.
		_type = typeof outsideLexiasObj ; debug("_type")
		_outsideLexiasObj=outsideLexiasObj; debug("_outsideLexiasObj")
		_firstTag=outsideLexiasObj["community,defect"]; debug("_firstTag")
		// Loop through documents in outsideLexiasObj.
		var outsideLexiasFound = false ;
		for (var documentProperty in outsideLexiasObj){
			outsideLexiasFound = true ;
			_documentProperty =				documentProperty ; debug("_documentProperty", 5) ;
			var documentCitation = new DocumentCitation( {
				containerEle: document.getElementById("lexias-out"),
				author: outsideLexiasObj[documentProperty].author,
				title: outsideLexiasObj[documentProperty].title,
				url: outsideLexiasObj[documentProperty].url
			} ) ;
			// Loop through lexias in that document.
			for ( var lexiaProperty in outsideLexiasObj[documentProperty].lexias ) {
				var lexiaExcerptOutObj = new LexiaExcerpt( {
					containerEle: document.getElementById("lexias-out"),
					url: outsideLexiasObj[documentProperty].url,
					headingText: outsideLexiasObj[documentProperty].lexias[lexiaProperty].heading,
					anchor: outsideLexiasObj[documentProperty].lexias[lexiaProperty].anchor,
					isOutside: true,
					lexiaText: outsideLexiasObj[documentProperty].lexias[lexiaProperty].excerpt
				} ) ;
				// Add to excerpt manager.
				lexiaExcerptOutObjs.push(lexiaExcerptOutObj) ;
				// Create a handle that looks like lexiaExcerptsObj["introduction"]. UPGRADE: NOT SURE IF THIS IS NEC.
				lexiaExcerptOutObjs[lexiaExcerptOutObj.itemEle.id] = lexiaExcerptOutObj ;
			}
		}
	}
	else {
		document.getElementById("lexias-out").innerHTML = "Pick a tag to see related lexias (essay sections)." ; // Default--move?
	}
	if ( !outsideLexiasFound ) {
		warnNoMatch( { tagArray: thoughtMesh.selectedTags, presentationId: "lexias-out" } ) ;
	}
	// UPGRADE: Trap for case where Telamon can't connect.
}
/************************* Other utilities.*************************/
function warnNoMatch( argObj ) {
		if (argObj.presentationId == "search-here") {
			var warningHTML =  "<h3> <span class='warning'>No excerpts contain that character combination.</span></h3>" ;
			document.getElementById("search-warning").innerHTML = warningHTML ;
		}
		else { // A tag combination.
			var tagList = "";
			for (var tagCounter=0; tagCounter < argObj.tagArray.length; tagCounter++) {
				tagList += argObj.tagArray[tagCounter].tag + " ";
			};
			var warningHTML =  "<h3>No excerpts match the tag combination <span class='warning'>" ;
			warningHTML += tagList ;
			warningHTML += ".</span></h3>" ;
			document.getElementById(argObj.presentationId).innerHTML = warningHTML ;
		}
}
function solicitHelp() {
	if ( confirm("ThoughtMesh is tag-based navigation for Web pages.\n\nCancel if you want to continue navigating this page.\n\nOr click OK to learn more at ThoughtMesh.net.") ) {
		document.location="http://vectors.usc.edu/thoughtmesh/help/";
	}
}
var htmlTagRegExp = /<\/?[^>]+>/gi;
function getInnerText(htmlArg){ // Because .innerText only works in IE.
	// Strip out HTML tags.
	htmlArg = htmlArg.replace(htmlTagRegExp,"");
	return htmlArg;
}
function searchFilter(searchString) { // Written to be independent of tag-based lexia navigation.
	/*________________________ Wait for three characters.________________________*/
	if (searchString.length < 3) {
		document.getElementById("search-warning").innerHTML = "Please type at least three characters." ;		
	}
	else { // Proceed with search.
		searchStr_length =		searchString.length ; debug("searchStr_length", 7) ;
		/*________________________ Initialize some objects.________________________*/
		var regexObj = new RegExp(searchString, "i");
		regexObj.compile(searchString, "i");
		lexiaSearchedObjs = [] ;
		document.getElementById("search-warning").innerHTML = "" ;
		document.getElementById("search-results").innerHTML = "" ;
		/*________________________ If content contains string, add a lexia to search presentation.________________________*/
		for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
			var contentText = getInnerText( homeMenu.itemManager[itemCounter].content.innerHTML ) ;
			if ( regexObj.test(contentText) ) {
				// Find the first place where the string appears. Pick the smaller of the lowercase (default) and capitalized appearances.
				// _testing = homeMenu.itemManager[itemCounter].content.id ; debug("_testing", 6)
				// _searchString =				searchString ; debug("_searchString") ;
				var firstStringOccurrence = contentText.indexOf( searchString ) ;
				// _firstStringOccurrence =				firstStringOccurrence ; debug("_firstStringOccurrence") ;
				var firstCapitalizedOccurrence = contentText.indexOf( searchString.substr(0,1).toUpperCase() + searchString.substr( 1, searchString.length-1 ) ) ;
				capVersion = searchString.substr(0,1).toUpperCase() + searchString.substr( 1, searchString.length-1 ) ; debug("capVersion")
				// _firstStringOccurrence =				firstStringOccurrence ; debug("_firstStringOccurrence") ;
				// _firstCapitalizedOccurrence =				firstCapitalizedOccurrence ; debug("_firstCapitalizedOccurrence") ;
				if ( firstCapitalizedOccurrence >= 0 ) { // A capital was found.
					if ( firstStringOccurrence < 0 || firstStringOccurrence > firstCapitalizedOccurrence ) { // Lowercase doesn't appear or is after capitalized appearance.
						firstStringOccurrence = firstCapitalizedOccurrence ;
						// _capital_is_first = homeMenu.itemManager[itemCounter].content.id ; debug("_capital_is_first", 5)
					}
				}
				if (firstStringOccurrence > 20) { // So you don't ask for more characters at the beginning than you have.
				// substring takes as second arg the *position* you want to go from the beginning.
				// substr takes as second arg the *length* you want.
					var contentToDisplay = "..." + contentText.substring( firstStringOccurrence-20 , firstStringOccurrence ) ;
				}
				else { // String appears near the beginning, so start there.
					var contentToDisplay = contentText.substring( 0 , firstStringOccurrence ) ;
				}
				// Now add and highlight the word found.
				contentToDisplay += "<strong>" + contentText.substr( firstStringOccurrence, searchString.length ) + "</strong>" ;
				// Now add the rest of the excerpt, to a total of 300 characters. Subtract 17 to account for <strong></strong>.
				contentToDisplay += contentText.substr(  firstStringOccurrence + searchString.length , 300 - (contentToDisplay.length - 17) ) + "...";
				var lexiaExcerptObj = new LexiaExcerpt( {
					containerEle: document.getElementById("search-results"),
					headingText: homeMenu.itemManager[itemCounter].htmlElement.innerHTML,
					itemEle: homeMenu.itemManager[itemCounter].htmlElement,
					lexiaText: contentToDisplay
				} ) ;
				// Add to excerpt manager.
				lexiaSearchedObjs.push(lexiaExcerptObj) ;
				// Create a handle that looks like lexiaSearchedObjs["introduction"].
				lexiaSearchedObjs[lexiaExcerptObj.itemEle.id] = lexiaSearchedObjs ;
			}
		};	
		if (lexiaSearchedObjs.length == 0 ) {
			warnNoMatch({ tagArray: thoughtMesh.selectedTags, presentationId: "search-here" } ) ;
		}
	} // End if search string long enough.
}
function addToMenuResponses() {
	debug("'Begin addToMenuResponses().'")
	// UPGRADE: Not sure if this will conflict with multi-menu implementations. Consider js_superclass...html to solve that.
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		homeMenu.itemManager[itemCounter].associateResponse(   {  
			responseFun: "hideCloud( document.getElementById('thoughtmesh-header-id'), true ); document.getElementById('content').style.display='block'"
		  }    ) ;
	};
	debug("'End addToMenuResponses().'")
}