Skip to content
Snippets Groups Projects
DDMsgReader.js 765 KiB
Newer Older

	// displayBoardInfoInHeader specifies whether or not to display
	// the message group and sub-board lines in the header at the
	// top of the screen (an additional 2 lines).
	this.displayBoardInfoInHeader = false;

	// msgList_displayMessageDateImported specifies whether or not to use the
	// message import date as the date displayed in the message list.  If false,
	// the message written date will be displayed.
	this.msgList_displayMessageDateImported = true;

	// The number of spaces to use for tab characters - Used in the
	// extended read mode
	this.numTabSpaces = 3;

	// Things for mouse support
	this.mouseTimeout = 0; // Timeout in ms.  Currently using 0 for no timeout.
	this.mouseEnabled = false; // To pass to mouse_getkey

	// this.text is an object containing text used for various prompts & functions.
	this.text = {
		scrollbarBGChar: BLOCK1,
		scrollbarScrollBlockChar: BLOCK2,
		goToPrevMsgAreaPromptText: "\1n\1c\1hGo to the previous message area",
		goToNextMsgAreaPromptText: "\1n\1c\1hGo to the next message area",
		newMsgScanText: "\1c\1hN\1n\1cew \1hM\1n\1cessage \1hS\1n\1ccan",
		newToYouMsgScanText: "\1c\1hN\1n\1cew \1hT\1n\1co \1hY\1n\1cou \1hM\1n\1cessage \1hS\1n\1ccan",
		allToYouMsgScanText: "\1c\1hA\1n\1cll \1hM\1n\1cessages \1hT\1n\1co \1hY\1n\1cou \1hS\1n\1ccan",
		goToMsgNumPromptText: "\1n\1cGo to message # (or \1hENTER\1n\1c to cancel)\1g\1h: \1c",
		msgScanAbortedText: "\1n\1h\1cM\1n\1cessage scan \1h\1y\1iaborted\1n",
		deleteMsgNumPromptText: "\1n\1cNumber of the message to be deleted (or \1hENTER\1n\1c to cancel)\1g\1h: \1c",
		editMsgNumPromptText: "\1n\1cNumber of the message to be edited (or \1hENTER\1n\1c to cancel)\1g\1h: \1c",
		searchingSubBoardAbovePromptText: "\1n\1cSearching (current sub-board: \1b\1h%s\1n\1c)",
		searchingSubBoardText: "\1n\1cSearching \1h%s\1n\1c...",
		noMessagesInSubBoardText: "\1n\1h\1bThere are no messages in the area \1w%s\1b.",
		noSearchResultsInSubBoardText: "\1n\1h\1bNo messages were found in the area \1w%s\1b with the given search criteria.",
		msgScanCompleteText: "\1n\1h\1cM\1n\1cessage scan complete\1h\1g.\1n",
		invalidMsgNumText: "\1n\1y\1hInvalid message number: %d",
		readMsgNumPromptText: "\1n\1g\1h\1i* \1n\1cRead message #: \1h",
		msgHasBeenDeletedText: "\1n\1h\1g* \1yMessage #\1w%d \1yhas been deleted.",
		noKludgeLinesForThisMsgText: "\1n\1h\1yThere are no kludge lines for this message.",
		searchingPersonalMailText: "\1w\1hSearching personal mail\1n",
		searchTextPromptText: "\1cEnter the search text\1g\1h:\1n\1c ",
		fromNamePromptText: "\1cEnter the 'from' name to search for\1g\1h:\1n\1c ",
		toNamePromptText: "\1cEnter the 'to' name to search for\1g\1h:\1n\1c ",
		abortedText: "\1n\1y\1h\1iAborted\1n",
		loadingPersonalMailText: "\1n\1cLoading %s...",
		msgDelConfirmText: "\1n\1h\1yDelete\1n\1c message #\1h%d\1n\1c: Are you sure",
		delSelectedMsgsConfirmText: "\1n\1h\1yDelete selected messages: Are you sure",
		msgDeletedText: "\1n\1cMessage #\1h%d\1n\1c has been marked for deletion.",
		selectedMsgsDeletedText: "\1n\1cSelected messages have been marked for deletion.",
		cannotDeleteMsgText_notYoursNotASysop: "\1n\1h\1wCannot delete message #\1y%d \1wbecause it's not yours or you're not a sysop.",
		cannotDeleteMsgText_notLastPostedMsg: "\1n\1h\1g* \1yCannot delete message #%d. You can only delete your last message in this area.\1n",
		cannotDeleteAllSelectedMsgsText: "\1n\1y\1h* Cannot delete all selected messages",
		msgEditConfirmText: "\1n\1cEdit message #\1h%d\1n\1c: Are you sure",
		noPersonalEmailText: "\1n\1cYou have no messages.",
		postOnSubBoard: "\1n\1gPost on %s %s"
	};


	// These two variables keep track of whether we're doing a message scan that spans
	// multiple sub-boards so that the enhanced reader function can enable use of
	// the > key to go to the next sub-board.
	this.doingMultiSubBoardScan = false;

	// An option for using the scrollable interface for messages with ANSI
	// content - The sysop can set this to false if the sysop thinks the
	// scrolling ANSI interface (using frame.js and scrollbar.js) doesn't
	// look good enough
	this.useScrollingInterfaceForANSIMessages = true;

	// Whether or not to pause (with a message) after doing a new message scan
	this.pauseAfterNewMsgScan = true;

