Skip to content
Snippets Groups Projects
DDMsgReader.js 764 KiB
Newer Older
			// this.tradListTopMsgIdx appropriately.
			else if (retvalObj.userInput == "N")
			{
					this.tradListTopMsgIdx -= this.tradMsgListNumLines;
				else
					this.tradListTopMsgIdx += this.tradMsgListNumLines;
			}
			// First page
			else if (retvalObj.userInput == "F")
			{
					this.tradListTopMsgIdx = this.NumMessages() - 1;
				else
					this.tradListTopMsgIdx = 0;
			}
			// Last page
			else if (retvalObj.userInput == "L")
			{
				{
					this.tradListTopMsgIdx = (this.NumMessages() % this.tradMsgListNumLines) - 1;
					// If this.tradListTopMsgIdx is now invalid (below 0), then adjust it
					// to properly display the last page of messages.
					if (this.tradListTopMsgIdx < 0)
						this.tradListTopMsgIdx = this.tradMsgListNumLines - 1;
				}
				else
				{
					var totalNumMessages = this.NumMessages();
					this.tradListTopMsgIdx = totalNumMessages - (totalNumMessages % this.tradMsgListNumLines);
					if (this.tradListTopMsgIdx >= totalNumMessages)
						this.tradListTopMsgIdx = totalNumMessages - this.tradMsgListNumLines;
				}
			}
			// D: Delete a message
			else if (retvalObj.userInput == "D")
			{
				if (this.CanDelete() || this.CanDeleteLastMsg())
				{
					var msgNum = this.PromptForMsgNum({ x: curpos.x, y: curpos.y+1 }, this.text.deleteMsgNumPromptText, false, ERROR_PAUSE_WAIT_MS, false);
					// If the user enters a valid message number, then call the
					// DeleteMessage() method, which will prompt the user for
					// confirmation and delete the message if confirmed.
					if (msgNum > 0)
						this.PromptAndDeleteMessage(msgNum-1);
					// Refresh the top header on the screen for continuing to list
					// messages.
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
				}
			}
			// E: Edit a message
			else if (retvalObj.userInput == "E")
			{
				if (this.CanEdit())
				{
					var msgNum = this.PromptForMsgNum({ x: curpos.x, y: curpos.y+1 }, this.text.editMsgNumPromptText, false, ERROR_PAUSE_WAIT_MS, false);
					// If the user entered a valid message number, then let the
					// user edit the message.
					if (msgNum > 0)
					{
						// See if the current message header has our "isBogus" property and it's true.
						// Only let the user edit the message if it's not a bogus message header.
						// The message header could have the "isBogus" property, for instance, if
						// it's a vote message (introduced in Synchronet 3.17).
						var tmpMsgHdr = this.GetMsgHdrByIdx(msgNum-1);
						var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false);
						if (!hdrIsBogus)
							var returnObj = this.EditExistingMsg(msgNum-1);
						else
						{
							console.print("\1n\r\n\1h\1yThat message isn't editable.\n");
							console.crlf();
							console.pause();
						}
					}

					// Refresh the top header on the screen for continuing to list
					// messages.
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
				}
			}
			// G: Go to a specific message by # (place that message on the top)
			else if (retvalObj.userInput == "G")
			{
				var msgNum = this.PromptForMsgNum(curpos, "\1n" + this.text.goToMsgNumPromptText, false, ERROR_PAUSE_WAIT_MS, false);
				if (msgNum > 0)
				// Refresh the top header on the screen for continuing to list
				// messages.
				console.clear("\1n");
				this.WriteMsgListScreenTopHeader();
			}
			// ?: Display help
			else if (retvalObj.userInput == "?")
			{
				console.clear("\1n");
				this.DisplayMsgListHelp(allowChgSubBoard, true);
				console.clear("\1n");
				this.WriteMsgListScreenTopHeader();
			}
			// C: Change to another message area (sub-board)
			else if (retvalObj.userInput == "C")
			{
				if (allowChgSubBoard && (this.subBoardCode != "mail"))
				{
					// Store the current sub-board code so we can see if it changed
					var oldSubCode = bbs.cursub_code;
					// Let the user choose another message area.  If they chose
					// a different message area, then set up the message base
					// object accordingly.
					this.SelectMsgArea();
					if (bbs.cursub_code != oldSubCode)
					{
						var chgSubRetval = this.ChangeSubBoard(bbs.cursub_code);
						continueOn = chgSubRetval.succeeded;
					}
					// Update the traditional list variables and refresh the screen
					if (continueOn)
					{
						this.SetUpTraditionalMsgListVars();
						console.clear("\1n");
						this.WriteMsgListScreenTopHeader();
					}
				}
			}
			// S: Select message(s)
			else if (retvalObj.userInput == "S")
			{
				// Input the message number list from the user
				console.print("\1n\1cNumber(s) of message(s) to select, (\1hA\1n\1c=All, \1hN\1n\1c=None, \1hENTER\1n\1c=cancel)\1g\1h: \1c");
				var userNumberList = console.getstr(128, K_UPPER);
				// If the user entered A or N, then select/un-select all messages.
				// Otherwise, select only the messages that the user entered.
				if ((userNumberList == "A") || (userNumberList == "N"))
				{
					var messageSelectToggle = (userNumberList == "A");
					var totalNumMessages = this.NumMessages();
					for (var msgIdx = 0; msgIdx < totalNumMessages; ++msgIdx)
						this.ToggleSelectedMessage(this.subBoardCode, msgIdx, messageSelectToggle);
				}
				else
				{
					if (userNumberList.length > 0)
					{
						var numArray = parseNumberList(userNumberList);
						for (var numIdx = 0; numIdx < numArray.length; ++numIdx)
							this.ToggleSelectedMessage(this.subBoardCode, numArray[numIdx]-1);
					}
				}
				// Refresh the top header on the screen for continuing to list
				// messages.
				console.clear("\1n");
				this.WriteMsgListScreenTopHeader();
			}
			// Ctrl-D: Batch delete (for selected messages)
			else if (retvalObj.userInput == CTRL_D)
			{
				console.print("\1n");
				console.crlf();
				if (this.NumSelectedMessages() > 0)
				{
					// The PromptAndDeleteSelectedMessages() method will prompt the user for confirmation
					// to delete the message and then delete it if confirmed.
					this.PromptAndDeleteSelectedMessages();

					// In case all messages were deleted, if that's the case, show
					// an appropriate message and don't continue listing messages.
					//if (this.NumMessages(true) == 0)
					if (!this.NonDeletedMessagesExist())
					{
						continueOn = false;
						// Note: The following doesn't seem to be necessary, since
						// the ReadOrListSubBoard() method will show a message saying
						// there are no messages to read and then will quit out.
						
						//this.msgbase.close();
						//this.msgbase = null;
						//console.clear("\1n");
						//console.center("\1n\1h\1yThere are no messages to display.");
						//console.crlf();
						//console.pause();
						
					}
					else
					{
						// There are still messages to list, so refresh the top
						// header on the screen for continuing to list messages.
						console.clear("\1n");
						this.WriteMsgListScreenTopHeader();
					}
				}
				else
				{
					// There are no selected messages
					console.print("\1n\1h\1yThere are no selected messages.");
					mswait(ERROR_PAUSE_WAIT_MS);
					// Refresh the top header on the screen for continuing to list messages.
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
				}
			}
				// If a message has been selected, exit out of this input loop
				// so we can return from this method - The calling method will
				// call the enhanced reader method.
				if (retObj.selectedMsgOffset >= 0)
}
// For the DigDistMsgReader class: Performs the message listing, given a
// sub-board code.  This verison uses a lightbar interface for message
// navigation.  Note: This function requires this.msgbase to be valid and
// open.
//
// Parameters:
//  pAllowChgSubBoard: Optional - A boolean to specify whether or not to allow
//                     changing to another sub-board.  Defaults to true.
//
// Return value: An object containing the following properties:
//               lastUserInput: The user's last keypress/input
//               selectedMsgOffset: The index of the message selected to read,
//                                  if one was selected.  If none was selected,
//                                  this will be -1.
function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
	var retObj = new Object();
	retObj.lastUserInput = "";
	retObj.selectedMsgOffset = -1;

	// This method is only supported if the user's terminal supports
	// ANSI.
	if (!canDoHighASCIIAndANSI()) // Could also be !console.term_supports(USER_ANSI)
	{
		console.print("\r\n\1h\1ySorry, an ANSI terminal is required for this operation.\1n\1w\r\n");
		console.pause();
		return retObj;
	}

	// Reset this.readAMessage and deniedReadingMessage to false, in case the
	// message listing has previously ended with them set to true.
	this.readAMessage = false;
	this.deniedReadingMessage = false;

	// this.msgbase must be valid before continuing.
	if ((typeof(this.msgbase) == "undefined") || (this.msgbase == null))
	{
		console.center("\1n\1h\1yError: \1wUnable to list messages because the sub-board is not open.\r\n\1p");
		return retObj;
		console.center("\1n\1h\1yError: \1wUnable to list messages because the sub-board is not open.\r\n\1p");
		return retObj;
	var allowChgSubBoard = (typeof(pAllowChgSubBoard) == "boolean" ? pAllowChgSubBoard : true);
	// This function will be used for displaying the help line at
	// the bottom of the screen.
	function DisplayHelpLine(pHelpLineText)
	{
		console.gotoxy(1, console.screen_rows);
		console.print(pHelpLineText);
		console.cleartoeol("\1n");
	}
	// Clear the screen and write the header at the top
	console.clear("\1n");
	this.WriteMsgListScreenTopHeader();
	DisplayHelpLine(this.msgListLightbarModeHelpLine);

	// If the lightbar message list index & cursor position variables haven't been
	// set yet, then set them.
	if ((this.lightbarListTopMsgIdx == -1) || (this.lightbarListSelectedMsgIdx == -1) ||
	    (this.lightbarListCurPos == null))
	{
		this.SetUpLightbarMsgListVars();
	}

	// List a screenful of message headers
	console.gotoxy(1, this.lightbarMsgListStartScreenRow);
	var lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
	// Move the cursor to where it needs to be
	console.gotoxy(this.lightbarListCurPos);
	// User input loop
	var bottomMsgIndex = 0;
	var userInput = "";
	var msgHeader = null;
	var continueOn = true;
	while (continueOn)
	{
		bbs.command_str = ""; // To prevent weirdness

		retObj.selectedMsgOffset = -1;

		// Calculate the message number (0-based) of the message
		// appearing on the bottom of the screen.
			bottomMsgIndex = this.lightbarListTopMsgIdx - this.lightbarMsgListNumLines + 1;
			if (bottomMsgIndex < 0)
				bottomMsgIndex = 0;
			var totalNumMessages = this.NumMessages();
			bottomMsgIndex = this.lightbarListTopMsgIdx + this.lightbarMsgListNumLines - 1;
			if (bottomMsgIndex >= totalNumMessages)
				bottomMsgIndex = totalNumMessages - 1;
		// Write the current message information with highlighting colors
		msgHeader = this.GetMsgHdrByIdx(this.lightbarListSelectedMsgIdx);
		this.PrintMessageInfo(msgHeader, true, this.lightbarListSelectedMsgIdx+1);
		console.gotoxy(this.lightbarListCurPos); // Make sure the cursor is still in the right place
		// Get a key from the user (upper-case) and take appropriate action.
		userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOECHO|K_NOSPIN);
		retObj.lastUserInput = userInput;
		// Q: Quit
		if (userInput == "Q")
		{
			// Quit
			continueOn = false;
			break;
		}
		// ?: Show help
		else if (userInput == "?")
		{
			// Display help
			console.clear("\1n");
			this.DisplayMsgListHelp(allowChgSubBoard, true);
			// Re-draw the message list on the screen
			console.clear("\1n");
			this.WriteMsgListScreenTopHeader();
			DisplayHelpLine(this.msgListLightbarModeHelpLine);
			console.gotoxy(1, this.lightbarMsgListStartScreenRow);
			lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
			console.gotoxy(this.lightbarListCurPos); // Put the cursor back where it should be
		}
		// Up arrow: Highlight the previous message
		else if (userInput == KEY_UP)
		{
			// Make sure this.lightbarListSelectedMsgIdx is within bounds before moving down.
			{
				if (this.lightbarListSelectedMsgIdx >= this.NumMessages() - 1)
					continue;
			}
			else
			{
				if (this.lightbarListSelectedMsgIdx <= 0)
					continue;
			}
			// Print the current message information with regular colors
			this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
				++this.lightbarListSelectedMsgIdx;
			else
				--this.lightbarListSelectedMsgIdx;
			// If the current screen row is above the first line allowed, then
			// move the cursor up one row.
			if (this.lightbarListCurPos.y > this.lightbarMsgListStartScreenRow)
			{
				console.gotoxy(1, this.lightbarListCurPos.y-1);
				this.lightbarListCurPos.x = 1;
				--this.lightbarListCurPos.y;
			}
			else
			{
				// Go onto the previous page, with the cursor highlighting
				// the last message on the page.
					this.lightbarListTopMsgIdx = this.lightbarListSelectedMsgIdx + this.lightbarMsgListNumLines - 1;
				else
					this.lightbarListTopMsgIdx = this.lightbarListSelectedMsgIdx - this.lightbarMsgListNumLines + 1;
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				console.gotoxy(1, this.lightbarMsgListStartScreenRow+this.lightbarMsgListNumLines-1);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow+this.lightbarMsgListNumLines-1;
			}
		}
		// Down arrow: Highlight the next message
		else if (userInput == KEY_DOWN)
		{
			// Make sure this.lightbarListSelectedMsgIdx is within bounds before moving down.
			{
				if (this.lightbarListSelectedMsgIdx <= 0)
					continue;
			}
			else
			{
				if (this.lightbarListSelectedMsgIdx >= this.NumMessages() - 1)
					continue;
			}
			// Print the current message information with regular colors
			this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
				--this.lightbarListSelectedMsgIdx;
			else
				++this.lightbarListSelectedMsgIdx;
			// If the current screen row is below the last line allowed, then
			// move the cursor down one row.
			if (this.lightbarListCurPos.y < this.lightbarMsgListStartScreenRow+this.lightbarMsgListNumLines-1)
			{
				console.gotoxy(1, this.lightbarListCurPos.y+1);
				this.lightbarListCurPos.x = 1;
				++this.lightbarListCurPos.y;
			}
			else
			{
				// Go onto the next page, with the cursor highlighting
				// the first message on the page.
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListTopMsgIdx = this.lightbarListSelectedMsgIdx;
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				// If we were on the last page, then clear the screen from
				// the current line to the end of the screen.
				if (lastPage)
				{
					this.lightbarListCurPos = console.getxy();
					clearToEOS(this.lightbarListCurPos.y);
					// Make sure the help line is still there
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
				}
				// Move the cursor to the top of the list
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
			}
		}
		// HOME key: Go to the first message on the screen
		else if (userInput == KEY_HOME)
		{
			// Print the current message information with regular colors
			this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
			// Go to the first message of the current page
				this.lightbarListSelectedMsgIdx += (this.lightbarListCurPos.y - this.lightbarMsgListStartScreenRow);
			else
				this.lightbarListSelectedMsgIdx -= (this.lightbarListCurPos.y - this.lightbarMsgListStartScreenRow);
			// Move the cursor to the first message line
			console.gotoxy(1, this.lightbarMsgListStartScreenRow);
			this.lightbarListCurPos.x = 1;
			this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
		}
		// END key: Go to the last message on the screen
		else if (userInput == KEY_END)
		{
			// Print the current message information with regular colors
			this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
			// Update the selected message #
			this.lightbarListSelectedMsgIdx = bottomMsgIndex;
			// Go to the last message of the current page
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow + this.lightbarListTopMsgIdx - bottomMsgIndex;
			else
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow + bottomMsgIndex - this.lightbarListTopMsgIdx;
			console.gotoxy(this.lightbarListCurPos);
		}
		// Enter key: Select a message to read
		else if (userInput == KEY_ENTER)
		{
			// See if the current message header has our "isBogus" property and it's true.
			// Only let the user read the message if it's not a bogus message header.
			// The message header could have the "isBogus" property, for instance, if
			// it's a vote message (introduced in Synchronet 3.17).
			var hdrIsBogus = (msgHeader.hasOwnProperty("isBogus") ? msgHeader.isBogus : false);
			if (!hdrIsBogus)
				var originalCurpos = console.getxy();

				// Allow the user to read the current message.
				var readMsg = true;
				if (this.promptToReadMessage)
				{
					// Confirm with the user whether to read the message.
					var sReadMsgConfirmText = this.colors["readMsgConfirmColor"]
											+ "Read message "
											+ this.colors["readMsgConfirmNumberColor"]
											+ +(this.GetMsgIdx(msgHeader.number) + 1)
											+ this.colors["readMsgConfirmColor"]
											+ ": Are you sure";
					console.gotoxy(1, console.screen_rows);
					console.print("\1n");
					console.clearline();
					readMsg = console.yesno(sReadMsgConfirmText);
				}
				var repliedToMessage = false;
				if (readMsg)
				{
					// If there is a search specified and the search result objects are
					// set up for the current sub-board, then the selected message offset
					// should be the search result array index.  Otherwise (if not
					// searching), the message offset should be the actual message offset
					// in the message base.
					if (this.SearchingAndResultObjsDefinedForCurSub())
						retObj.selectedMsgOffset = this.lightbarListSelectedMsgIdx;
					else
					{
						//retObj.selectedMsgOffset = msgHeader.offset;
						retObj.selectedMsgOffset = this.GetMsgIdx(msgHeader.number);
						if (retObj.selectedMsgOffset < 0)
							retObj.selectedMsgOffset = 0;
					// Return from here so that the calling function can switch into
					// reader mode.
					continueOn = false;
					return retObj;
				}
				// Ask the user if  they want to continue reading messages
				if (this.promptToContinueListingMessages)
					continueOn = console.yesno(this.colors["afterReadMsg_ListMorePromptColor"] +
					"Continue listing messages");
				}
				// If the user chose to continue reading messages, then refresh
				// the screen.  Even if the user chooses not to read the message,
				// the screen needs to be re-drawn so it appears properly.
				if (continueOn)
				{
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
					console.gotoxy(1, this.lightbarMsgListStartScreenRow);
					// If we're dispaying in reverse order and the user replied
					// to the message, then we'll have to re-arrange the screen
					// a bit to make way for the new message that will appear
					// in the list.
					if (this.reverseListOrder && repliedToMessage)
						// Make way for the new message, which will appear at the
						// top.
						++this.lightbarListTopMsgIdx;
						// If the cursor is below the bottommost line displaying
						// messages, then advance the cursor down one position.
						// Otherwise, increment this.lightbarListSelectedMsgIdx (since a new message
						// will appear at the top, the previous selected message
						// will be pushed to the next page).
						if (this.lightbarListCurPos.y < console.screen_rows - 1)
						{
							++originalCurpos.y;
							++this.lightbarListCurPos.y;
						}
						else
							++this.lightbarListSelectedMsgIdx;
					lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
					console.gotoxy(originalCurpos); // Put the cursor back where it should be
				}
			}
		}
		// PageDown: Next page
		else if (userInput == KEY_PAGE_DOWN)
		{
			// Next page
			if (!lastPage)
			{
					this.lightbarListTopMsgIdx -= this.lightbarMsgListNumLines;
				else
					this.lightbarListTopMsgIdx += this.lightbarMsgListNumLines;
				this.lightbarListSelectedMsgIdx = this.lightbarListTopMsgIdx;
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);

				// If we were on the last page, then clear the screen from
				// the current line to the end of the screen.
				if (lastPage)
				{
					this.lightbarListCurPos = console.getxy();
					clearToEOS(this.lightbarListCurPos.y);
					// Make sure the help line is still there
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
				}
				// Move the cursor back to the first message info line
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
			}
			else {
				// The user is on the last page - Go to the last message on the page.
				if (this.lightbarListSelectedMsgIdx != bottomMsgIndex)
				{
					// Print the current message information with regular colors
					this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
					// Update the selected message #
					this.lightbarListSelectedMsgIdx = bottomMsgIndex;
					this.lightbarListCurPos.x = 1;
					if (this.reverseListOrder)
						this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow + this.lightbarListTopMsgIdx - bottomMsgIndex;
					else
						this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow + bottomMsgIndex - this.lightbarListTopMsgIdx;
					console.gotoxy(this.lightbarListCurPos);
				}
			}
		}
		// PageUp: Previous page
		else if (userInput == KEY_PAGE_UP)
		{
			var canGoToPrevious = false;
				canGoToPrevious = (this.lightbarListTopMsgIdx < this.NumMessages() - 1);
			else
				canGoToPrevious = (this.lightbarListTopMsgIdx > 0);
					this.lightbarListTopMsgIdx += this.lightbarMsgListNumLines;
				else
					this.lightbarListTopMsgIdx -= this.lightbarMsgListNumLines;
				this.lightbarListSelectedMsgIdx = this.lightbarListTopMsgIdx;
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
			}
			else
			{
				// The user is on the first page - Go to the first message on the page.
				if (this.lightbarListSelectedMsgIdx != 0)
				{
					// Print the current message information with regular colors
					this.PrintMessageInfo(msgHeader, false, this.lightbarListSelectedMsgIdx+1);
					// Go to the first message of the current page
					if (this.reverseListOrder)
						this.lightbarListSelectedMsgIdx += (this.lightbarListCurPos.y - this.lightbarMsgListStartScreenRow);
					else
						this.lightbarListSelectedMsgIdx -= (this.lightbarListCurPos.y - this.lightbarMsgListStartScreenRow);
					// Move the cursor to the first message line
					console.gotoxy(1, this.lightbarMsgListStartScreenRow);
					this.lightbarListCurPos.x = 1;
					this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
				}
			}
		}
		// F: First page
		else if (userInput == "F")
		{
			var canGoToFirst = false;
				canGoToFirst = (this.lightbarListTopMsgIdx < this.NumMessages() - 1);
			else
				canGoToFirst = (this.lightbarListTopMsgIdx > 0);

			if (canGoToFirst)
			{
					this.lightbarListTopMsgIdx = this.NumMessages() - 1;
				else
					this.lightbarListTopMsgIdx = 0;
				this.lightbarListSelectedMsgIdx = this.lightbarListTopMsgIdx;
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
			}
		}
		// L: Last page
		else if (userInput == "L")
		{
			if (!lastPage)
			{
				// Set the top message index.  If this.lightbarListTopMsgIdx is beyond the last
				// message in the sub-board, then move back a full page of messages.
				{
					this.lightbarListTopMsgIdx = (this.NumMessages() % this.lightbarMsgListNumLines) - 1;
					// If this.lightbarListTopMsgIdx is now invalid (below 0), then adjust it
					// to properly display the last page of messages.
					if (this.lightbarListTopMsgIdx < 0)
						this.lightbarListTopMsgIdx = this.lightbarMsgListNumLines - 1;
				}
				else
				{
					var totalNumMessages = this.NumMessages();
					this.lightbarListTopMsgIdx = totalNumMessages - (totalNumMessages % this.lightbarMsgListNumLines);
					if (this.lightbarListTopMsgIdx >= totalNumMessages)
						this.lightbarListTopMsgIdx = totalNumMessages - this.lightbarMsgListNumLines;
				}
				this.lightbarListSelectedMsgIdx = this.lightbarListTopMsgIdx;
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				// If we were on the last page, then clear the screen from
				// the current line to the end of the screen.
				if (lastPage)
				{
					this.lightbarListCurPos = console.getxy();
					clearToEOS(this.lightbarListCurPos.y);
					// Make sure the help line is still there
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
				}
				// Move the cursor back to the first message info line
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				this.lightbarListCurPos.x = 1;
				this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
			}
		}
		// Numeric digit: The start of a number of a message to read
		else if (userInput.match(/[0-9]/))
		{
			var originalCurpos = console.getxy();
			// Put the user's input back in the input buffer to
			// be used for getting the rest of the message number.
			console.ungetstr(userInput);
			// Move the cursor to the bottom of the screen and
			// prompt the user for the message number.
			console.gotoxy(1, console.screen_rows);
			userInput = this.PromptForMsgNum({ x: 1, y: console.screen_rows }, this.text.readMsgNumPromptText, true, ERROR_PAUSE_WAIT_MS, false);
			if (userInput > 0)
			{
				// See if the current message header has our "isBogus" property and it's true.
				// Only let the user read the message if it's not a bogus message header.
				// The message header could have the "isBogus" property, for instance, if
				// it's a vote message (introduced in Synchronet 3.17).
				//GetMsgHdrByIdx(pMsgIdx, pExpandFields)
				var tmpMsgHdr = this.GetMsgHdrByIdx(+(userInput-1), false);
				var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false);
				if (!hdrIsBogus)
					// Confirm with the user whether to read the message
					var readMsg = true;
					if (this.promptToReadMessage)
					{
						var sReadMsgConfirmText = this.colors["readMsgConfirmColor"]
												+ "Read message "
												+ this.colors["readMsgConfirmNumberColor"]
												+ userInput + this.colors["readMsgConfirmColor"]
												+ ": Are you sure";
						readMsg = console.yesno(sReadMsgConfirmText);
					}
					if (readMsg)
					{
						// Update the message list screen variables
						this.CalcMsgListScreenIdxVarsFromMsgNum(+userInput);
						retObj.selectedMsgOffset = userInput - 1;
						// Return from here so that the calling function can switch
						// into reader mode.
						return retObj;
					}
					else
						this.deniedReadingMessage = true;

					// Prompt the user whether or not to continue listing
					// messages.
					if (this.promptToContinueListingMessages)
					{
						continueOn = console.yesno(this.colors["afterReadMsg_ListMorePromptColor"] +
												   "Continue listing messages");
					}
					writeWithPause(1, console.screen_rows, "\1n\1h\1yThat's not a readable message.",
					               ERROR_PAUSE_WAIT_MS, "\1n", true);
			// If the user chose to continue listing messages, then re-draw
			// the screen.
			if (continueOn)
			{
				console.clear("\1n");
				this.WriteMsgListScreenTopHeader();
				DisplayHelpLine(this.msgListLightbarModeHelpLine);
				console.gotoxy(1, this.lightbarMsgListStartScreenRow);
				lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
				console.gotoxy(originalCurpos); // Put the cursor back where it should be
			}
		}
		// DEL key: Delete a message
		else if (userInput == KEY_DEL)
		{
			if (this.CanDelete() || this.CanDeleteLastMsg())
			{
				var originalCurpos = console.getxy();
				console.gotoxy(1, console.screen_rows);
				console.print("\1n");
				console.clearline();
				// The PromptAndDeleteMessage() method will prompt the user for confirmation
				// to delete the message and then delete it if confirmed.
				this.PromptAndDeleteMessage(this.lightbarListSelectedMsgIdx, { x: 1, y: console.screen_rows});
				
				// In case all messages were deleted, if that's the case, show
				// an appropriate message and don't continue listing messages.
				//if (this.NumMessages(true) == 0)
				if (!this.NonDeletedMessagesExist())
				{
					continueOn = false;
					// Note: The following doesn't seem to be necessary, since
					// the ReadOrListSubBoard() method will show a message saying
					// there are no messages to read and then will quit out.
					/*
					this.msgbase.close();
					this.msgbase = null;
					console.clear("\1n");
					console.center("\1n\1h\1yThere are no messages to display.");
					console.crlf();
					console.pause();
					*/
				}
				else
				{
					// There are still some messages to show, so refresh the screen.
					// Refresh the screen
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
					console.gotoxy(1, this.lightbarMsgListStartScreenRow);
					lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
					console.gotoxy(originalCurpos); // Put the cursor back where it should be
				}
			}
		}
		// E: Edit a message
		else if (userInput == "E")
		{
			if (this.CanEdit())
			{
				// See if the current message header has our "isBogus" property and it's true.
				// Only let the user edit the message if it's not a bogus message header.
				// The message header could have the "isBogus" property, for instance, if
				// it's a vote message (introduced in Synchronet 3.17).
				var hdrIsBogus = (msgHeader.hasOwnProperty("isBogus") ? msgHeader.isBogus : false);
				if (!hdrIsBogus)
				{
					var originalCurpos = console.getxy();
					// Ask the user if they really want to edit the message
					console.gotoxy(1, console.screen_rows);
					console.print("\1n");
					console.clearline();
					// Let the user edit the message
					//var returnObj = this.EditExistingMsg(msgHeader.offset);
					var returnObj = this.EditExistingMsg(this.lightbarListSelectedMsgIdx);
					// Refresh the screen
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
					console.gotoxy(1, this.lightbarMsgListStartScreenRow);
					lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
					console.gotoxy(originalCurpos); // Put the cursor back where it should be
				}
			}
		}
		// G: Go to a specific message by # (highlight or place that message on the top)
		else if (userInput == "G")
		{
			var originalCurpos = console.getxy();

			// Move the cursor to the bottom of the screen and
			// prompt the user for a message number.
			console.gotoxy(1, console.screen_rows);
			userInput = this.PromptForMsgNum({ x: 1, y: console.screen_rows }, "\n" + this.text.goToMsgNumPromptText, true, ERROR_PAUSE_WAIT_MS, false);
			if (userInput > 0)
			{
				// Make sure the message number is for a valid message (i.e., it
				// could be an invalid message number if there is a search, where
				// not all message numbers are consecutive).
				if (this.GetMsgHdrByMsgNum(userInput) != null)
				{
					// If the message is on the current page, then just go to and
					// highlight it.  Otherwise, set the user's selected message on the
					// top of the page.  We also have to make sure that this.lightbarListCurPos.y and
					// originalCurpos.y are set correctly.  Also, account for search
					// results if there are any (we'll need to have the correct array
					// index for the search results).
					var chosenMsgIndex = userInput - 1;
					if ((chosenMsgIndex <= bottomMsgIndex) && (chosenMsgIndex >= this.lightbarListTopMsgIdx))
					{
						this.lightbarListSelectedMsgIdx = chosenMsgIndex;
						originalCurpos.y = this.lightbarListCurPos.y = this.lightbarListSelectedMsgIdx - this.lightbarListTopMsgIdx + this.lightbarMsgListStartScreenRow;
					}
					else
					{
						this.lightbarListTopMsgIdx = this.lightbarListSelectedMsgIdx = chosenMsgIndex;
						originalCurpos.y = this.lightbarListCurPos.y = this.lightbarMsgListStartScreenRow;
					}
				}
				else
				{
					// The user entered an invalid message number
					console.print("\1n" + this.text.invalidMsgNumText.replace("%d", userInput) + "\1n");
					console.inkey(K_NONE, ERROR_PAUSE_WAIT_MS);
				}
			// Clear & re-draw the screen, to fix any possible alignment problems
			// caused by newline output after the user inputs their choice.
			console.clear("\1n");
			this.WriteMsgListScreenTopHeader();
			DisplayHelpLine(this.msgListLightbarModeHelpLine);
			console.gotoxy(1, this.lightbarMsgListStartScreenRow);
			lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
			console.gotoxy(originalCurpos); // Put the cursor back where it should be
		}
		// C: Change to another message area (sub-board)
		else if (userInput == "C")
		{
			if (allowChgSubBoard && (this.subBoardCode != "mail"))
			{
				// Store the current sub-board code so we can see if it changed
				var oldSubCode = bbs.cursub_code;
				// Let the user choose another message area.  If they chose
				// a different message area, then set up the message base
				// object accordingly.
				this.SelectMsgArea();
				if (bbs.cursub_code != oldSubCode)
				{
					var chgSubRetval = this.ChangeSubBoard(bbs.cursub_code);
					continueOn = chgSubRetval.succeeded;
				}
				// Update the lightbar list variables and refresh the screen
				if (continueOn)
				{
					this.SetUpLightbarMsgListVars();
					console.clear("\1n");
					this.WriteMsgListScreenTopHeader();
					DisplayHelpLine(this.msgListLightbarModeHelpLine);
					// List a screenful of message headers
					console.gotoxy(1, this.lightbarMsgListStartScreenRow);
					var lastPage = this.ListScreenfulOfMessages(this.lightbarListTopMsgIdx, this.lightbarMsgListNumLines);
					// Move the cursor to where it needs to be
					console.gotoxy(this.lightbarListCurPos);
				}
			}
		}
		// Spacebar: Select a message for batch operations (such as batch
		// delete, etc.)
		else if (userInput == " ")
			this.ToggleSelectedMessage(this.subBoardCode, this.lightbarListSelectedMsgIdx);
		// Ctrl-A: Select/de-select all messages
		else if (userInput == CTRL_A)
		{
			var originalCurpos = console.getxy();
			console.gotoxy(1, console.screen_rows);
			console.print("\1n");
			console.clearline();
			console.gotoxy(1, console.screen_rows);

			// Prompt the user to select All, None (un-select all), or Cancel
			console.print("\1n\1gSelect \1c(\1hA\1n\1c)\1gll, \1c(\1hN\1n\1c)\1gone, or \1c(\1hC\1n\1c)\1gancel: \1h\1g");
			var userChoice = getAllowedKeyWithMode("ANC", K_UPPER | K_NOCRLF);
			if ((userChoice == "A") || (userChoice == "N"))
			{
				// Toggle all the messages
				var messageSelectToggle = (userChoice == "A");
				var totalNumMessages = this.NumMessages();
				var messageIndex = 0;
				for (messageIndex = 0; messageIndex < totalNumMessages; ++messageIndex)
					this.ToggleSelectedMessage(this.subBoardCode, messageIndex, messageSelectToggle);
				// Refresh the selected message checkmarks on the screen - Add the
				// checkmarks for messages that are selected, and write a blank space
				// (no checkmark) for messages that are not selected.
				var currentRow = this.lightbarMsgListStartScreenRow;
				var messageIndexEnd = this.lightbarListTopMsgIdx + this.lightbarMsgListNumLines;
				for (messageIndex = this.lightbarListTopMsgIdx; messageIndex < messageIndexEnd; ++messageIndex)
				{
					// Skip the current selected message because that one's checkmark
					// will be refreshed.  Also skip this one if the message has been
					// marked as deleted already.
					if (!this.MessageIsDeleted(messageIndex) && (messageIndex != this.lightbarListSelectedMsgIdx))
					{
						console.gotoxy(this.MSGNUM_LEN+1, currentRow);
						console.print("\1n");