Monday, November 08, 2010 at 12:04 AM.

system.verbs.builtins.mainResponder.discuss.renderOneMessage

on renderOneMessage (adrMsgTable, adrTemplate, flShowAuthor=true, flShowPostTime=true, flShowReadCount=true, flShowResponseCount=true, flShowEnclosures=true, flShowResponseForm=true, flShowCowSkullLink=false, editPageUrl="", adrData=nil, bgColor="ivory", otherColor="beige", headerColor="black", headerTextColor="ivory", adrGetBodyCallback=nil, adrGetTimeCallback=nil, cssPrefix="", y=nil, m=nil, d=nil, defaultMode="day", responseItemTemplate=nil, responseListHeader=nil, responseListFooter=nil, responseIndentTemplate=nil, responseSubThreadStartTemplate=nil, responseSubThreadEndTemplate=nil, pta=nil, userIcon=nil, adrInDgCallback=nil, depthToExpandResponses=0, flResponsesInChronologicalOrder=false, responseFullItemTemplate=nil, flUseMappedMemberKeys=false) {
	<<Render a single message in a thread, flowing through a template if specified.
		<<10/11/00; 10:30:27 PM by JES
	<<Changes:
		<<4/8/03; 11:43:18 PM by JES
			<<Added support for the {ipAddress} pseudo-macro in the template.
		<<4/4/02; 1:07:05 AM by JES
			<<New optional parameter, adrInDgCallback, is the address of a callback which determines whether a message should be included in the rendering. If the callback returns true, add the message. If it returns false, don't add the message.
			<<More new optional params: depthToExpandResponses, flResponsesInChronologicalOrder and responseFullItemTemplate, used for displaying the full text of responses in the response list. These support the Slashdot-style discussion group renderings.
		<<10/18/01; 11:11:11 AM by PBS
			<<Don't use IP address when constructing URLs to built-in icons. Fixes a bug with machines with multiple IP addresses.
		<<11/21/00; 6:04:09 PM by PBS
			<<Use new bottleneck for displaying edit message in Pike button.
		<<10/18/00; 10:48:50 PM by JES
			<<Commented out code that adds the Edit in Frontier button.
		<<10/14/00; 2:29:42 PM by JES
			<<Added 'in response to xxx' to the msg number when rendered in a template.
		<<10/13/00; 12:05:00 AM by JES
			<<Added support for template-based rendering.
	
	bundle { // get pta, adrData, y, m and d
		if pta == nil {
			pta = html.getPageTableAddress ()};
		if adrData == nil {
			adrData = mainResponder.discuss.openRoot (pta)};
		if y == nil {
			y = date.year ()};
		if m == nil {
			m = date.month ()};
		if d == nil {
			d = date.day ()}};
	bundle { //increment read stats on the top message
		adrMsgTable^.ctReads++;
		config.mainResponder.stats.ctDiscussionGroupReads++};
	
	local (flMember = defined (pta^.adrMemberInfo));
	local (membershipGroup = pta^.responderAttributes.defaultMembershipGroup);
	local (flCss = (cssPrefix != ""));
	local (msgReaderUrl = pta^.urls^.discussMsgReader);
	if not (msgReaderUrl endsWith "$") {
		msgReaderUrl = msgReaderUrl + "$"};
	
	on buildSearchArgs (adrArgTable) {
		local (ct = sizeOf (adrArgTable^), i, s = "?");
		for i = 1 to ct {
			s = s + nameOf (adrArgTable^[i]) + "=" + adrArgTable^[i] + "&"};
		s = string.mid (s, 1, sizeOf (s) - 1);
		return (s)};
	
	local (mode, thisModeSearchArgs, otherModeSearchArgs, replyToThisSearchArgs, flShowResponses=true);
	bundle { // get the mode, and the search args for preserving and switching the mode
		local (searchArgs = "", argTable);
		new (tableType, @argTable);
		bundle { //parse search args
			if defined (pta^.searchArgs) {
				searchArgs = pta^.searchArgs};
			webserver.parseArgs (searchArgs, @argTable)};
		if not defined (argTable.mode) {
			argTable.mode = defaultMode};
		bundle { //user-specifiable search args
			if defined (argTable.showResponses) {
				flShowResponses = boolean (argTable.showResponses)};
			if defined (argTable.expandToDepth) {
				depthToExpandResponses = number (argTable.expandToDepth)};
			if defined (argTable.newestFirst) {
				flResponsesInChronologicalOrder = not boolean (argTable.newestFirst)}};
		bundle { //this mode search args
			argTable.mode = string.lower (argTable.mode);
			mode = argTable.mode;
			thisModeSearchArgs = buildSearchArgs (@argTable)};
		bundle { //reply to this search args
			local (replyArgsTable = argTable);
			replyArgsTable.showResponses = false;
			replyToThisSearchArgs = buildSearchArgs (@replyArgsTable)};
		bundle { //other mode search args
			local (otherModeArgsTable = argTable);
			if otherModeArgsTable.mode == "day" {
				otherModeArgsTable.mode = "topic"}
			else {
				otherModeArgsTable.mode = "day"};
			otherModeSearchArgs = buildSearchArgs (@otherModeArgsTable)}};
	
	local (htmltext);
	local (msgnum = adrMsgTable^.msgnum);
	local (template);
	if adrTemplate != nil {
		template = string (adrTemplate^)};
	
	local (userIconUrl, userIconHeight = 16, userIconWidth = 16);
	local (internalLinkImgUrl, internalLinkImgWidth = 11, internalLinkImgHeight = 9, internalLinkUrl);
	bundle { // build image data for internal link arrows and message status icons
		<<local (myDottedId = tcp.myDottedId ())
		<<local (myPort = user.inetd.config.http.port)
		<<local (portString = "")
		<<if myPort != 80
			<<portString = ":" + myPort
		<<local (resourcesUrl = "http://" + myDottedId + portString + "/mainResponderResources/")
		local (resourcesUrl = "/mainResponderResources/");
		
		internalLinkImgUrl = resourcesUrl + "icons/leftArrow";
		internalLinkUrl = pta^.responderAttributes.urls^.discussMsgReader;
		if not (internalLinkUrl endsWith '$') {
			internalLinkUrl = internalLinkUrl + "$"};
		<<internalLinkUrl = internalLinkUrl + msgNum + "#"
		userIconUrl = resourcesUrl + "icons/user";
		
		<<msgIconUrl = resourcesUrl + "icons/message"
		if userIcon == nil {
			userIcon = "<img src=\"" + userIconUrl + "\" height=\"" + userIconHeight + "\" width=\"" + userIconWidth + "\" border=\"0\" alt=\"user\">"}};
	
	local (cowskullImg = mainResponder.discuss.cowSkullImage (pta), cowskullLink);
	bundle { // get the cowskull link html
		cowskullLink = "<a href=\"" + pta^.responderAttributes.urls^.discussMsgReader + msgNum + otherModeSearchArgs + "\">" + cowskullImg + "</a>"};
	
	on add (s) {
		htmlText = htmlText + s};
	on countResponses (adrMsgTable) { // count all the responses in a thread
		local (responseCount);
		local (i, ct = sizeOf (adrMsgTable^.responses));
		responseCount = ct;
		for i = 1 to ct {
			local (adrResponse = @adrData^.messages.[string.padWithZeros (adrMsgTable^.responses[i], 7)]);
			if sizeOf (adrResponse^.responses) {
				responseCount = responseCount + countResponses (adrResponse)}};
		return (responseCount)};
	on getBody (adrMsgTable) { // get the body of the message, calling callbacks if necessary
		local (body, flCallbackReturnedBody = false);
		if adrGetBodyCallback != nil {
			body = adrGetBodyCallback^ (adrMsgTable);
			if body != "" {
				<<add (body)
				flCallbackReturnedBody = true}};
		if not flCallbackReturnedBody {
			bundle { //PBS 10/20/99: if an image is attached to this message, display it above the body
				if defined (adrMsgTable^.image) {
					if defined (pta^.responderAttributes.urls^.imageViewer) { //must be defined: it's the link to the imageViewer script
						<<The imageViewer script takes a msgNum as pathArgs and displays the picture attached to that dg message.
						body = body + mainResponder.discuss.buildImageTag (adrMsgTable^.msgNum);
						body = body + "<p>"}}};
			if typeOf (adrMsgTable^.body) == outlineType { //outlines get passed through an outline renderer
				local (oldRenderOutlineWith = nil);
				
				if defined (pta^.renderOutlineWith) {
					oldRenderOutlineWith = pta^.renderOutlineWith};
				if defined (pta^.renderDiscussOutlinesWith) {
					pta^.renderOutlineWith = pta^.renderDiscussOutlinesWith};
				
				if defined (pta^.flReadMessageProcessMacros) and pta^.flReadMessageProcessMacros {
					try {body = body + html.processMacros (html.tenderRender (@adrMsgTable^.body), false, pta)}}
				else {
					if defined (pta^.flReadMessageExpandUrls) and not pta^.flReadMessageExpandUrls {
						try {body = body + html.tenderRender (@adrMsgTable^.body)}}
					else { //default, original behavior
						try {body = body + html.expandURLs (html.tenderRender (@adrMsgTable^.body))}}};
				
				if oldRenderOutlineWith != nil {
					pta^.renderOutlineWith = oldRenderOutlineWith}}
			else { //it's not an outline
				local (s = string (adrMsgTable^.body));
				s = string.replaceAll (s, "\n", ""); //2/15/99 DW, inside <pre>s these cause extra line-skips
				
				if not (defined (pta^.flReadMessageAutoParagraphs)) or pta^.flReadMessageAutoParagraphs {
					s = string.replaceAll (s, "\r\r", "\r\r<p>")};
				if not (defined (pta^.flReadMessageProcessMacros)) or pta^.flReadMessageProcessMacros {
					s = html.processMacros (s, false, pta)};
				body = body + s}};
		return (body)};
	on getEditButtons () { //add Edit button (or buttons) to the page
		local (s, editableInFrontier = false);
		
		<<s = s + "<table cellpadding=\"5\" cellspacing=\"0\" border=\"0\" bgcolor=\"white\"><tr>"
		<<if defined (pta^.adrMemberInfo^.isFrontierSixAlpha) //edit in Frontier button
			<<local (url = pta^.responderAttributes.urls^.discussEditInFrontier + msgNum)
			<<
			<<Build the Edit in Frontier button.
			<<s = s + "<td valign=\"baseline\">"
			<<s = s + "<form method=\"POST\" action=\"" + url + "\">"
			<<s = s + "<input name=\"msgnum\" type=\"hidden\" value=\"" + msgNum + "\">"
			<<s = s + " <input type=\"submit\" value=\"" + mainResponder.getString ("discuss.editInFrontier") + "\">"
			<<s = s + "</form>"
			<<s = s + "</td>"
			<<
			<<editableInFrontier = true
		bundle { //edit in web browser button
			if editPageURL == "" {
				editPageURL = pta^.responderAttributes.urls^.discussEditInBrowser};
			
			<<s = s + "<td valign=\"baseline\">"
			
			s = s + "<form method=\"POST\" action=\"" + editPageURL + msgNum + "\">";
			s = s + "<input name=\"msgnum\" type=\"hidden\" value=\"" + msgNum + "\">";
			local (buttonValue = " " + mainResponder.getString ("discuss.editThisPage") + " ");
			
			if editableInFrontier {
				buttonValue = mainResponder.getString ("discuss.editInBrowser"); //change button name from Edit this Page to Edit in Browser
				s = s + " "};
			
			s = s + " <input type=\"submit\" value=\"" + buttonValue + "\">";
			s = s + "</form>";
			};
			<<s = s + "</td>"
		bundle { //PBS 01/13/00: Add the Edit in Pike button
			<<if defined (pta^.responderAttributes.urls^.editInPike)
				<<Pike support is a feature not of mainResponder but of Manila. However, this script, which displays the discussion group messages and the Edit this Page button, needs to be Pike-aware enough to display the Pike button.
				<<if manilaSuite.pike.memberHasPike (pta)
					<<local (editInPikeUrl = pta^.responderAttributes.urls^.editInPike)
					<<if not (editInPikeUrl endsWith "$")
						<<editInPikeUrl = editInPikeUrl + "$"
					<<editInPikeUrl = editInPikeUrl + msgNum
					<<local (adrSite = pta^.adrSiteRootTable)
					<<local (adrMember = pta^.adrMemberInfo)
					<<
					<<s = s + "<td valign=\"baseline\">"
					<<s = s + " " + manilaSuite.pike.pikeButton (editInPikeUrl, adrMsgTable, adrMember, adrSite, true, mainResponder.getString ("discuss.editThisStoryInPikeOutline"), pta) //true because this is a message
					<<s = s + "</td>"
			s = s + " " + manilaSuite.pike.editMessageButton (adrMsgTable, pta:pta)}; //PBS 11/21/00: use new bottleneck for displaying edit message in Pike button
		<<s = s + "</tr></table>"
		
		return (s)};
	on getTopicMsgTable () {
		local (nomad = adrMsgTable);
		while nomad^.inResponseTo > 0 {
			<<nomad = @adrData^.messages.[string.padWithZeros (nomad^.inResponseTo, 7)]
			nomad = mainResponder.discuss.getMessageTable (nomad^.inResponseTo, pta, adrData)};
		return (nomad)};
	on getNextMsgNum () {
		local (lastMsgNum = adrData^.prefs.nextMsgNum - 1);
		local (nomad, nextMsgNum = msgNum + 1);
		while nextMsgNum <= lastMsgNum {
			nomad = mainResponder.discuss.getMessageTable (nextMsgNum, pta, adrData);
			if not (mainResponder.discuss.isMessageDeleted (nextMsgNum, nomad, pta:pta)) {
				if adrInDgCallback == nil {
					return (nextMsgNum)}
				else {
					local (adrmsg = mainResponder.discuss.getMessageTable (nextMsgNum, pta));
					if adrInDgCallback^ (adrmsg) {
						return (nextMsgNum)}}};
			nextMsgNum++};
		return (0)};
	on getPrevMsgNum () {
		local (nomad, prevMsgNum = msgNum - 1);
		while prevMsgNum > 0 {
			nomad = mainResponder.discuss.getMessageTable (prevMsgNum, pta, adrData);
			if not (mainResponder.discuss.isMessageDeleted (prevMsgNum, nomad, pta:pta)) {
				if adrInDgCallback == nil {
					return (prevMsgNum)}
				else {
					local (adrmsg = mainResponder.discuss.getMessageTable (prevMsgNum, pta));
					if adrInDgCallback^ (adrmsg) {
						return (prevMsgNum)}}};
			prevMsgNum--};
		return (prevMsgNum)};
	on obscureEmail (s, link=false) {
		if s contains '@' {
			local (part1 = string.nthField (s, '@', 1));
			local (part2 = string.nthField (s, '@', 2));
			if link {
				return ("<a href=\"mailto:" + s + "\">" + part1 + "@" + part2[1] + "...</a>")}
			else {
				return (part1 + "@" + part2[1] + "...")}};
		return (s)};
	
	if mainResponder.discuss.isMessageDeleted (msgNum, adrMsgTable, pta) {
		return ("")};
	
	bundle { //calculate enclosure and edit buttons display flags
		flShowEnclosures = flShowEnclosures and (defined (adrMsgTable^.enclosureAddress) and (adrMsgTable^.enclosureAddress != ""));
		flShowEditButtons = (flMember and (mainResponder.discuss.memberCanEdit (adrMsgTable, pta^.adrMemberInfo, pta)))};
	
	if adrTemplate == nil { //render the message in default fashion
		bundle { //add member, internal link, read count and date
			if flCss { // the header row and cell
				add ("<tr class=\"" + cssPrefix + "HeaderRow\">");
				add ("<td class=\"" + cssPrefix + "AuthorCell\">")}
			else {
				add ("<tr><td>")};
			
			bundle { // the header table
				add ("<table bgcolor=\"" + othercolor + "\" cellpadding=\"5\" cellspacing=\"0\" border=\"0\" width=\"100%\"><tr><td>");
				
				if flShowAuthor {
					add (mainResponder.members.linkToMember (membershipGroup, adrMsgTable^.member, hideEmail: true, useMap:flUseMappedMemberKeys))}
				else {
					add (" ")};
				bundle { // add internal link
					add ("<a name=\"" + msgNum + "\"> </a>");
					add ("<a href=\"" + internalLinkUrl + msgNum + "?y=" + y + "&m=" + m + "&d=" + d + "\" title=\"Permanent link to this message in archives.\">");
					add ("<img src=\"" + internalLinkImgUrl + "\" height=\"" + internalLinkImgHeight + "\" width=\"" + internalLinkImgWidth + "\" border=\"0\" alt=\"blueArrow\"></a>")};
				
				if flShowReadCount or flShowResponseCount {
					local (s = " (");
					if flShowReadCount {
						s = s + adrMsgTable^.ctReads + " read";
						if adrMsgTable^.ctReads != 1 {
							s = s + "s"};
						if flShowResponseCount {
							s = s + ", "}};
					if flShowResponseCount {
						local (responses = countResponses (adrMsgTable));
						s = s + responses + " response";
						if responses != 1 {
							s = s + "s"}};
					s = s + ")";
					add ("<font size=\"-1\">" + s + "</font>")};
				add ("</td>");
				
				if flCss { // the post time cell and font
					add ("<td class=\"" + cssPrefix + "DateCell\" align=\"right\"><font size=\"-1\">")}
				else {
					add ("<td align=\"right\" width=\"100%\"><font size=\"-1\">")};
				if flShowPostTime {
					local (postTime = adrMsgTable^.postTime);
					if adrGetTimeCallback != nil {
						postTime = adrGetTimeCallback^ (postTime)};
					add (mainResponder.localization.abbrevDateString (postTime, pta) + " " + mainResponder.localization.timeString (postTime, pta))}
				else {
					add (" ")};
				add ("</font></td>");
				
				add ("</table>")};
			
			add ("</td></tr>");
			};
			<<add ("</font></td></tr>")
		bundle { //add the body of the message
			if flCss { // the row and cell containing the body of the message
				add ("<tr bgcolor=\"white\" class=\"" + cssPrefix + "BodyRow\">");
				add ("<td class=\"" + cssPrefix + "BodyCell\">")}
			else {
				add ("<tr bgcolor=\"white\"><td>")};
			
			bundle { // the body table
				add ("<table cellpadding=\"5\" cellspacing=\"0\" border=\"0\" width=\"100%\"><tr><td>");
				
				add (getBody (adrMsgTable));
				
				if flShowEditButtons {
					add (getEditButtons ())};
				
				if flShowCowSkullLink { //add cowskull link to standard msgReader page
					<<The cowskull link is a link to the discussion group interface version of this page.
					add ("<p>" + cowskullLink + "</p>")};
				
				add ("</td></tr></table>")};
			
			add ("</td></tr>")}}
	else { //flow the message through the template, and add it
		local (s = template, lowerTemplate = string.lower (template));
		
		bundle { // replace bgcolor, headerColor and headerTextColor
			s = string.replaceAll (s, "{bgcolor}", otherColor, false);
			s = string.replaceAll (s, "{headerColor}", headerColor, false);
			s = string.replaceAll (s, "{headerTextColor}", headerTextColor, false)};
		bundle { // replace author
			local (memberLink = mainResponder.members.linkToMember (membershipGroup, adrMsgTable^.member, hideEmail: true, useMap:flUseMappedMemberKeys));
			s = string.replaceAll (s, "{author}", memberLink, false)};
		bundle { // replace authorIcon
			s = string.replaceAll (s, "{authorIcon}", userIcon, false)};
		bundle { // replace subject
			s = string.replaceAll (s, "{subject}", adrMsgTable^.subject, false)};
		bundle { // replace topic link -- this does a mode flip
			local (topicLink, topicMsgTable = getTopicMsgTable ());
			topicLink = "<a href=\"" + msgReaderUrl + topicMsgTable^.msgNum + otherModeSearchArgs + "\"";
			if flCss {
				topicLink = topicLink + " class=\"" + cssPrefix + "TopicLink\""};
			topicLink = topicLink + ">" + topicMsgTable^.subject + "</a>";
			s = string.replaceAll (s, "{topicLink}", topicLink, false)};
		bundle { // replace messageNumberLink -- this doea a mode flip
			local (msgNumLink = "<a href=\"" + msgReaderUrl + msgNum + otherModeSearchArgs + "\"");
			if flCss {
				msgNumLink = msgNumLink + " class=\"" + cssPrefix + "MessageNumberLink\""};
			msgNumLink = msgNumLink + ">" + msgNum + "</a>";
			s = string.replaceAll (s, "{messageNumberLink}", msgNumLink, false)};
		bundle { // replace inResponseToLink
			local (inResponseToLink);
			if adrMsgTable^.inResponseTo > 0 {
				local (replacementTable); new (tableType, @replacementTable);
				replacementTable.msglink = "<a href=\"" + msgReaderUrl + adrMsgTable^.inResponseTo + thisModeSearchArgs + "\">" + adrMsgTable^.inResponseTo + "</a>";
				inResponseToLink = mainResponder.getString ("discuss.inResponseTo", @replacementTable, pta: pta)}
			else {
				inResponseToLink = mainResponder.getString ("discuss.topMsgInThread", pta: pta)};
			s = string.replaceAll (s, "{inResponseToLink}", inResponseToLink, false)};
		bundle { // replace previousMessageLink
			local (prevMsgNum = getPrevMsgNum (), msgNumLink = "");
			if prevMsgNum {
				msgNumLink = "<a href=\"" + msgReaderUrl + prevMsgNum + thisModeSearchArgs + "\"";
				if flCss {
					msgNumLink = msgNumLink + " class=\"" + cssPrefix + "PreviousMessageLink\""};
				msgNumLink = msgNumLink + ">" + prevMsgNum + "</a>"};
			s = string.replaceAll (s, "{previousMessageLink}", msgNumLink, false)};
		bundle { // replace nextMessageLink
			local (nextMsgNum = getNextMsgNum (), msgNumLink = "");
			if nextMsgNum {
				msgNumLink = "<a href=\"" + msgReaderUrl + nextMsgNum + thisModeSearchArgs + "\"";
				if flCss {
					msgNumLink = msgNumLink + " class=\"" + cssPrefix + "NextMessageLink\""};
				msgNumLink = msgNumLink + ">" + nextMsgNum + "</a>"};
			s = string.replaceAll (s, "{nextMessageLink}", msgNumLink, false)};
		bundle { // replace postTime
			local (d = adrMsgTable^.postTime);
			if adrGetTimeCallback != nil {
				d = adrGetTimeCallback^ (d)};
			s = string.replaceAll (s, "{postTime}", d, false)};
		bundle { // replace readCount
			s = string.replaceAll (s, "{readCount}", adrMsgTable^.ctReads, false)};
		bundle { // replace response count
			if lowerTemplate contains "{responsecount}" { // this may be time-consuming if there are lots of responses
				s = string.replaceAll (s, "{responseCount}", countResponses (adrMsgTable), false)}};
		bundle { // replace editLink 
			if flShowEditButtons {
				s = string.replaceAll (s, "{editLink}", getEditButtons (), false)}
			else { // replace with the empty string
				s = string.replaceAll (s, "{editLink}", "", false)}};
		bundle { // replace discussLink -- this is the cowskull, and it's a mode flip
			if flShowCowSkullLink {
				s = string.replaceAll (s, "{discussLink}", cowskullLink, false)}
			else { // replace with the empty string
				s = string.replaceAll (s, "{discussLink}", "", false)}};
		bundle { // replace internal link
			local (internalLink);
			internalLink = internalLink + "<a name=\"" + msgNum + "\"> </a>";
			internalLink = internalLink + "<a href=\"" + internalLinkUrl + msgNum + "?y=" + y + "&m=" + m + "&d=" + d + "\" title=\"Permanent link to this message in archives.\">";
			internalLink = internalLink + "<img src=\"" + internalLinkImgUrl + "\" height=\"" + internalLinkImgHeight + "\" width=\"" + internalLinkImgWidth + "\" border=\"0\" alt=\"blueArrow\"></a>";
			s = string.replaceAll (s, "{internalLink}", internalLink, false)};
		bundle { // replace responseList
			if lowerTemplate contains "{responselist}" { // short circuit if the template doesn't include a responseList macro because it may be time-consuming
				local (responseList = "");
				if flShowResponses {
					bundle { // set default templates, headers, etc.
						<<It's not ideal, but we check to see if the value of mini-templates equals nil, and also that the type == typeOf (nil). That way we can tell the difference between the empty string and not having passed in a value. This is because the empty string equals nil, but its type is stringType, not typeOf (nil)
						if responseListHeader == nil and typeOf (responseListHeader) == typeOf (nil) {
							responseListHeader = "<p><hr><b>" + mainResponder.getString ("discuss.thereAreResponsesToThisMsg") + "</b></p>"};
						if responseSubThreadStartTemplate == nil and typeOf (responseSubThreadStartTemplate) == typeOf (nil) {
							responseSubThreadStartTemplate = "<ul>"};
						if responseSubThreadEndTemplate == nil and typeOf (responseSubThreadEndTemplate) == typeOf (nil) {
							responseSubThreadEndTemplate = "</ul>"};
						if responseItemTemplate == nil and typeOf (responseItemTemplate) == typeOf (nil) {
							responseItemTemplate = "<li><p><a href=\"{responseUrl}\">{responseSubject}</a>, {responseAuthor}, {responsePostTime}</p></li>"};
						if responseFullItemTemplate == nil and typeOf (responseFullItemTemplate) == typeOf (nil) {
							responseFullItemTemplate = responseItemTemplate}};
					local (stack = {}); // keep track of which messages have been added to aviod infinite recursion
					on addResponses (adrItem, level = 0) {
						local (adrTable, msgNum, responseMember, i, ct = sizeOf (adrItem^.responses), itemHtml);
						local (flFullMessage = level <= (depthToExpandResponses - 1) ); //level is zero-based but the pref is 1-based
						
						if not flFullMessage { //add threadSubStartTemplate
							responseList = responseList + responseSubThreadStartTemplate};
						
						on addResponse () {
							msgNum = adrItem^.responses [i];
							if stack contains msgNum { //avoid infinite recursion
								continue;
								};
							adrTable = mainResponder.discuss.getMessageTable (msgNum, pta, adrData);
							if not defined (adrTable^) {
								continue};
							
							local (flAddMessage = not mainResponder.discuss.isMessageDeleted (msgNum, adrTable, pta) );
							<<if flAddMessage
								<<if adrInDgCallback != nil
									<<flAddMessage = adrInDgCallback^ (adrTable)
							if flAddMessage {
								responseMember = mainResponder.members.getMemberName (membershipGroup, adrTable^.member);
								
								if flFullMessage {
									itemHtml = responseFullItemTemplate;
									if string.lower (responseFullItemTemplate) contains "{responsetext}" { //bump read count
										adrTable^.ctReads++;
										config.mainResponder.stats.ctDiscussionGroupReads++};
									itemHtml = string.replaceAll (itemHtml, "{responseText}", adrTable^.body, false)}
								else {
									itemHtml = responseItemTemplate;
									itemHtml = string.replaceAll (itemHtml, "{responseText}", "", false)};
								itemHtml = string.replaceAll (itemHtml, "{replyUrl}", msgReaderUrl + msgNum + replyToThisSearchArgs, false);
								itemHtml = string.replaceAll (itemHtml, "{responseUrl}", msgReaderUrl + msgNum + thisModeSearchArgs, false);
								itemHtml = string.replaceAll (itemHtml, "{responseSubject}", adrTable^.subject, false);
								itemHtml = string.replaceAll (itemHtml, "{responseAuthor}", obscureEmail (responseMember), false);
								itemHtml = string.replaceAll (itemHtml, "{responsePostTime}", mainResponder.localization.dateTimeString (adrTable^.postTime, pta: pta), false);
								itemHtml = string.replaceAll (itemHtml, "{responseMessageNumber}", adrTable^.msgNum, false);
								
								if responseIndentTemplate != "" { // work around kernel bug on windows: string.filledString ("", n) == crash.
									responseList = responseList + string.filledString (responseIndentTemplate, level)};
								responseList = responseList + itemHtml;
								
								if sizeof (adrTable^.responses) > 0 {
									stack = stack + {adrTable^.msgNum};
									addResponses (adrTable, level + 1);
									delete (@stack [sizeof (stack)])}}};
						if flResponsesInChronologicalOrder {
							for i = 1 to ct {
								addResponse ()}}
						else {
							for i = ct downTo 1 {
								addResponse ()}};
						
						if not flFullMessage { //add subThreadEntTemplate
							responseList = responseList + responseSubThreadEndTemplate};
						};
					if sizeOf (adrMsgTable^.responses) {
						responseList = responseListHeader;
						addResponses (adrMsgTable); //it's recursive
						responseList = responseList + responseListFooter}};
				s = string.replaceAll (s, "{responseList}", responseList, false)}};
		bundle { // replace messageText
			s = string.replaceAll (s, "{messageText}", getBody (adrMsgTable), false)};
		bundle { // replace responseForm
			local (responseForm = "", responseFormHeader = "<p><hr>", responseFormFooter = "");
			if flMember and flShowResponseForm {
				local (subject = adrMsgTable^.subject);
				if not (string.lower (subject) beginswith string.lower (string.trimWhiteSpace (mainResponder.getString ("discuss.rePrefix")))) {
					subject = mainResponder.getString ("discuss.rePrefix") + subject};
				local (subjectprompt, buttontext, bodyprompt);
				subjectprompt = mainResponder.getString ("discuss.responseSubjectPrompt");
				buttontext = mainResponder.getString ("discuss.postResponseButtonText");
				bodyprompt = mainResponder.getString ("discuss.responseBodyPrompt");
				responseForm = responseFormHeader + mainResponder.discuss.newMessageForm (msgNum, subject, subjectprompt, buttontext, bodyprompt: bodyprompt, pta:pta) + responseFormFooter};
			s = string.replaceAll (s, "{responseForm}", responseForm, false)};
		bundle { // 11/03/00 JES: replace enclosureLink
			if flShowEnclosures {
				local (viewingUrl = pta^.responderAttributes.urls^.discussEnclosureViewer + msgNum);
				local (listingLink = "<a href=\"" + viewingUrl + "\">" + mainResponder.getString ("discuss.viewEnclosure") + "</a>");
				local (fatPageLink = "<a href=\"" + pta^.responderAttributes.urls^.discussEnclosureDownloader + msgNum + "\">" + mainResponder.getString ("discuss.fatPageEnclosure") + "</a>");
				local (rpcLink = "<a href=\"" + pta^.responderAttributes.urls^.discussEnclosureRPCer + msgNum + "\">" + mainResponder.getString ("discuss.RPCenclosure") + "</a>");
				
				s = string.replaceAll (s, "{enclosureLink}", listingLink + ", " + fatPageLink + ", " + rpcLink, false)}
			else {
				s = string.replaceAll (s, "{enclosureLink}", "", false)}};
		bundle { //replace ipAddress
			local (ipAddress = "");
			if defined (adrMsgTable^.ipAddress) {
				ipAddress = adrMsgTable^.ipAddress};
			s = string.replaceAll (s, "{ipAddress}", ipAddress, false)};
		
		add (s)};
	
	bundle { //set member info
		<<For every member, store the highest message read for the current discussion group.
			<<First make sure the discussionGroupInfo table exists for this member, then make sure there's a table for this dg, then set highestMessageRead for this dg for this member.
		if flMember { //is this a member?
			local (adrMember = pta^.adrMemberInfo);
			
			if not defined (adrMember^.discussionGroupInfo) {
				new (tabletype, @adrmember^.discussionGroupInfo)};
			
			local (rootName = nameOf (adrData^));
			local (adrTable = @adrMember^.discussionGroupInfo.[rootName]);
			
			if not defined (adrTable^) {
				new (tableType, adrTable)};
			if not defined (adrTable^.highestMessageRead) {
				adrTable^.highestMessageRead = 0};
			if msgNum > adrTable^.highestMessageRead {
				adrTable^.highestMessageRead = msgNum}}};
	
	return (htmltext)}



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.