nightfox's avatar
nightfox committed
	// For the message area chooser header filename & maximum number of
	// area chooser header lines to display
	this.areaChooserHdrFilenameBase = "areaChgHeader";
	this.areaChooserHdrMaxLines = 5;
	
	// Some key bindings for enhanced reader mode
	this.enhReaderKeys = {
		reply: "R",
		privateReply: "I",
		editMsg: "E",
		showHelp: "?",
		postMsg: "P",
		nextMsg: KEY_RIGHT,
		previousMsg: KEY_LEFT,
		firstMsg: "F",
		lastMsg: "L",
		showKludgeLines: "K",
		showHdrInfo: "H",
		showMsgList: "M",
		chgMsgArea: "C",
		userEdit: "U",
		quit: "Q",
		prevMsgByTitle: "<",
		nextMsgByTitle: ">",
		prevMsgByAuthor: "{",
		nextMsgByAuthor: "}",
		prevMsgByToUser: "[",
		nextMsgByToUser: "]",
		prevMsgByThreadID: "(",
		nextMsgByThreadID: ")",
		prevSubBoard: "-",
		nextSubBoard: "+",
		downloadAttachments: CTRL_A,
		saveToBBSMachine: CTRL_S,
		deleteMessage: KEY_DEL,
		selectMessage: " ",
		batchDelete: CTRL_D,
		forwardMsg: "O",
		vote: "V",
		bypassSubBoardInNewScan: "B",
		threadView: "*" // TODO: Implement this
		this.enhReaderKeys.validateMsg = "A";
nightfox's avatar
nightfox committed

	// Whether or not to display avatars
	this.displayAvatars = true;
	// Message list sort option
	this.msgListSort = MSG_LIST_SORT_DATETIME_RECEIVED;

	this.cfgFilename = "DDMsgReader.cfg";
	// Check the command-line arguments for a custom configuration file name
	// before reading the configuration file.
	var scriptArgsIsValid = (typeof(pScriptArgs) == "object");
	if (scriptArgsIsValid && pScriptArgs.hasOwnProperty("configfilename"))
		this.cfgFilename = pScriptArgs["configfilename"];
	// Read the settings from the config file
	this.cfgFileSuccessfullyRead = false;
	this.ReadConfigFile();
	// Set any other values specified by the command-line parameters
	// Reader start mode - Read or list mode
	if (scriptArgsIsValid)
	{
		if (pScriptArgs.hasOwnProperty("startmode"))
		{
			var readerStartMode = readerModeStrToVal(pScriptArgs["startmode"]);
			if (readerStartMode != -1)
				this.startMode = readerStartMode;
		}
		// Search mode
		if (pScriptArgs.hasOwnProperty("search"))
		{
			var searchType = searchTypeStrToVal(pScriptArgs["search"]);
			if (searchType != SEARCH_NONE)
				this.searchType = searchType;
		}
	}
	// Color value adjusting (must be done after reading the config file in case
	// the color settings were changed from defaults)
	// Message list highlight colors: For each (except for the background),
	// prepend the normal attribute and append the background attribute to the end.
	// This is to ensure that high attributes don't affect the rest of the line and
	// the background attribute stays for the rest of the line.
	this.colors.msgListMsgNumHighlightColor = "\1n" + this.colors.msgListMsgNumHighlightColor + this.colors.msgListHighlightBkgColor;
	this.colors.msgListFromHighlightColor = "\1n" + this.colors.msgListFromHighlightColor + this.colors.msgListHighlightBkgColor;
	this.colors.msgListToHighlightColor = "\1n" + this.colors.msgListToHighlightColor + this.colors.msgListHighlightBkgColor;
	this.colors.msgListSubjHighlightColor = "\1n" + this.colors.msgListSubjHighlightColor + this.colors.msgListHighlightBkgColor;
	this.colors.msgListDateHighlightColor = "\1n" + this.colors.msgListDateHighlightColor + this.colors.msgListHighlightBkgColor;
	this.colors.msgListTimeHighlightColor = "\1n" + this.colors.msgListTimeHighlightColor + this.colors.msgListHighlightBkgColor;
	// Similar for the area chooser lightbar highlight colors
	this.colors.areaChooserMsgAreaNumHighlightColor = "\1n" + this.colors.areaChooserMsgAreaNumHighlightColor + this.colors.areaChooserMsgAreaBkgHighlightColor;
	this.colors.areaChooserMsgAreaDescHighlightColor = "\1n" + this.colors.areaChooserMsgAreaDescHighlightColor + this.colors.areaChooserMsgAreaBkgHighlightColor;
	this.colors.areaChooserMsgAreaDateHighlightColor = "\1n" + this.colors.areaChooserMsgAreaDateHighlightColor + this.colors.areaChooserMsgAreaBkgHighlightColor;
	this.colors.areaChooserMsgAreaTimeHighlightColor = "\1n" + this.colors.areaChooserMsgAreaTimeHighlightColor + this.colors.areaChooserMsgAreaBkgHighlightColor;
	this.colors.areaChooserMsgAreaNumItemsHighlightColor = "\1n" + this.colors.areaChooserMsgAreaNumItemsHighlightColor + this.colors.areaChooserMsgAreaBkgHighlightColor;
	// Similar for the enhanced reader help line colors
	this.colors.enhReaderHelpLineGeneralColor = "\1n" + this.colors.enhReaderHelpLineGeneralColor + this.colors.enhReaderHelpLineBkgColor;
	this.colors.enhReaderHelpLineHotkeyColor = "\1n" + this.colors.enhReaderHelpLineHotkeyColor + this.colors.enhReaderHelpLineBkgColor;
	this.colors.enhReaderHelpLineParenColor = "\1n" + this.colors.enhReaderHelpLineParenColor + this.colors.enhReaderHelpLineBkgColor;
	// Similar for the lightbar message list help line colors
	this.colors.lightbarMsgListHelpLineGeneralColor = "\1n" + this.colors.lightbarMsgListHelpLineGeneralColor + this.colors.lightbarMsgListHelpLineBkgColor;
	this.colors.lightbarMsgListHelpLineHotkeyColor = "\1n" + this.colors.lightbarMsgListHelpLineHotkeyColor + this.colors.lightbarMsgListHelpLineBkgColor;
	this.colors.lightbarMsgListHelpLineParenColor = "\1n" + this.colors.lightbarMsgListHelpLineParenColor + this.colors.lightbarMsgListHelpLineBkgColor;
	// Similar for the lightbar area chooser help line colors
	this.colors.lightbarAreaChooserHelpLineGeneralColor = "\1n" + this.colors.lightbarAreaChooserHelpLineGeneralColor + this.colors.lightbarAreaChooserHelpLineBkgColor;
	this.colors.lightbarAreaChooserHelpLineHotkeyColor = "\1n" + this.colors.lightbarAreaChooserHelpLineHotkeyColor + this.colors.lightbarAreaChooserHelpLineBkgColor;
	this.colors.lightbarAreaChooserHelpLineParenColor = "\1n" + this.colors.lightbarAreaChooserHelpLineParenColor + this.colors.lightbarAreaChooserHelpLineBkgColor;
	// Prepend most of the text strings with the normal attribute (if they don't
	// have it already) to make sure the correct colors are used.
	for (var prop in this.text)
	{
		if ((prop != "scrollbarBGChar") && (prop != "scrollbarScrollBlockChar"))
		{
			if ((this.text[prop].length > 0) && (this.text[prop].charAt(0) != "\1n"))
				this.text[prop] = "\1n" + this.text[prop];
		}
	}

	// this.tabReplacementText will be the text that tabs will be replaced
	// with in enhanced reader mode
	this.tabReplacementText = format("%" + this.numTabSpaces + "s", "");

	// Calculate the message list widths and format strings based on the current
	// sub-board code and color settings.  Start with a message # field length
	// of 4 characters.  This will be re-calculated later after message headers
	// are loaded.
	this.RecalcMsgListWidthsAndFormatStrs(4);

	// If the user's terminal doesn't support ANSI, then append a newline to
	// the end of the format string (we won't be able to move the cursor).
	if (!canDoHighASCIIAndANSI())
	{
		this.sMsgInfoFormatStr += "\r\n";
		this.sMsgInfoToUserFormatStr += "\r\n";
		this.sMsgInfoFromUserFormatStr += "\r\n";
		this.sMsgInfoFormatHighlightStr += "\r\n";
	}
	// Enhanced reader help line (will be set up in
	// DigDistMsgReader_SetEnhancedReaderHelpLine())
	this.enhReadHelpLine = "";
	// This array will store object with x and y coordinates for mouse click locations
	// for the enhanced reader help line, as well as a string describing the action.
	this.enhReadHelpLineClickCoords = [];

	// Read the enhanced message header file and populate this.enhMsgHeaderLines,
	// the header text for enhanced reader mode.  The enhanced reader header file
	// name will start with 'enhMsgHeader', and there can be multiple versions for
	// different terminal widths (i.e., msgHeader_80.ans for an 80-column console
	// and msgHeader_132 for a 132-column console).
	this.enhMsgHeaderLines = loadTextFileIntoArray("enhMsgHeader", 10);
	// this.enhMsgHeaderLinesToReadingUser will be a copy of this.endMsgReaderLines
	// but with the 'To' user line changed to highlight the name for messages to
	// the logged-on reading user
	this.enhMsgHeaderLinesToReadingUser = [];
	// If the header file didn't exist, then populate the enhanced reader header
	// array with default lines.
	this.usingInternalEnhMsgHdr = (this.enhMsgHeaderLines.length == 0);
	if (this.usingInternalEnhMsgHdr)
	{
		// Group name: 20% of console width
		// Sub-board name: 34% of console width
		var msgGrpNameLen = Math.floor(console.screen_columns * 0.2);
		var subBoardNameLen = Math.floor(console.screen_columns * 0.34);
		var hdrLine1 = "\1n\1h\1c" + UPPER_LEFT_SINGLE + HORIZONTAL_SINGLE + "\1n\1c"
		             + HORIZONTAL_SINGLE + " \1h@GRP-L";
		var numChars = msgGrpNameLen - 7;
		for (var i = 0; i < numChars; ++i)
			hdrLine1 += "#";
		hdrLine1 += "@ @SUB-L";
		numChars = subBoardNameLen - 7;
		for (var i = 0; i < numChars; ++i)
			hdrLine1 += "#";
		hdrLine1 += "@\1k";
		numChars = console.screen_columns - console.strlen(hdrLine1) - 4;
		for (var i = 0; i < numChars; ++i)
			hdrLine1 += HORIZONTAL_SINGLE;
		hdrLine1 += "\1n\1c" + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h"
		         + HORIZONTAL_SINGLE + UPPER_RIGHT_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine1);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine1);
		var hdrLine2 = "\1n\1c" + VERTICAL_SINGLE + "\1h\1k" + BLOCK1 + BLOCK2
		             + BLOCK3 + "\1gM\1n\1gsg#\1h\1c: " + this.colors.msgHdrMsgNumColor + "@MSG_NUM_AND_TOTAL-L";
		numChars = console.screen_columns - 32;
		for (var i = 0; i < numChars; ++i)
			hdrLine2 += "#";
		hdrLine2 += "@\1n\1c" + VERTICAL_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine2);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine2);
		var hdrLine3 = "\1n\1h\1k" + VERTICAL_SINGLE + BLOCK1 + BLOCK2 + BLOCK3
					 + "\1gF\1n\1grom\1h\1c: " + this.colors.msgHdrFromColor + "@MSG_FROM_AND_FROM_NET-L";
		numChars = console.screen_columns - 36;
		for (var i = 0; i < numChars; ++i)
			hdrLine3 += "#";
		hdrLine3 += "@\1k" + VERTICAL_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine3);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine3);
		this.enhMsgHeaderLines.push(genEnhHdrToUserLine(this.colors, false));
		this.enhMsgHeaderLinesToReadingUser.push(genEnhHdrToUserLine(this.colors, true));
		var hdrLine5 = "\1n\1h\1k" + VERTICAL_SINGLE + BLOCK1 + BLOCK2 + BLOCK3
		             + "\1gS\1n\1gubj\1h\1c: " + this.colors.msgHdrSubjColor + "@MSG_SUBJECT-L";
		numChars = console.screen_columns - 26;
		for (var i = 0; i < numChars; ++i)
			hdrLine5 += "#";
		hdrLine5 += "@\1k" + VERTICAL_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine5);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine5);
		var hdrLine6 = "\1n\1c" + VERTICAL_SINGLE + "\1h\1k" + BLOCK1 + BLOCK2 + BLOCK3
		             + "\1gD\1n\1gate\1h\1c: " + this.colors.msgHdrDateColor + "@MSG_DATE-L";
		//numChars = console.screen_columns - 23;
		numChars = console.screen_columns - 67;
		for (var i = 0; i < numChars; ++i)
			hdrLine6 += "#";
		//hdrLine6 += "@\1n\1c" + VERTICAL_SINGLE;
		hdrLine6 += "@ @MSG_TIMEZONE@\1n";
		for (var i = 0; i < 40; ++i)
			hdrLine6 += " ";
		hdrLine6 += "\1n\1c" + VERTICAL_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine6);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine6);
		var hdrLine7 = "\1n\1h\1c" + BOTTOM_T_SINGLE + HORIZONTAL_SINGLE + "\1n\1c"
		             + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h\1k";
		numChars = console.screen_columns - 8;
		for (var i = 0; i < numChars; ++i)
			hdrLine7 += HORIZONTAL_SINGLE;
		hdrLine7 += "\1n\1c" + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h"
		         + HORIZONTAL_SINGLE + BOTTOM_T_SINGLE;
		this.enhMsgHeaderLines.push(hdrLine7);
		this.enhMsgHeaderLinesToReadingUser.push(hdrLine7);
	}
	else
	{
		// We loaded the enhanced message header lines from a custom file.
		// Copy from this.enhMsgHeaderLines to this.enhMsgHeaderLinesToReadingUser
		// but change any 'To:' line to highlight the 'to' username.
		this.enhMsgHeaderLinesToReadingUser = this.enhMsgHeaderLines.slice();
		// Go through the header lines and ensure the 'To' line has a different
		// color
		for (var lineIdx = 0; lineIdx < this.enhMsgHeaderLinesToReadingUser.length; ++lineIdx)
			this.enhMsgHeaderLinesToReadingUser[lineIdx] = syncAttrCodesToANSI(strWithToUserColor(this.enhMsgHeaderLinesToReadingUser[lineIdx], this.colors.msgHdrToUserColor));
	// Save the enhanced reader header width.  This will be the length of the longest
	// line in the header.
	this.enhMsgHeaderWidth = 0;
	if (this.enhMsgHeaderLines.length > 0)
	{
		var lineLen = 0;
		for (var i = 0; i < this.enhMsgHeaderLines.length; ++i)
		{
			lineLen = console.strlen(this.enhMsgHeaderLines[i]);
			if (lineLen > this.enhMsgHeaderWidth)
				this.enhMsgHeaderWidth = lineLen;
		}
	}

	// Message display area information
	this.msgAreaTop = this.enhMsgHeaderLines.length + 1;
	this.msgAreaBottom = console.screen_rows-1;  // The last line of the message area
	// msgAreaLeft and msgAreaRight are the rightmost and leftmost columns of the
	// message area, respectively.  These are 1-based.  1 is subtracted from
	// msgAreaRight to leave room for the scrollbar in enhanced reader mode.
	this.msgAreaLeft = 1;
	this.msgAreaRight = console.screen_columns - 1;
	this.msgAreaWidth = this.msgAreaRight - this.msgAreaLeft + 1;
	this.msgAreaHeight = this.msgAreaBottom - this.msgAreaTop + 1;

	//////////////////////////////////////////////
	// Things related to changing to a different message group & sub-board

	// In the message area lists (for changing to another message area), the
	// date & time of the last-imported message will be shown.
	// msgAreaList_lastImportedMsg_showImportTime is a boolean to specify
	// whether or not to use the import time for the last-imported message.
	// If false, the message written time will be used.
	this.msgAreaList_lastImportedMsg_showImportTime = true;

	// These variables store the lengths of the various columns displayed in
	// the message group/sub-board lists.
	// Sub-board info field lengths
	this.areaNumLen = 4;
	this.numItemsLen = 4;
	this.dateLen = 10; // i.e., YYYY-MM-DD
	this.timeLen = 8;  // i.e., HH:MM:SS
	// Sub-board name length - This should be 47 for an 80-column display.
	this.subBoardNameLen = console.screen_columns - this.areaNumLen - this.numItemsLen - this.dateLen - this.timeLen - 7;
	// Message group description length (67 chars on an 80-column screen)
	this.msgGrpDescLen = console.screen_columns - this.areaNumLen - this.numItemsLen - 5;

	// Some methods for choosing the message area
	this.WriteChgMsgAreaKeysHelpLine = DigDistMsgReader_WriteLightbarChgMsgAreaKeysHelpLine;
	this.WriteGrpListHdrLine1 = DigDistMsgReader_WriteGrpListTopHdrLine1;
	this.WriteSubBrdListHdrLine = DigDistMsgReader_WriteSubBrdListHdrLine;
	this.SelectMsgArea = DigDistMsgReader_SelectMsgArea;
	this.SelectMsgArea_Lightbar = DigDistMsgReader_SelectMsgArea_Lightbar;
	this.SelectMsgArea_Traditional = DigDistMsgReader_SelectMsgArea_Traditional;
	this.ListMsgGrps = DigDistMsgReader_ListMsgGrps_Traditional;
	this.ListSubBoardsInMsgGroup = DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional;
	// Lightbar-specific methods
	this.WriteMsgGroupLine = DigDistMsgReader_writeMsgGroupLine;
	this.UpdateMsgAreaPageNumInHeader = DigDistMsgReader_updateMsgAreaPageNumInHeader;
	this.GetMsgSubBoardLine = DigDistMsgReader_GetMsgSubBrdLine;
	// Choose Message Area help screen
	this.ShowChooseMsgAreaHelpScreen = DigDistMsgReader_showChooseMsgAreaHelpScreen;
	// Method to build the sub-board printf information for a message
	// group
	this.BuildSubBoardPrintfInfoForGrp = DigDistMsgReader_BuildSubBoardPrintfInfoForGrp;
	// Methods for calculating a page number for a message list item
	this.CalcTraditionalMsgListTopIdx = DigDistMsgReader_CalcTraditionalMsgListTopIdx;
	this.CalcLightbarMsgListTopIdx = DigDistMsgReader_CalcLightbarMsgListTopIdx;
	this.CalcMsgListScreenIdxVarsFromMsgNum = DigDistMsgReader_CalcMsgListScreenIdxVarsFromMsgNum;
	// A method for validating a user's choice of message area
	this.ValidateMsgAreaChoice = DigDistMsgReader_ValidateMsgAreaChoice;
	this.ValidateMsg = DigDistMsgReader_ValidateMsg;
	this.GetGroupNameAndDesc = DigDistMsgReader_GetGroupNameAndDesc;

	// printf strings for message group/sub-board lists
	// Message group information (printf strings)
	this.msgGrpListPrintfStr = "\1n " + this.colors.areaChooserMsgAreaNumColor + "%" + this.areaNumLen
	                         + "d " + this.colors.areaChooserMsgAreaDescColor + "%-"
	                         + this.msgGrpDescLen + "s " + this.colors.areaChooserMsgAreaNumItemsColor
	                         + "%" + this.numItemsLen + "d";
	this.msgGrpListHilightPrintfStr = "\1n" + this.colors.areaChooserMsgAreaBkgHighlightColor + " "
	                                + "\1n" + this.colors.areaChooserMsgAreaBkgHighlightColor
	                                + this.colors.areaChooserMsgAreaNumHighlightColor + "%" + this.areaNumLen
	                                + "d \1n" + this.colors.areaChooserMsgAreaBkgHighlightColor
	                                + this.colors.areaChooserMsgAreaDescHighlightColor + "%-"
	                                + this.msgGrpDescLen + "s \1n" + this.colors.areaChooserMsgAreaBkgHighlightColor
	                                + this.colors.areaChooserMsgAreaNumItemsHighlightColor + "%" + this.numItemsLen
	                                + "d";
	// Message group list header (printf string)
	this.msgGrpListHdrPrintfStr = this.colors.areaChooserMsgAreaHeaderColor + "%6s %-"
	                            + +(this.msgGrpDescLen-8) + "s %-12s";
	// Sub-board information header (printf string)
	this.subBoardListHdrPrintfStr = this.colors.areaChooserMsgAreaHeaderColor + " %5s %-"
	                              + +(this.subBoardNameLen-3) + "s %-7s %-19s";
	// Lightbar area chooser help line text
	this.lightbarAreaChooserHelpLine = "\1n"
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + ""
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + ""
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "HOME"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "END"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "#"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "PgUp"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + "/"
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "Dn"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "F"
	                          + this.colors.lightbarAreaChooserHelpLineParenColor + ")"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + "irst pg, "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "L"
	                          + this.colors.lightbarAreaChooserHelpLineParenColor + ")"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + "ast pg, "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "CTRL-F"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "/"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "N"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + ", "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "Q"
	                          + this.colors.lightbarAreaChooserHelpLineParenColor + ")"
	                          + this.colors.lightbarAreaChooserHelpLineGeneralColor + "uit, "
	                          + this.colors.lightbarAreaChooserHelpLineHotkeyColor + "?";
	// Pad the lightbar key help text on either side to center it on the screen
	// (but leave off the last character to avoid screen drawing issues)
	var textLen = strip_ctrl(this.lightbarAreaChooserHelpLine).length;
	var padLen = console.screen_columns - textLen - 1;
	var leftPadLen = Math.floor(padLen/2);
	var rightPadLen = padLen - leftPadLen - 2;
	this.lightbarAreaChooserHelpLine = this.colors.lightbarAreaChooserHelpLineGeneralColor
	                                 + format("%" + leftPadLen + "s", "")
	                                 + this.lightbarAreaChooserHelpLine
	                                 + this.colors.lightbarAreaChooserHelpLineGeneralColor
	                                 + format("%" + rightPadLen + "s", "") + "\1n";

	// this.subBoardListPrintfInfo will be an array of printf strings
	// for the sub-boards in the message groups.  The index is the
	// message group index.  The sub-board printf information is created
	// on the fly the first time the user lists sub-boards for a message
	// group.

	// Variables to save the top message index for the traditional & lightbar
	// message lists.  Initialize them to -1 to mean the message list hasn't been
	// displayed yet - In that case, the lister will use the user's last
	// read pointer.
	this.tradListTopMsgIdx = -1;
	this.tradMsgListNumLines = console.screen_rows-3;
	if (this.displayBoardInfoInHeader)
		this.tradMsgListNumLines -= 2;
	this.lightbarListTopMsgIdx = -1;
	this.lightbarMsgListNumLines = console.screen_rows-2;
	this.lightbarMsgListStartScreenRow = 2; // The first line number on the screen for the message list
	// If we will be displaying the message group and sub-board in the
	// header at the top of the screen (an additional 2 lines), then
	// update this.lightbarMsgListNumLines and this.lightbarMsgListStartScreenRow to
	// account for this.
	if (this.displayBoardInfoInHeader)
	{
		this.lightbarMsgListNumLines -= 2;
		this.lightbarMsgListStartScreenRow += 2;
	}
	// The selected message index for the lightbar message list (initially -1, will
	// be set in the lightbar list method)
	this.lightbarListSelectedMsgIdx = -1;
	// The selected message cursor position for the lightbar message list (initially
	// null, will be set in the lightbar list message)
	this.lightbarListCurPos = null;

	// selectedMessages will be an object (indexed by sub-board internal code)
	// containing objects that contain message indexes (as properties) for the
	// sub-boards.  Messages can be selected by the user for doing things such
	// as a batch delete, etc.
nightfox's avatar
nightfox committed

	// areaChangeHdrLines is an array of text lines to use as a header to display
	// above the message area changer lists.
	this.areaChangeHdrLines = loadTextFileIntoArray(this.areaChooserHdrFilenameBase, this.areaChooserHdrMaxLines);

	// pausePromptText is the text that will be used for some of the pause
	// prompts.  It's loaded from text.dat, but in case that text contains
	// "@EXEC:" (to execute a script), this script will default to a "press
	// a key" message.
	this.pausePromptText = bbs.text(Pause);
	if (this.pausePromptText.toUpperCase().indexOf("@EXEC:") > -1)
		this.pausePromptText = "\1n\1c[ Press a key ] ";
}

