Skip to content
Snippets Groups Projects
DDMsgReader.js 786 KiB
Newer Older
						writeMessage = true; // We want to refresh the message on the screen
					else
					{
						// There are no kludge lines for this message
						this.DisplayEnhReaderError(this.text.noKludgeLinesForThisMsgText, msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
						console.gotoxy(originalCurPos);
					}
				}
				else // The user is not a sysop
					writeMessage = false;
				break;
				// Message list, change message area: Quit out of this input loop
				// and let the calling function, this.ReadMessages(), handle the
				// action.
			case this.enhReaderKeys.showMsgList: // Message list
				console.print("\1n");
				console.crlf();
				console.print("Loading...");
				retObj.nextAction = ACTION_DISPLAY_MSG_LIST;
				continueOn = false;
				break;
			case this.enhReaderKeys.chgMsgArea: // Change message area, if allowed
				if (allowChgMsgArea)
				{
					retObj.nextAction = ACTION_CHG_MSG_AREA;
				}
				else
					writeMessage = false; // No need to refresh the message
				break;
			case this.enhReaderKeys.downloadAttachments: // Download attachments
				if (msgInfo.attachments.length > 0)
				{
					console.print("\1n");
					console.gotoxy(1, console.screen_rows);
					console.crlf();
					console.print("\1c- Download Attached Files -\1n");
					// Note: sendAttachedFiles() will output a CRLF at the beginning.
					sendAttachedFiles(msgInfo.attachments);

					// Refresh things on the screen
					console.clear("\1n");
					// Display the message header and key help line again
					this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1);
					this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					// Display the scrollbar again to refresh it on the screen
					solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage);
					this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks);
					writeMessage = true; // We want to refresh the message on the screen
				}
				else
					writeMessage = false;
				break;
			case this.enhReaderKeys.saveToBBSMachine:
				// Save the message to the BBS machine - Only allow this
				// if the user is a sysop.
				if (gIsSysop)
				{
					// Prompt the user for a filename to save the message to the
					// BBS machine
					var promptPos = this.EnhReaderPrepLast2LinesForPrompt();
					console.print("\1n\1cFilename:\1h");
					var inputLen = console.screen_columns - 10; // 10 = "Filename:" length + 1
					var filename = console.getstr(inputLen, K_NOCRLF);
					console.print("\1n");
					if (filename.length > 0)
						//var saveMsgRetObj = this.SaveMsgToFile(msgHeader, filename, true, msgInfo.messageLines);
						var saveMsgRetObj = this.SaveMsgToFile(msgHeader, filename, true);
						console.gotoxy(promptPos);
						console.cleartoeol("\1n");
						console.gotoxy(promptPos);
						if (saveMsgRetObj.succeeded)
							console.print("\1n\1cThe message has been saved.\1n");
						else
							console.print("\1n\1y\1hFailed: " + saveMsgRetObj.errorMsg + "\1n");
						mswait(ERROR_PAUSE_WAIT_MS);
						console.gotoxy(promptPos);
						console.print("\1n\1y\1hMessage not exported\1n");
						mswait(ERROR_PAUSE_WAIT_MS);
					// Refresh the last 2 lines of the message on the screen to overwrite
					// the file save prompt
					this.DisplayEnhReaderError("", msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
					writeMessage = false; // Don't write the whole message again
				}
				else
					writeMessage = false;
				break;
			case this.enhReaderKeys.userEdit: // Edit the user who wrote the message
				if (gIsSysop)
				{
					console.print("\1n");
					console.crlf();
					console.print("- Edit user " + msgHeader.from);
					console.crlf();
					var editObj = editUser(msgHeader.from);
					if (editObj.errorMsg.length != 0)
						console.crlf();
						console.print("\1y\1h" + editObj.errorMsg + "\1n");
						console.crlf();
						console.pause();

					// Refresh things on the screen
					console.clear("\1n");
					// Display the message header and key help line again
					this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1);
					this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					// Display the scrollbar again to refresh it on the screen
					solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage);
					this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks);
					writeMessage = true; // We want to refresh the message on the screen
				}
				else // The user is not a sysop
			case this.enhReaderKeys.forwardMsg: // Forward the message
				console.print("\1n");
				console.crlf();
				console.print("\1c- Forward message\1n");
				console.crlf();
				var retStr = this.ForwardMessage(msgHeader, messageText);
				if (retStr.length > 0)
				{
					console.print("\1n\1h\1y* " + retStr + "\1n");
					console.crlf();
					console.pause();
				}

				// Refresh things on the screen
				console.clear("\1n");
				// Display the message header and key help line again
				this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1);
				this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
				// Display the scrollbar again to refresh it on the screen
				solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage);
				this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks);
				writeMessage = true; // We want to refresh the message on the screen
				break;
			case this.enhReaderKeys.vote: // Vote on the message
				// Move the cursor to the last line in the message area so the
				// vote question text prompt will appear there.
				var promptPos = this.EnhReaderPrepLast2LinesForPrompt();
				console.gotoxy(1, console.screen_rows-1);
				// Let the user vote on the message
				var voteRetObj = this.VoteOnMessage(msgHeader, true);
				if (voteRetObj.BBSHasVoteFunction)
				{
					var msgIsPollVote = ((typeof(MSG_TYPE_POLL) != "undefined") && (msgHeader.type & MSG_TYPE_POLL) == MSG_TYPE_POLL);
						// If the message is a poll vote, then output any error
						// message on its own line and refresh the whole screen.
						// Otherwise, use the last 2 rows for an error message
						// and only refresh what's necessary.
						if (msgIsPollVote)
							console.gotoxy(1, console.screen_rows-1);
							if (voteRetObj.errorMsg.length > 0)
							{
								if (voteRetObj.mnemonicsRequiredForErrorMsg)
								{
									console.mnemonics(voteRetObj.errorMsg);
									console.print("\1n");
								}
								else
									console.print("\1y\1h* " + voteRetObj.errorMsg + "\1n");
							}
							else if (!voteRetObj.savedVote)
								console.print("\1y\1h* Failed to save the vote\1n");
						{
							// Not a poll vote - Just an up/down vote
							if ((voteRetObj.errorMsg.length > 0) || (!voteRetObj.savedVote))
							{
								console.print("\1n");
								console.gotoxy(1, console.screen_rows-1);
								if (voteRetObj.errorMsg.length > 0)
								{
									if (voteRetObj.mnemonicsRequiredForErrorMsg)
									{
										console.mnemonics(voteRetObj.errorMsg);
										console.print("\1n");
									}
									else
										console.print("\1y\1h* " + voteRetObj.errorMsg + "\1n");
								}
								else if (!voteRetObj.savedVote)
									console.print("\1y\1h* Failed to save the vote\1n");
							}
							else
								msgHeader = voteRetObj.updatedHdr; // To get updated vote information
							mswait(ERROR_PAUSE_WAIT_MS);
						// Exit out of the reader and come back to read
						// the same message again so that the voting results
						// are re-loaded and displayed on the screen.
						retObj.newMsgOffset = pOffset;
						retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
						continueOn = false;
						this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					else
					{
						// The user quit out of voting.  Refresh the screen.
						// Exit out of the reader and come back to read
						// the same message again so that the screen is refreshed.
						retObj.newMsgOffset = pOffset;
						retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
						continueOn = false;
						this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					}
				break;
			case this.enhReaderKeys.showVotes: // Show votes
				// Save the original cursor position
				var originalCurPos = console.getxy();
				if (msgHeader.hasOwnProperty("total_votes") && msgHeader.hasOwnProperty("upvotes"))
				{
					var voteInfo = this.GetUpvoteAndDownvoteInfo(msgHeader);
					// Display the vote info and let the user scroll through them
					// (the console height should be enough, but do this just in case)
					// Calculate information for the scrollbar for the vote info lines
					var infoFractionShown = this.msgAreaHeight / voteInfo.length;
					if (infoFractionShown > 1)
						infoFractionShown = 1.0;
					var numInfoSolidScrollBlocks = Math.floor(this.msgAreaHeight * infoFractionShown);
					if (numInfoSolidScrollBlocks == 0)
						numInfoSolidScrollBlocks = 1;
					var numNonSolidInfoScrollBlocks = this.msgAreaHeight - numInfoSolidScrollBlocks;
					var lastInfoSolidBlockStartRow = this.msgAreaTop;
					// Define a scrollbar update function for the header info/kludge lines
					function msgInfoScrollbarUpdateFn(pFractionToLastPage)
					{
						var infoSolidBlockStartRow = msgReaderObj.msgAreaTop + Math.floor(numNonSolidInfoScrollBlocks * pFractionToLastPage);
						if (infoSolidBlockStartRow != lastInfoSolidBlockStartRow)
							msgReaderObj.UpdateEnhancedReaderScollbar(infoSolidBlockStartRow, lastInfoSolidBlockStartRow, numInfoSolidScrollBlocks);
						lastInfoSolidBlockStartRow = infoSolidBlockStartRow;
						console.gotoxy(1, console.screen_rows);
					}
					// Display the vote info lines and let the user scroll through them
					this.DisplayEnhancedReaderWholeScrollbar(this.msgAreaTop, numInfoSolidScrollBlocks);
					scrollTextLines(voteInfo, 0, this.colors["msgBodyColor"], true,
					this.msgAreaLeft, this.msgAreaTop, this.msgAreaWidth,
					msgAreaHeight, 1, console.screen_rows,
					msgInfoScrollbarUpdateFn);
					// Display the scrollbar for the message to refresh it on the screen
					solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage);
					this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks);
					writeMessage = true; // We want to refresh the message on the screen
				}
				else
				{
					this.DisplayEnhReaderError("There is no voting information for this message", msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
					console.gotoxy(originalCurPos);
					writeMessage = false;
				}
				break;
			case this.enhReaderKeys.closePoll: // Close a poll message
				// Save the original cursor position
				var originalCurPos = console.getxy();
				var pollCloseMsg = "";
				// If this message is a poll, then allow closing it.
				if ((typeof(MSG_TYPE_POLL) != "undefined") && (msgHeader.type & MSG_TYPE_POLL) == MSG_TYPE_POLL)
				{
					if ((msgHeader.auxattr & POLL_CLOSED) == 0)
					{
						// Only let the user close the poll if they created it
						if (userHandleAliasNameMatch(msgHeader.from))
						{
							// Prompt to confirm whether the user wants to close the poll
							console.gotoxy(1, console.screen_rows-1);
							printf("\1n%" + +(console.screen_columns-1) + "s", "");
							console.gotoxy(1, console.screen_rows-1);
							if (!console.noyes("Close poll"))
							{
								// Close the poll (open the sub-board first)
								var msgbase = new MsgBase(this.subBoardCode);
								if (msgbase.open())
									if (closePollWithOpenMsgbase(msgbase, msgHeader.number))
									{
										msgHeader.auxattr |= POLL_CLOSED;
										pollCloseMsg = "\1n\1cThis poll was successfully closed.";
									}
									else
										pollCloseMsg = "\1n\1r\1h* Failed to close this poll!";
									msgbase.close();
									pollCloseMsg = "\1n\1y\1hUnable to open sub-board to close the poll";
							}
						}
						else
							pollCloseMsg = "\1n\1y\1hCan't close this poll because it's not yours";
					}
					else
						pollCloseMsg = "\1n\1y\1hThis poll is already closed";
				}
				else
					pollCloseMsg = "This message is not a poll";

				// Display the poll closing status message
				this.DisplayEnhReaderError(pollCloseMsg, msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
				console.gotoxy(originalCurPos);
				writeMessage = false;
				break;
			case this.enhReaderKeys.validateMsg: // Validate the message
				if (gIsSysop && (this.subBoardCode != "mail") && msg_area.sub[this.subBoardCode].is_moderated)
				{
					var message = "";
					if (this.ValidateMsg(this.subBoardCode, msgHeader.number))
					{
						message = "\1n\1cMessage validation successful";
						// Refresh the message header in the arrays
						this.RefreshMsgHdrInArrays(msgHeader.number);
						// Exit out of the reader and come back to read
						// the same message again so that the voting results
						// are re-loaded and displayed on the screen.
						retObj.newMsgOffset = pOffset;
						retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
						continueOn = false;
						this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					}
					else
						message = "\1n\1y\1hMessage validation failed!";
					this.DisplayEnhReaderError(message, msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
				}
				else
					writeMessage = false;
				break;
			case this.enhReaderKeys.bypassSubBoardInNewScan:
				writeMessage = false; // TODO: Finish
				/*
				if (this.doingMsgScan)
				{
					console.print("\1n");
					var originalCurpos = console.getxy();
					// The 2nd to last row of the screen is where the user will
					// be prompted for confirmation to delete the message.
					// Ideally, I'd like to put the cursor on the last row of
					// the screen for this, but console.noyes() lets the enter
					// key shift everything on screen up one row, and there's
					// no way to avoid that.  So, to optimize screen refreshing,
					// the cursor is placed on the 2nd to the last row on the
					// screen to prompt for confirmation.
					var promptPos = this.EnhReaderPrepLast2LinesForPrompt();
					if (!console.noyes("Bypass this sub-board in newscans"))
					{
						continueOn = false;
						msg_area.sub[this.subBoardCode].scan_cfg &= SCAN_CFG_NEW;
					}
					else
					{
						this.DisplayEnhReaderError("", msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
						// Move the cursor back to its original position
						console.gotoxy(originalCurpos);
						this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
					}
				}
				else
					writeMessage = false;
				*/
				break;
				retObj.nextAction = ACTION_QUIT;
				continueOn = false;
				break;
			default:
				writeMessage = false;
				break;

	return retObj;
}
// Helper method for ReadMessageEnhanced() - Does the traditional (non-scrollable) reader interface
function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsgArea, messageText, msgHasANSICodes, pOffset)
	var retObj = {
		offsetValid: true,
		msgNotReadable: false,
		userReplied: false,
		lastKeypress: "",
		newMsgOffset: -1,
		nextAction: ACTION_NONE,
		refreshEnhancedRdrHelpLine: false
	};
	
	// Separate the message text from any attachments in the message.
	var msgAndAttachmentInfo = determineMsgAttachments(msgHeader, messageText, true);
	// Only interpret @-codes if the user is reading personal email.  There
	// are many @-codes that do some action such as move the cursor, execute a
	// script, etc., and I don't want users on message networks to do anything
	// malicious to users on other BBSes.
	if (this.readingPersonalEmail)
		msgAndAttachmentInfo.msgText = replaceAtCodesInStr(msgAndAttachmentInfo.msgText); // Or this.ParseMsgAtCodes(msgAndAttachmentInfo.msgText, msgHeader) to replace only some @ codes
	var msgTextWrapped = word_wrap(msgAndAttachmentInfo.msgText, console.screen_columns-1);

	// Generate the key help text
	var keyHelpText = "\1n\1c\1h#\1n\1b, \1c\1hLeft\1n\1b, \1c\1hRight\1n\1b, ";
	if (this.CanDelete() || this.CanDeleteLastMsg())
		keyHelpText += "\1c\1hDEL\1b, ";
	if (this.CanEdit())
		keyHelpText += "\1c\1hE\1y)\1n\1cdit\1b, ";
	keyHelpText += "\1c\1hF\1y)\1n\1cirst\1b, \1c\1hL\1y)\1n\1cast\1b, \1c\1hR\1y)\1n\1ceply\1b, ";
	// If the user is allowed to change to a different message area, then
	// include that option.
	if (allowChgMsgArea)
	{
		// If there's room for the private reply option, then include that
		// before the change area option.
		if (console.screen_columns >= 89)
			keyHelpText += "\1c\1hP\1y)\1n\1crivate reply\1b, ";
		keyHelpText += "\1c\1hC\1y)\1n\1chg area\1b, ";
	}
		// The user isn't allowed to change to a different message area.
		// Go ahead and include the private reply option.
		keyHelpText += "\1c\1hP\1y)\1n\1crivate reply\1b, ";
	}
	keyHelpText += "\1c\1hQ\1y)\1n\1cuit\1b, \1c\1h?\1g: \1c";
	// User input loop
	var writeMessage = true;
	var writePromptText = true;
	var continueOn = true;
	while (continueOn)
	{
		if (writeMessage)
			if (console.term_supports(USER_ANSI))
				console.clear("\1n");
			// Write the message header & message body to the screen
			this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1);
			console.print("\1n" + this.colors["msgBodyColor"]);
			console.putmsg(msgTextWrapped, P_NOATCODES);
		// Write the prompt text
		if (writePromptText)
			console.print(keyHelpText);
		// Default the writing of the message & input prompt to true for the
		// next iteration.
		writeMessage = true;
		writePromptText = true;
		// Input a key from the user and take action based on the keypress.
		//retObj.lastKeypress = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOECHO|K_NOSPIN);
		retObj.lastKeypress = getKeyWithESCChars(K_UPPER);
		switch (retObj.lastKeypress)
			case this.enhReaderKeys.deleteMessage: // Delete message
				console.crlf();
				// Prompt the user for confirmation to delete the message.
				// Note: this.PromptAndDeleteMessage() will check to see if the user
				// is a sysop or the message was posted by the user.
				// If the message was deleted, then exit this read method
				// and return KEY_RIGHT as the last keypress so that the
				// calling method will go to the next message/sub-board.
				// Otherwise (if the message was not deleted), refresh the
				// last 2 lines of the message on the screen.
				// TODO: For the DeleteMessage() call, pass the array of file
				// attachments for it to delete (i.e., msgInfo.attachments)
				var msgWasDeleted = this.PromptAndDeleteMessage(pOffset);
				if (msgWasDeleted)
				{
					var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset);
					continueOn = msgSearchObj.continueInputLoop;
					retObj.newMsgOffset = msgSearchObj.newMsgOffset;
					retObj.nextAction = msgSearchObj.nextAction;
					if (msgSearchObj.promptGoToNextArea)
					{
						if (console.yesno(this.text.goToNextMsgAreaPromptText))
						{
							// Let this method exit and let the caller go to the next sub-board
							continueOn = false;
							retObj.nextAction = ACTION_GO_NEXT_MSG;
						}
						else
							writeMessage = false; // No need to refresh the message
					}
				}
				break;
			case this.enhReaderKeys.selectMessage: // Select message (for batch delete, etc.)
				console.crlf();
				var selectMessage = !console.noyes("Select this message");
				this.ToggleSelectedMessage(this.subBoardCode, pOffset, selectMessage);
				break;
				// TODO: Write this?  Not sure yet if it makes much sense to
				// have batch delete in the reader interface.
				// Prompt the user for confirmation, and use
				// this.DeleteSelectedMessages() to mark the selected messages
				// as deleted.
				// Returns an object with the following properties:
				//  deletedAll: Boolean - Whether or not all messages were successfully marked
				//              for deletion
				//  failureList: An object containing indexes of messages that failed to get
				//               marked for deletion, indexed by internal sub-board code, then
				//               containing messages indexes as properties.  Reasons for failing
				//               to mark messages deleted can include the user not having permission
				//               to delete in a sub-board, failure to open the sub-board, etc.
				writeMessage = false; // No need to refresh the message
				break;
			case this.enhReaderKeys.editMsg: // Edit the message
					// Let the user edit the message if they want to
					var editReturnObj = this.EditExistingMsg(pOffset);
					// If the user confirmed editing the message, then see if the
					// message was edited and refresh the screen accordingly.
					if (editReturnObj.userConfirmed)
					{
						// If the message was edited, then refresh the text lines
						// array and update the other message-related variables.
						if (editReturnObj.msgEdited && (editReturnObj.newMsgIdx > -1))
						{
							// When the message is edited, the old message will be
							// deleted and the edited message will be posted as a new
							// message.  So we should return to the caller and have it
							// go directly to that new message.
							continueOn = false;
							retObj.newMsgOffset = editReturnObj.newMsgIdx;
						}
					}
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			case this.enhReaderKeys.showHelp: // Show help
				if (!console.term_supports(USER_ANSI))
				{
					console.crlf();
					console.crlf();
				}
				this.DisplayEnhancedReaderHelp(allowChgMsgArea, msgAndAttachmentInfo.attachments.length > 0);
				if (!console.term_supports(USER_ANSI))
				{
					console.crlf();
					console.crlf();
				}
				break;
			case this.enhReaderKeys.reply: // Reply to the message
			case this.enhReaderKeys.privateReply: // Private reply
				// If the user pressed the private reply key while reading private
				// mail, then do nothing (allow only the regular reply key to reply).
				// If not reading personal email, go ahead and let the user reply
				// with either the reply or private reply keypress.
				var privateReply = (retObj.lastKeypress == this.enhReaderKeys.privateReply);
				if (privateReply && this.readingPersonalEmail)
				{
					writeMessage = false; // Don't re-write the current message again
					writePromptText = false; // Don't write the prompt text again
				}
				else
				{
					console.crlf();
					// Get the message header with fields expanded so we can get the most info possible.
					//var extdMsgHdr = this.GetMsgHdrByAbsoluteNum(msgHeader.number, true);
					var msgbase = new MsgBase(this.subBoardCode);
					if (msgbase.open())
						var extdMsgHdr = msgbase.get_msg_header(false, msgHeader.number, true);
						msgbase.close();
						// Let the user reply to the message
						var replyRetObj = this.ReplyToMsg(extdMsgHdr, msgAndAttachmentInfo.msgText, privateReply, pOffset);
						retObj.userReplied = replyRetObj.postSucceeded;
						//retObj.msgNotReadable = replyRetObj.msgWasDeleted;
						var msgWasDeleted = replyRetObj.msgWasDeleted;
						if (msgWasDeleted)
							var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset);
							continueOn = msgSearchObj.continueInputLoop;
							retObj.newMsgOffset = msgSearchObj.newMsgOffset;
							retObj.nextAction = msgSearchObj.nextAction;
							if (msgSearchObj.promptGoToNextArea)
								if (console.yesno(this.text.goToNextMsgAreaPromptText))
								{
									// Let this method exit and let the caller go to the next sub-board
									continueOn = false;
									retObj.nextAction = ACTION_GO_NEXT_MSG;
								}
								else
									writeMessage = true; // We want to refresh the message on the screen
						console.print("\1n");
						console.crlf();
						console.print("\1h\1yFailed to open the sub-board.  Aborting.\1n");
						mswait(ERROR_PAUSE_WAIT_MS);
			case this.enhReaderKeys.postMsg: // Post a message
				if (!this.readingPersonalEmail)
				{
					// Let the user post a message.
					if (bbs.post_msg(this.subBoardCode))
						// TODO: If the user is doing a search, it might be
						// useful to search their new message and add it to
						// the search results if it's a match..  but maybe
						// not?

					console.pause();

					// We'll want to refresh the message & prompt text on the screen
					writeMessage = true;
					writePromptText = true;
				}
				else
				{
					// Don't write the current message or prompt text in the next iteration
					writeMessage = false;
					writePromptText = false;
				}
				break;
			// Numeric digit: The start of a number of a message to read
			case "0":
			case "1":
			case "2":
			case "3":
			case "4":
			case "5":
			case "6":
			case "7":
			case "8":
			case "9":
				console.crlf();
				// Put the user's input back in the input buffer to
				// be used for getting the rest of the message number.
				console.ungetstr(retObj.lastKeypress);
				// Prompt for the message number
				var msgNumInput = this.PromptForMsgNum(null, this.text.readMsgNumPromptText, false, ERROR_PAUSE_WAIT_MS, false);
				// Only allow reading the message if the message number is valid
				// and it's not the same message number that was passed in.
				if ((msgNumInput > 0) && (msgNumInput-1 != pOffset))
				{
					// If the message is marked as deleted, then output an error
					if (this.MessageIsDeleted(msgNumInput-1))
						console.print("\1n" + this.text.msgHasBeenDeletedText.replace("%d", msgNumInput) + "\1n");
						// Confirm with the user whether to read the message
						var readMsg = true;
						if (this.promptToReadMessage)
							readMsg = console.yesno("\1n" + this.colors["readMsgConfirmColor"]
													+ "Read message "
													+ this.colors["readMsgConfirmNumberColor"]
													+ msgNumInput + this.colors["readMsgConfirmColor"]
													+ ": Are you sure");
							retObj.newMsgOffset = msgNumInput - 1;
							retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
						}
					}
			case this.enhReaderKeys.prevMsgByTitle: // Previous message by title
			case this.enhReaderKeys.prevMsgByAuthor: // Previous message by author
			case this.enhReaderKeys.prevMsgByToUser: // Previous message by 'to user'
			case this.enhReaderKeys.prevMsgByThreadID: // Previous message by thread ID
				// Only allow this if we aren't doing a message search.
				if (!this.SearchingAndResultObjsDefinedForCurSub())
				{
					console.crlf(); // For the "Searching..." text
					var threadPrevMsgOffset = this.FindThreadPrevOffset(msgHeader,
																		keypressToThreadType(retObj.lastKeypress, this.enhReaderKeys),
						retObj.newMsgOffset = threadPrevMsgOffset;
						retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
			case this.enhReaderKeys.nextMsgByTitle: // Next message by title (subject)
			case this.enhReaderKeys.nextMsgByAuthor: // Next message by author
			case this.enhReaderKeys.nextMsgByToUser: // Next message by 'to user'
			case this.enhReaderKeys.nextMsgByThreadID: // Next message by thread ID
				// Only allow this if we aren't doing a message search.
				if (!this.SearchingAndResultObjsDefinedForCurSub())
				{
					console.crlf(); // For the "Searching..." text
					var threadNextMsgOffset = this.FindThreadNextOffset(msgHeader,
																		keypressToThreadType(retObj.lastKeypress, this.enhReaderKeys),
						retObj.newMsgOffset = threadNextMsgOffset;
						retObj.nextAction = ACTION_GO_SPECIFIC_MSG;
			case this.enhReaderKeys.previousMsg: // Previous message
				// TODO: Change the key for this?
				// Look for a prior message that isn't marked for deletion.  Even
				// if we don't find one, we'll still want to return from this
				// function (with message index -1) so that this script can go
				// onto the previous message sub-board/group.
				retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, false);
				var goToPrevMessage = false;
				if ((retObj.newMsgOffset > -1) || allowChgMsgArea)
				{
					if (retObj.newMsgOffset == -1 && !curMsgSubBoardIsLast())
						console.crlf();
						goToPrevMessage = console.yesno(this.text.goToPrevMsgAreaPromptText);
						// We're not at the beginning of the sub-board, so it's okay to exit this
						// method and go to the previous message.
						goToPrevMessage = true;
				}
				if (goToPrevMessage)
				{
					continueOn = false;
					retObj.nextAction = ACTION_GO_PREVIOUS_MSG;
				}
				break;
			case this.enhReaderKeys.nextMsg: // Next message
			case KEY_ENTER:
				// Look for a later message that isn't marked for deletion.  Even
				// if we don't find one, we'll still want to return from this
				// function (with message index -1) so that this script can go
				// onto the next message sub-board/group.
				retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, true);
				// Note: Unlike the left arrow key, we want to exit this method when
				// navigating to the next message, regardless of whether or not the
				// user is allowed to change to a different sub-board, so that processes
				// that require continuation (such as new message scan) can continue.
				// Still, if there are no more readable messages in the current sub-board
				// (and thus the user would go onto the next message area), prompt the
				// user whether they want to continue onto the next message area.
				if (retObj.newMsgOffset == -1 && !curMsgSubBoardIsLast())
				{
					console.print("\1n");
					console.crlf();
					// If configured to allow the user to post in the sub-board
					// instead of going to the next message area and we're not
					// scanning, then do so.
					if (this.readingPostOnSubBoardInsteadOfGoToNext && !this.doingMsgScan)
						// Ask the user if they want to post on the sub-board.
						// If they say yes, then do so before exiting.
						var grpNameAndDesc = this.GetGroupNameAndDesc();
						if (!console.noyes(format(this.text.postOnSubBoard, grpNameAndDesc.grpName, grpNameAndDesc.grpDesc)))
						if (console.yesno(this.text.goToNextMsgAreaPromptText))
						{
							// Let this method exit and let the caller go to the next sub-board
							continueOn = false;
							retObj.nextAction = ACTION_GO_NEXT_MSG;
						}
				}
				else
				{
					// We're not at the end of the sub-board, so it's okay to exit this
					// method and go to the next message.
					continueOn = false;
					retObj.nextAction = ACTION_GO_NEXT_MSG;
				}
				break;
			case this.enhReaderKeys.firstMsg: // First message
				// Only leave this function if we aren't already on the first message.
				if (pOffset > 0)
				{
					continueOn = false;
					retObj.nextAction = ACTION_GO_FIRST_MSG;
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			case this.enhReaderKeys.lastMsg: // Last message
				// Only leave this function if we aren't already on the last message.
				if (pOffset < this.NumMessages() - 1)
				{
					continueOn = false;
					retObj.nextAction = ACTION_GO_LAST_MSG;
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			case "-": // Go to the previous message area
				if (allowChgMsgArea)
				{
					continueOn = false;
					retObj.nextAction = ACTION_GO_PREV_MSG_AREA;
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			case "+": // Go to the next message area
				if (allowChgMsgArea || this.doingMultiSubBoardScan)
				{
					continueOn = false;
					retObj.nextAction = ACTION_GO_NEXT_MSG_AREA;
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			// H and K: Display the extended message header info/kludge lines
			// (for the sysop)
			case this.enhReaderKeys.showHdrInfo:
			case this.enhReaderKeys.showKludgeLines:
				if (gIsSysop)
				{
					console.crlf();
					// Get an array of the extended header info/kludge lines and then
					// display them.
					var extdHdrInfoLines = this.GetExtdMsgHdrInfo(msgHeader, (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines));
						for (var infoIter = 0; infoIter < extdHdrInfoLines.length; ++infoIter)
							console.print(extdHdrInfoLines[infoIter]);
						// There are no kludge lines for this message
						console.print(this.text.noKludgeLinesForThisMsgText);
						console.crlf();
						console.pause();
				}
				else // The user is not a sysop
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
				// Message list, change message area: Quit out of this input loop
				// and let the calling function, this.ReadMessages(), handle the
				// action.
			case this.enhReaderKeys.showMsgList: // Message list
				console.print("\1n");
				console.crlf();
				console.print("Loading...");
				retObj.nextAction = ACTION_DISPLAY_MSG_LIST;
				continueOn = false;
				break;
			case this.enhReaderKeys.chgMsgArea: // Change message area, if allowed
				if (allowChgMsgArea)
				{
					retObj.nextAction = ACTION_CHG_MSG_AREA;
			case this.enhReaderKeys.downloadAttachments: // Download attachments
				if (msgAndAttachmentInfo.attachments.length > 0)
				{
					console.print("\1n");
					console.crlf();
					console.print("\1c- Download Attached Files -\1n");
					// Note: sendAttachedFiles() will output a CRLF at the beginning.
					sendAttachedFiles(msgAndAttachmentInfo.attachments);

					// Ensure the message is refreshed on the screen
					writeMessage = true;
					writePromptText = true;
				}
				else
				{
					writeMessage = false;
					writePromptText = false;
				}
				break;
			case this.enhReaderKeys.saveToBBSMachine:
				// Save the message to the BBS machine - Only allow this
				// if the user is a sysop.
				if (gIsSysop)
				{
					console.crlf();
					console.print("\1n\1cFilename:\1h");
					var inputLen = console.screen_columns - 10; // 10 = "Filename:" length + 1
					var filename = console.getstr(inputLen, K_NOCRLF);
					console.print("\1n");
					console.crlf();
					if (filename.length > 0)
						var saveMsgRetObj = this.SaveMsgToFile(msgHeader, filename, true);
						if (saveMsgRetObj.succeeded)
							console.print("\1n\1cThe message has been saved.\1n");
						else
							console.print("\1n\1y\1hFailed: " + saveMsgRetObj.errorMsg + "\1n");
						mswait(ERROR_PAUSE_WAIT_MS);
						console.print("\1n\1y\1hMessage not exported\1n");
						mswait(ERROR_PAUSE_WAIT_MS);
					writeMessage = true;
				}
				else
					writeMessage = false;
				break;
			case this.enhReaderKeys.userEdit: // Edit the user who wrote the message
				if (gIsSysop)
				{
					console.print("\1n");
					console.crlf();
					console.print("- Edit user " + msgHeader.from);
					console.crlf();
					var editObj = editUser(msgHeader.from);
					if (editObj.errorMsg.length != 0)
						console.print("\1y\1h" + editObj.errorMsg + "\1n");
			case this.enhReaderKeys.forwardMsg: // Forward the message
				console.print("\1n");
				console.crlf();
				console.print("\1c- Forward message\1n");
				console.crlf();
				var retStr = this.ForwardMessage(msgHeader, messageText);
				if (retStr.length > 0)