// For the DigDistMsgReader class: Sets the subBoardCode property and also
// sets the readingPersonalEmail property, a boolean for whether or not
// personal email is being read (whether the sub-board code is "mail")
//
// Parameters:
//  pSubCode: The sub-board code to set in the object
function DigDistMsgReader_SetSubBoardCode(pSubCode)
{
	this.subBoardCode = pSubCode;
	this.readingPersonalEmail = (this.subBoardCode.toLowerCase() == "mail");
// For the DigDistMsgReader class: Populates the hdrsForCurrentSubBoard
// array with message headers from the current sub-board.  Filters out
// messages that are deleted, unvalidated, private, and voting messages.
function DigDistMsgReader_PopulateHdrsForCurrentSubBoard()
{
	if (this.subBoardCode == "mail")
		this.hdrsForCurrentSubBoardByMsgNum = {};
	var msgbase = new MsgBase(this.subBoardCode);
	if (msgbase.open())
		// First get all headers in a temporary array, then filter them into
		// this.hdrsForCurrentSubBoard.
		// If get_all_msg_headers exists as a function, then use it.  Otherwise,
		// iterate through all message offsets and get the headers.
		if (typeof(msgbase.get_all_msg_headers) === "function")
			// The first parameter is whether to include votes (the parameter was introduced in Synchronet 3.17+).
			// We used to pass false here.
			tmpHdrs = msgbase.get_all_msg_headers(true);
			var numMsgs = msgbase.total_msgs;
			for (var msgIdx = 0; msgIdx < numMsgs; ++msgIdx)
			{
				msgHdr = msgbase.get_msg_header(true, msgIdx, expandFields);
				tmpHdrs.push(msgHdr);

	// Filter the headers into this.hdrsForCurrentSubBoard
	this.FilterMsgHdrsIntoHdrsForCurrentSubBoard(tmpHdrs, true);
}

// For the DigDistMsgReader class: Takes an array of message headers in the current
// sub-board and filters them into this.hdrsForCurrentSubBoard and
// this.hdrsForCurrentSubBoardByMsgNum based on which messages are readable to the
// user.
//
// Parameters:
//  pMsgHdrs: An array/object of message header objects
//  pClearFirst: Boolean - Whether or not to empty this.hdrsForCurrentSubBoard
//               and this.hdrsForCurrentSubBoardByMsgNum first.
function DigDistMsgReader_FilterMsgHdrsIntoHdrsForCurrentSubBoard(pMsgHdrs, pClearFirst)
{
	if (pClearFirst)
	{
		this.hdrsForCurrentSubBoard = [];
		this.hdrsForCurrentSubBoardByMsgNum = {};
		// Only add the message header if the message is readable to the user.
		// this.hdrsForCurrentSubBoardByMsgNum also has to be populated, but
		// that's done later in this function, in case this.hdrsForCurrentSubBoard
		// needs to be sorted.
		if (isReadableMsgHdr(pMsgHdrs[prop], this.subBoardCode))
		{
			this.hdrsForCurrentSubBoard.push(pMsgHdrs[prop]);
			// This isn't done right here anymore due to the possibility of
			// this.hdrsForCurrentSubBoard being sorted
			//this.hdrsForCurrentSubBoardByMsgNum[pMsgHdrs[prop].number] = this.hdrsForCurrentSubBoard.length - 1;

	// If the sort type is date/time written, then sort the message header
	// array as such
	if (this.msgListSort == MSG_LIST_SORT_DATETIME_WRITTEN)
		this.hdrsForCurrentSubBoard.sort(sortMessageHdrsByDateTime);

	// Populate this.hdrsForCurrentSubBoardByMsgNum (this needs to be done here
	// based on the order of this.hdrsForCurrentSubBoard)
	for (var idx = 0; idx < this.hdrsForCurrentSubBoard.length; ++idx)
		this.hdrsForCurrentSubBoardByMsgNum[this.hdrsForCurrentSubBoard[idx].number] = idx;
}

// For the DigDistMsgReader class: Gets the message offset (index) for a message, given
// a message header.  Returns -1 on failure.  The returned index is for the object's
// message header array(s), if populated, in the priority of search headers, then
// hdrsForCurrentSubBoard.  If neither of those are populated, the offset of the header
// in the messagebase will be returned.
//
// Parameters:
//  pHdrOrMsgNum: Can either be a message header object or a message number.
//
// Return value: The message index (or offset in the messagebase)
function DigDistMsgReader_GetMsgIdx(pHdrOrMsgNum)
{
	if (typeof(pHdrOrMsgNum) == "object")
		msgNum = pHdrOrMsgNum.number;
	else if (typeof(pHdrOrMsgNum) == "number")
		msgNum = pHdrOrMsgNum;
	else
	var msgIdx = 0;
	if (this.msgSearchHdrs.hasOwnProperty(this.subBoardCode) &&
	    (this.msgSearchHdrs[this.subBoardCode].indexed.length > 0))
	{
		for (var i = 0; i < this.msgSearchHdrs[this.subBoardCode].indexed.length; ++i)
		{
			if (this.msgSearchHdrs[this.subBoardCode].indexed[i].number == msgNum)
			{
				msgIdx = i;
				break;
			}
		}
	}
	else if (this.hdrsForCurrentSubBoard.length > 0)
		if (this.hdrsForCurrentSubBoardByMsgNum.hasOwnProperty(msgNum))
			msgIdx = this.hdrsForCurrentSubBoardByMsgNum[msgNum];
		else
		{
			msgIdx = msgNumToIdxFromMsgbase(this.subBoardCode, msgNum);
			if (msgIdx != -1)
				this.hdrsForCurrentSubBoardByMsgNum[msgNum] = msgIdx;
		}
		msgIdx = msgNumToIdxFromMsgbase(this.subBoardCode, msgNum);
	return msgIdx;
}

// Given a sub-board code and message number, this function gets the index
// of that message from the Synchronet messagebase.  Returns -1 if not found.
//
// Parameters:
//  pSubCode: The sub-board code
//  pMsgNum: The message number
//
// Return value: The index of the message, or -1 if not found.
function msgNumToIdxFromMsgbase(pSubCode, pMsgNum)
{
	var msgIdx = -1;

	var msgbase = new MsgBase(pSubCode);
	if (msgbase.open())
		var msgHdr =  msgbase.get_msg_header(false, pMsgNum, false);
		if (msgHdr != null)
			msgIdx = msgHdr.offset;
		msgbase.close();
	return msgIdx;
}

// For the DigDistMsgReader class: Refreshes a message header in the message header
// arrays in this.msgSearchHdrs.
//
// Parameters:
//  pMsgIndex: The index (0-based) of the message header
//  pAttrib: Optional - An attribute to apply.  If this is is not specified,
//           then the message header will be retrieved from the message base.
// pSubBoardCode: Optional - An internal sub-board code.  If not specified, then
//                this method will default to this.subBoardCode.
function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoardCode)
	var subCode = (typeof(pSubBoardCode) == "string" ? pSubBoardCode : this.subBoardCode);
	var msgbase = new MsgBase(subCode);
	if (msgbase.open())
		if (this.msgSearchHdrs.hasOwnProperty(subCode))
			var msgNum = pMsgIndex + 1;
			if (typeof(pAttrib) != "undefined")
				if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex))
				{
					this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr | pAttrib;
					var msgOffsetFromHdr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].offset;
					msgbase.put_msg_header(true, msgOffsetFromHdr, this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex]);
				}
				var msgHeader = this.GetMsgHdrByIdx(pMsgIndex);
				if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex))
				{
					this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex] = msgHeader;
					msgbase.put_msg_header(true, msgHeader.offset, msgHeader);
				}
// For the DigDistMsgReader class: Refreshes a message header in the message header
// array for the current sub-board.
//
// Parameters:
//  pMsgIndex: The index (0-based) of the message header
//  pAttrib: Optional - An attribute to apply.  If this is is not specified,
//           then the message header will be retrieved from the message base.
function DigDistMsgReader_RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib)
{
	if (typeof(pMsgIndex) != "number")
		return;

	if ((pMsgIndex >= 0) && (pMsgIndex < this.hdrsForCurrentSubBoard.length))
		this.hdrsForCurrentSubBoard[pMsgIndex].attr = this.hdrsForCurrentSubBoard[pMsgIndex].attr | pAttrib;
}

// For the DigDistMsgReader class: Refreshes a message header in the saved message
// header arrays.
//
// Parameters:
//  pMsgIndex: The index (0-based) of the message header
//  pAttrib: Optional - An attribute to apply.  If this is is not specified,
//           then the message header will be retrieved from the message base.
// pSubBoardCode: Optional - An internal sub-board code.  If not specified, then
//                this method will default to this.subBoardCode.
function DigDistMsgReader_RefreshHdrInSavedArrays(pMsgIndex, pAttrib, pSubBoardCode)
{
	this.RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoardCode);
	this.RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib);
}

// For the DigDistMsgReader class: Inputs search text from the user, then reads/lists
// messages, which will perform the search.
//
// Paramters:
//  pSearchModeStr: A string to specify the lister mode to use - This can
//                  be one of the search modes to specify how to search:
//                  "keyword_search": Search the message subjects & bodies by keyword
//                  "from_name_search": Search messages by from name
//                  "to_name_search": Search messages by to name
//                  "to_user_search": Search messages by to name, to the logged-in user
//  pSubBoardCode: Optional - The Synchronet sub-board code, or "mail"
//                 for personal email.  Or, this can be the a boolean false for scan
//                 search mode to scan through sub-boards while searching each of them.
//  pScanScopeChar: Optional string with a character specifying "A" to scan all sub-boards,
//                  "G" for the current message group, or "S" for the user's current sub-board.
//                  If this is not specified, the current sub-board will be used.
//  pTxtToSearch: Optional - Text to search for (if specified, this won't prompt the user for search text)
function DigDistMsgReader_SearchMessages(pSearchModeStr, pSubBoardCode, pScanScopeChar, pTxtToSearch)
	var searchTextProvided = (typeof(pTxtToSearch) === "string" && pTxtToSearch != "");
	// Convert the search mode string to an integer representing the search
	// mode.  If we get back -1, that means the search mode string was invalid.
	// If that's the case, simply list messages.  Otherwise, do the search.
	this.searchType = searchTypeStrToVal(pSearchModeStr);
	if (this.searchType == SEARCH_NONE) // No search; search mode string was invalid
	{
		// Clear the search information and read/list messages.
		this.ClearSearchData();
		this.ReadOrListSubBoard(pSubBoardCode);
	}
	else
	{
		// The search mode string was valid, so go ahead and search.
		console.print("\1n");
		console.crlf();
		var subCode = "";
		if (typeof(pScanScopeChar) !== "string")
		{
			subCode = (typeof(pSubBoardCode) === "string" ? pSubBoardCode : this.subBoardCode);
			if (subCode == "mail")
				console.print("\1n" + replaceAtCodesInStr(this.text.searchingPersonalMailText));
			else
			{
				var formattedText = format(this.text.searchingSubBoardAbovePromptText, subBoardGrpAndName(bbs.cursub_code));
				console.print("\1n" + replaceAtCodesInStr(formattedText) + "\1n");
			}
			console.crlf();
		}
		// Output the prompt text to the user (for modes where a prompt is needed)
		switch (this.searchType)
		{
				if (!searchTextProvided)
					console.print("\1n" + replaceAtCodesInStr(this.text.searchTextPromptText));
				else
					console.print("\1n\1gSearching for: \1c" + pTxtToSearch + "\1n\r\n");
				if (!searchTextProvided)
					console.print("\1n" + replaceAtCodesInStr(this.text.fromNamePromptText));
				else
					console.print("\1n\1gSearching for: \1c" + pTxtToSearch + "\1n\r\n");
				if (!searchTextProvided)
					console.print("\1n" +replaceAtCodesInStr(this.text.toNamePromptText));
				else
					console.print("\1n\1gSearching for: \1c" + pTxtToSearch + "\1n\r\n");
				break;
			case SEARCH_TO_USER_CUR_MSG_AREA:
				// Note: No prompt needed for this - Will search for the user's name/handle
				console.line_counter = 0; // To prevent a pause before the message list comes up
				break;
			default:
				break;
		}
		//var promptUserForText = this.SearchTypePopulatesSearchResults();
		var promptUserForText = this.SearchTypeRequiresSearchText();
		// Get the search text from the user
		if (promptUserForText)
		{
			if (searchTextProvided)
				this.searchString = pTxtToSearch;
			else
				this.searchString = console.getstr(512, K_UPPER);
		}
		// If the user was prompted for search text but no search text was entered,
		// then show an abort message and don't do anything.  Otherwise, go ahead
		// and list/read messages.
		if (promptUserForText && (this.searchString.length == 0))
		{
			this.ClearSearchData();
			console.print("\1n" + replaceAtCodesInStr(this.text.abortedText));
			// If pScanScopeChar is a string, then do scan search mode.  Otherwise,
			// scan/search the current sub-board.
			if (typeof(pScanScopeChar) === "string" && (pScanScopeChar === "S" || pScanScopeChar === "G" || pScanScopeChar === "A"))
			{
				var subBoardCodeBackup = this.subBoardCode;
				var subBoardsToScan = getSubBoardsToScanArray(pScanScopeChar);
				this.doingMsgScan = true;
				var continueScan = true;
				var userAborted = false;
				this.doingMultiSubBoardScan = (subBoardsToScan.length > 1);
				// If the sub-board's access requirements allows the user to read it
				// and it's enabled in the user's message scan configuration, then go
				// ahead with this sub-board.
				// Note: Used to use this to determine whether the user could access the
				// sub-board:
				//user.compare_ars(msg_area.grp_list[grpIndex].sub_list[subIndex].ars)
				// Now using the can_read property.
				for (var subCodeIdx = 0; (subCodeIdx < subBoardsToScan.length) && continueScan; ++subCodeIdx)
				{
					subCode = subBoardsToScan[subCodeIdx];
					if (msg_area.sub[subCode].can_read && ((msg_area.sub[subCode].scan_cfg & SCAN_CFG_NEW) == SCAN_CFG_NEW))
					{
						// Force garbage collection to ensure enough memory is available to continue
						js.gc(true);
						// Set the console line counter to 0 to prevent screen pausing
						// when the "Searching ..." and "No messages were found" text is
						// displayed repeatedly
						console.line_counter = 0;
						// let the user read the sub-board (and toggle betweeen reading and
						// listing)
						var readOrListRetObj = this.ReadOrListSubBoard(subCode, null, true, true, false, true, READER_MODE_READ);
						console.print("\1n");
						console.crlf();
						//if (this.SearchTypePopulatesSearchResults())
						//	console.print("\1n\r\nSearching...");
						console.line_counter = 0;
						if (readOrListRetObj.stoppedReading)
							break;
					}
				}
				this.subBoardCode = subBoardCodeBackup;
			// Clear the search data so that subsequent listing or reading sessions
			// don't repeat the same search
			this.ClearSearchData();
		}
	}
// For the DigDistMsgReader class: Performs a message search scan through sub-boards.
// Prompts the user for Sub-board/Group/All, then inputs search text from the user, then
// reads/lists messages through the sub-boards, performing the search in each sub-board.
//
// Paramters:
//  pSearchModeStr: A string to specify the lister mode to use - This can
//                  be one of the search modes to specify how to search:
//                  "keyword_search": Search the message subjects & bodies by keyword
//                  "from_name_search": Search messages by from name
//                  "to_name_search": Search messages by to name
//                  "to_user_search": Search messages by to name, to the logged-in user
//  pTxtToSearch: Optional - Text to search for (if specified, this won't prompt the user for search text)
//  pSubCode: Optional - An internal code of a sub-board if scanning just one sub-board
function DigDistMsgReader_SearchMsgScan(pSearchModeStr, pTxtToSearch, pSubCode)
{
	if (typeof(pSearchModeStr) !== "string" || pSearchModeStr.length == 0)
		return;

	// If the given sub-board code is valid, then use that and scan only in that
	// sub-board.  Otherwise, prompt the user for sub-board, group, or all, then
	// call SearchMessages to do the search.
	var scanScopeChar = "";
	var previousSubBoardCode = null;
	if (typeof(pSubCode) === "string" && subBoardCodeIsValid(pSubCode))
	{
		var previousSubBoardCode = this.subBoardCode;
		this.subBoardCode = pSubCode;
		scanScopeChar = "S";
	}
	else
	{
		console.mnemonics(bbs.text(SubGroupOrAll));
		scanScopeChar = console.getkeys("SGAC").toString();
	}
		this.SearchMessages(pSearchModeStr, null, scanScopeChar, pTxtToSearch);
	else
	{
		console.crlf();
		console.print(replaceAtCodesInStr(this.text.msgScanAbortedText));
		console.crlf();
		console.pause();
	}
	// Restore this.subBoardCode if necessary
	if (typeof(previousSubBoardCode) === "string")
		this.subBoardCode = previousSubBoardCode;
// This function clears the search data from the object.
function DigDistMsgReader_ClearSearchData()
{
   this.searchType = SEARCH_NONE;
   this.searchString == "";
   if (this.msgSearchHdrs != null)
   {
		for (var subCode in this.msgSearchHdrs)
		{
			delete this.msgSearchHdrs[subCode].indexed;
			delete this.msgSearchHdrs[subCode];
		}
		delete this.msgSearchHdrs;
   }
}

// For the DigDistMsgReader class: Performs message reading/listing.
// Depending on the value of this.startMode, starts in either reader
// mode or lister mode.  Uses an input loop to let the user switch
// between the two modes.
//
// Parameters:
//  pSubBoardCode: Optional - The internal code of a sub-board to read.
//                 If not specified, the internal sub-board code specified
//                 when creating the object will be used.
//  pStartingMsgOffset: Optional - The offset of a message to start at
//  pAllowChgArea: Optional boolean - Whether or not to allow changing the
//                 message area
//  pReturnOnNextAreaNav: Optional boolean - Whether or not this method should
//                        return when it would move to the next message area due
//                        navigation from the user (i.e., with the right arrow key)
//  pPauseOnNoMsgSrchResults: Optional boolean - Whether or not to pause when
//                            a message search doesn't find any search results
//                            in the current sub-board.  Defaults to true.
//  pPromptToGoNextIfNoResults: Optional boolean - Whether or not to prompt the user
//                         to go onto the next/previous sub-board if there are no
//                         search results in the current sub-board.  Defaults to true.
//  pInitialModeOverride: Optional (numeric) to override the initial mode in this