Commit 48808509 authored by nightfox's avatar nightfox
Browse files

Version 1.07 beta 2 - Added a way to select multiple messages in the message...

Version 1.07 beta 2 - Added a way to select multiple messages in the message list and batch-delete selected messages (only if permissions allow deleting all messages).  This is most useful for personal mail when a user gets a lot of spam messages.  In the lightbar message list, the spacebar selects individual messages and CTRL-A is used to select/de-select all messages.  In the traditional UI message list, the S key is used for message selection.  CTRL-D then does a batch delete of selected messages in the message list.  The spacebar can also be used to select a message when reading it.

This is marked as 'beta', but I have tested the new features and they appear to be working as intended.  I will likely release a non-beta soon.
parent 512c760d
......@@ -101,6 +101,9 @@
* 2015-12-13 Eric Oulashin Version 1.06
* Releasing this version after testing showed it's
* working as expected
* 2015-12-19 Eric Oulashin Version 1.07 Beta
* Started working on a way of tagging message (i.e., to
* do a batch delete).
*/
 
/* Command-line arguments (in -arg=val format, or -arg format to enable an
......@@ -192,8 +195,8 @@ if (system.version_num < 31500)
}
 
// Reader version information
var READER_VERSION = "1.06";
var READER_DATE = "2015-12-13";
var READER_VERSION = "1.07 Beta 2";
var READER_DATE = "2015-12-23";
 
// Keyboard key codes for displaying on the screen
var UP_ARROW = ascii(24);
......@@ -685,6 +688,10 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 
// String lengths for the columns to write
// Fixed field widths: Message number, date, and time
// TODO: It might be good to figure out the longest message number for a
// sub-board and set the message number length dynamically. It would have
// to change whenever the user changes to a different sub-board, and the
// message list format string would have to change too.
this.MSGNUM_LEN = 4;
this.DATE_LEN = 10; // i.e., YYYY-MM-DD
this.TIME_LEN = 8; // i.e., HH:MM:SS
......@@ -765,9 +772,12 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.text.abortedText = "\1n\1y\1h\1iAborted\1n";
this.text.loadingPersonalMailText = "\1n\1cLoading %s...";
this.text.msgDelConfirmText = "\1n\1h\1yDelete\1n\1c message #\1h%d\1n\1c: Are you sure";
this.text.delSelectedMsgsConfirmText = "\1n\1h\1yDelete selected messages: Are you sure";
this.text.msgDeletedText = "\1n\1cMessage #\1h%d\1n\1c has been marked for deletion.";
this.text.selectedMsgsDeletedText = "\1n\1cSelected messages have been marked for deletion.";
this.text.cannotDeleteMsgText_notYoursNotASysop = "\1n\1h\1wCannot delete message #\1y%d \1wbecause it's not yours or you're not a sysop.";
this.text.cannotDeleteMsgText_notLastPostedMsg = "\1n\1h\1g* \1yCannot delete message #%d. You can only delete your last message in this area.\1n";
this.text.cannotDeleteAllSelectedMsgsText = "\1n\1y\1h* Cannot delete all selected messages";
this.text.msgEditConfirmText = "\1n\1cEdit message #\1h%d\1n\1c: Are you sure";
this.text.noPersonalEmailText = "\1n\1cYou have no messages.";
 
......@@ -817,6 +827,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.AbsMsgNumToIdx = DigDistMsgReader_AbsMsgNumToIdx;
this.IdxToAbsMsgNum = DigDistMsgReader_IdxToAbsMsgNum;
this.NumMessages = DigDistMsgReader_NumMessages;
this.NonDeletedMessagesExist = DigDistMsgReader_NonDeletedMessagesExist;
this.HighestMessageNum = DigDistMsgReader_HighestMessageNum;
this.IsValidMessageNum = DigDistMsgReader_IsValidMessageNum;
this.PromptForMsgNum = DigDistMsgReader_PromptForMsgNum;
......@@ -835,7 +846,8 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.MessageIsLastFromUser = DigDistMsgReader_MessageIsLastFromUser;
this.DisplayEnhReaderError = DigDistMsgReader_DisplayEnhReaderError;
this.EnhReaderPromptYesNo = DigDistMsgReader_EnhReaderPromptYesNo;
this.DeleteMessage = DigDistMsgReader_DeleteMessage;
this.PromptAndDeleteMessage = DigDistMsgReader_PromptAndDeleteMessage;
this.PromptAndDeleteSelectedMessages = DigDistMsgReader_PromptAndDeleteSelectedMessages;
this.GetExtdMsgHdrInfo = DigDistMsgReader_GetExtdMsgHdrInfo;
this.GetMsgInfoForEnhancedReader = DigDistMsgReader_GetMsgInfoForEnhancedReader;
this.GetLastReadMsgIdx = DigDistMsgReader_GetLastReadMsgIdx;
......@@ -844,6 +856,12 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.RemoveFromSearchResults = DigDistMsgReader_RemoveFromSearchResults;
this.FindThreadNextOffset = DigDistMsgReader_FindThreadNextOffset;
this.FindThreadPrevOffset = DigDistMsgReader_FindThreadPrevOffset;
this.SaveMsgToFile = DigDistMsgReader_SaveMsgToFile;
this.ToggleSelectedMessage = DigDistMsgReader_ToggleSelectedMessage;
this.MessageIsSelected = DigDistMsgReader_MessageIsSelected;
this.AllSelectedMessagesCanBeDeleted = DigDistMsgReader_AllSelectedMessagesCanBeDeleted;
this.DeleteSelectedMessages = DigDistMsgReader_DeleteSelectedMessages;
this.NumSelectedMessages = DigDistMsgReader_NumSelectedMessages;
 
// 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
......@@ -1198,7 +1216,6 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.CalcMsgListScreenIdxVarsFromMsgNum = DigDistMsgReader_CalcMsgListScreenIdxVarsFromMsgNum;
// A method for validating a user's choice of message area
this.ValidateMsgAreaChoice = DigDistMsgReader_ValidateMsgAreaChoice;
this.SaveMsgToFile = DigDistMsgReader_SaveMsgToFile;
 
// printf strings for message group/sub-board lists
// Message group information (printf strings)
......@@ -1284,6 +1301,12 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
// 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.
this.selectedMessages = new Object();
}
 
// For the DigDistMsgReader class: Sets the subBoardCode property and also
......@@ -1304,33 +1327,36 @@ function DigDistMsgReader_SetSubBoardCode(pSubCode)
// 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_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib)
// pSubBoardCode: Optional - An internal sub-board code. If not specified, then
// this method will default to this.subBoardCode.
function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoardCode)
{
if (typeof(pMsgIndex) != "number")
return;
if (typeof(pMsgIndex) != "number")
return;
 
if (this.msgSearchHdrs.hasOwnProperty(this.subBoardCode))
{
var subCode = (typeof(pSubBoardCode) == "string" ? pSubBoardCode : this.subBoardCode);
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;
this.msgbase.put_msg_header(true, msgOffsetFromHdr, this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex]);
}
}
else
{
var msgHeader = this.GetMsgHdrByIdx(pMsgIndex);
if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex))
{
this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex] = msgHeader;
this.msgbase.put_msg_header(true, msgHeader.offset, msgHeader);
}
}
}
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;
this.msgbase.put_msg_header(true, msgOffsetFromHdr, this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex]);
}
}
else
{
var msgHeader = this.GetMsgHdrByIdx(pMsgIndex);
if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex))
{
this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex] = msgHeader;
this.msgbase.put_msg_header(true, msgHeader.offset, msgHeader);
}
}
}
}
 
// For the DigDistMsgReader class: Inputs search text from the user, then reads/lists
......@@ -2905,7 +2931,7 @@ function DigDistMsgReader_ListMessages_Traditional(pReturnOnMsgSelect, pAllowChg
// DeleteMessage() method, which will prompt the user for
// confirmation and delete the message if confirmed.
if (msgNum > 0)
this.DeleteMessage(msgNum-1);
this.PromptAndDeleteMessage(msgNum-1);
 
// Refresh the top header on the screen for continuing to list
// messages.
......@@ -2975,6 +3001,82 @@ function DigDistMsgReader_ListMessages_Traditional(pReturnOnMsgSelect, pAllowChg
}
}
}
// 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();
}
}
else
{
// If pReturnOnMsgSelect is true and the user selected a message to
......@@ -3597,17 +3699,39 @@ function DigDistMsgReader_ListMessages_Lightbar(pReturnOnMsgSelect, pAllowChgSub
console.print("\1n");
console.clearline();
 
// The DeleteMessage() methdo will prompt the user for confirmation
// The PromptAndDeleteMessage() method will prompt the user for confirmation
// to delete the message and then delete it if confirmed.
this.DeleteMessage(this.lightbarListSelectedMsgIdx, { x: 1, y: console.screen_rows});
// 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
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
......@@ -3715,6 +3839,110 @@ function DigDistMsgReader_ListMessages_Lightbar(pReturnOnMsgSelect, pAllowChgSub
}
}
}
// 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
if (messageIndex != this.lightbarListSelectedMsgIdx)
{
console.gotoxy(this.MSGNUM_LEN+1, currentRow);
console.print("\1n");
if (this.MessageIsSelected(this.subBoardCode, messageIndex))
console.print(this.colors.selectedMsgMarkColor + CHECK_CHAR + "\1n");
else
console.print(" \1n");
}
++currentRow;
}
}
// Refresh the help line and move the cursor back to its original position
console.gotoxy(1, console.screen_rows);
DisplayHelpLine(this.msgListLightbarModeHelpLine);
console.gotoxy(originalCurpos);
}
// Ctrl-D: Batch delete (for selected messages)
else if (userInput == CTRL_D)
{
var originalCurpos = console.getxy();
if (this.NumSelectedMessages() > 0)
{
console.gotoxy(1, console.screen_rows);
console.print("\1n");
console.clearline();
// The PromptAndDeleteSelectedMessages() method will prompt the user for confirmation
// to delete the message and then delete it if confirmed.
this.PromptAndDeleteSelectedMessages({ 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 messages to list, so 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
}
}
else
{
// There are no selected messages
writeWithPause(1, console.screen_rows, "\1n\1h\1yThere are no selected messages.",
ERROR_PAUSE_WAIT_MS, "\1n", true);
// Refresh the help line and move the cursor back to its original position
DisplayHelpLine(this.msgListLightbarModeHelpLine);
console.gotoxy(originalCurpos);
}
}
}
 
return retObj;
......@@ -3724,8 +3952,8 @@ function DigDistMsgReader_ListMessages_Lightbar(pReturnOnMsgSelect, pAllowChgSub
//
// Parameters:
// pMsgHeader: The message header object, returned by MsgBase.get_msg_header().
// pHighlight: Optional boolean - Whether or not to highlight the line or
// use the standard colors.
// pHighlight: Optional boolean - Whether or not to highlight the line (true) or
// use the standard colors (false).
// pMsgNum: Optional - A number to use for the message instead of the number/offset
// in the message header
function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum)
......@@ -3737,27 +3965,34 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum)
return;
 
var highlight = false;
if (typeof(pHighlight) != "undefined")
if (typeof(pHighlight) == "boolean")
highlight = pHighlight;
 
// Determine if the message has been deleted.
var msgDeleted = ((pMsgHeader.attr & MSG_DELETE) == MSG_DELETE);
// Get the message's import date & time as strings. If
// this.msgList_displayMessageDateImported is true, use the message import date.
// Otherwise, use the message written date.
var sDate;
var sTime;
if (this.msgList_displayMessageDateImported)
{
sDate = strftime("%Y-%m-%d", pMsgHeader.when_imported_time);
sTime = strftime("%H:%M:%S", pMsgHeader.when_imported_time);
}
else
{
sDate = strftime("%Y-%m-%d", pMsgHeader.when_written_time);
sTime = strftime("%H:%M:%S", pMsgHeader.when_written_time);
}
var msgNum = (typeof(pMsgNum) == "number" ? pMsgNum : pMsgHeader.offset+1);
 
// Get the message's import date & time as strings. If
// this.msgList_displayMessageDateImported is true, use the message import date.
// Otherwise, use the message written date.
var sDate;
var sTime;
if (this.msgList_displayMessageDateImported)
{
sDate = strftime("%Y-%m-%d", pMsgHeader.when_imported_time);
sTime = strftime("%H:%M:%S", pMsgHeader.when_imported_time);
}
else
{
sDate = strftime("%Y-%m-%d", pMsgHeader.when_written_time);
sTime = strftime("%H:%M:%S", pMsgHeader.when_written_time);
}
// Determine if the message has been deleted.
var msgDeleted = ((pMsgHeader.attr & MSG_DELETE) == MSG_DELETE);
// msgIndicatorChar will contain (possibly) a character to display after
// the message number to indicate whether it has been deleted, selected,
// etc. If not, then it will just be a space.
var msgIndicatorChar = " ";
 
// Write the message header information.
// Note: The message header has the following fields:
......@@ -3768,36 +4003,42 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum)
// 'subject': The message subject (string)
// 'date': The date - Full text (string)
// To access one of these, use brackets; i.e., msgHeader['to']
var msgNum = (typeof(pMsgNum) == "number" ? pMsgNum : pMsgHeader.offset+1);
if (highlight)
{
if (msgDeleted)
msgIndicatorChar = "\1n\1r\1h\1i" + this.colors.msgListHighlightBkgColor + "*\1n";
else if (this.MessageIsSelected(this.subBoardCode, msgNum-1))
msgIndicatorChar = "\1n" + this.colors.selectedMsgMarkColor + this.colors.msgListHighlightBkgColor + CHECK_CHAR + "\1n";
printf(this.sMsgInfoFormatHighlightStr,
msgNum,
(msgDeleted ? "\1r\1i*\1n\1h" + this.colors["msgListHighlightBkgColor"] : " "),
pMsgHeader.from.substr(0, this.FROM_LEN),
pMsgHeader.to.substr(0, this.TO_LEN),
pMsgHeader.subject.substr(0, this.SUBJ_LEN),
sDate, sTime);
msgNum,
msgIndicatorChar,
pMsgHeader.from.substr(0, this.FROM_LEN),
pMsgHeader.to.substr(0, this.TO_LEN),
pMsgHeader.subject.substr(0, this.SUBJ_LEN),
sDate, sTime);
}
else
{
if (msgDeleted)
msgIndicatorChar = "\1n\1r\1h\1i*\1n";
else if (this.MessageIsSelected(this.subBoardCode, msgNum-1))
msgIndicatorChar = "\1n" + this.colors.selectedMsgMarkColor + CHECK_CHAR + "\1n";
// Determine whether to use the normal, "to-user", or "from-user" format string.
// The differences are the colors. Then, output the message information line.
var toNameUpper = pMsgHeader.to.toUpperCase();
var msgToUser = ((toNameUpper == user.alias.toUpperCase()) || (toNameUpper == user.name.toUpperCase()) || (toNameUpper == user.handle.toUpperCase()));
var fromNameUpper = pMsgHeader.from.toUpperCase();
var msgIsFromUser = ((fromNameUpper == user.alias.toUpperCase()) || (fromNameUpper == user.name.toUpperCase()) || (fromNameUpper == user.handle.toUpperCase()));
printf((msgToUser ? this.sMsgInfoToUserFormatStr :
(msgIsFromUser ? this.sMsgInfoFromUserFormatStr :
this.sMsgInfoFormatStr)),
msgNum,
(msgDeleted ? "\1r\1i*\1n" : " "),
pMsgHeader.from.substr(0, this.FROM_LEN),
pMsgHeader.to.substr(0, this.TO_LEN),
pMsgHeader.subject.substr(0, this.SUBJ_LEN),
sDate, sTime);
}
console.cleartoeol("\1"); // To clear away any extra text that may have been entered by the user
printf((msgToUser ? this.sMsgInfoToUserFormatStr : (msgIsFromUser ? this.sMsgInfoFromUserFormatStr : this.sMsgInfoFormatStr)),
msgNum,
msgIndicatorChar,
pMsgHeader.from.substr(0, this.FROM_LEN),
pMsgHeader.to.substr(0, this.TO_LEN),
pMsgHeader.subject.substr(0, this.SUBJ_LEN),
sDate, sTime);
}
console.cleartoeol("\1n"); // To clear away any extra text that may have been entered by the user
}
// For the traditional interface of DigDistMsgListerClass: Prompts the user to
// continue or read a message (by number).
......@@ -3833,7 +4074,7 @@ function DigDistMsgReader_PromptContinueOrReadMsg(pStart, pEnd, pReturnOnMsgSele
// depending whether we're at the beginning, in the middle, or at
// the end of the message list.
var userInput = "";
var allowedKeys = "?G"; // ? = help, G = Go to message #
var allowedKeys = "?GS"; // ? = help, G = Go to message #, S = Select message(s), Ctrl-D: Batch delete
if (allowChgSubBoard)
allowedKeys += "C"; // Change to another message area
if (this.CanDelete() || this.CanDeleteLastMsg())
......@@ -3868,9 +4109,15 @@ function DigDistMsgReader_PromptContinueOrReadMsg(pStart, pEnd, pReturnOnMsgSele
console.print(this.sContinuePrompt);
allowedKeys += "FLNPQ";
}
// Get the user's input. Allow the keys in allowedKeys or a number from 1
// Get the user's input. Allow CTRL-D (batch delete) without echoing it.
// If the user didn't press CTRL-L, allow the keys in allowedKeys or a number from 1
// to the highest message number.
userInput = console.getkeys(allowedKeys, this.HighestMessageNum()).toString();
userInput = console.getkey(K_NOECHO);
if (userInput != CTRL_D)
{
console.ungetstr(userInput);
userInput = console.getkeys(allowedKeys, this.HighestMessageNum()).toString();
}
if (userInput == "Q")
continueOn = false;
 
......@@ -4105,8 +4352,11 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
enhReaderKeys.nextMsgByThreadID = ")";
enhReaderKeys.prevSubBoard = "-";
enhReaderKeys.nextSubBoard = "+";
enhReaderKeys.downloadAttachments = CTRL_D;
enhReaderKeys.downloadAttachments = CTRL_A;
enhReaderKeys.saveToBBSMachine = CTRL_S;
enhReaderKeys.deleteMessage = KEY_DEL;
enhReaderKeys.selectMessage = " ";
enhReaderKeys.batchDelete = CTRL_D;
 
// This function converts a thread navigation key character to its
// corresponding thread type value
......@@ -4224,7 +4474,7 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
retObj.lastKeypress = scrollRetObj.lastKeypress;
switch (retObj.lastKeypress)
{
case KEY_DEL: // Delete message
case enhReaderKeys.deleteMessage: // Delete message
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.
......@@ -4237,15 +4487,15 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
var promptPos = this.EnhReaderPrepLast2LinesForPrompt();
 
// Prompt the user for confirmation to delete the message.
// Note: this.DeleteMessage() will check to see if the user
// 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.
var msgWasDeleted = this.DeleteMessage(pOffset, promptPos, true, this.msgAreaWidth,
true, msgInfo.attachments);
var msgWasDeleted = this.PromptAndDeleteMessage(pOffset, promptPos, true, this.msgAreaWidth,
true, msgInfo.attachments);
if (msgWasDeleted)
{
var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset);
......@@ -4272,6 +4522,30 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
writeMessage = false;
}
break;
case enhReaderKeys.selectMessage: // Select message (for batch delete, etc.)
var originalCurpos = console.getxy();
var promptPos = this.EnhReaderPrepLast2LinesForPrompt();
if (this.EnhReaderPromptYesNo("Select this message", msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr, solidBlockStartRow, numSolidScrollBlocks, true))
this.ToggleSelectedMessage(this.subBoardCode, pOffset, true);
else
this.ToggleSelectedMessage(this.subBoardCode, pOffset, false);
writeMessage = false; // No need to refresh the message
break;
case enhReaderKeys.batchDelete:
// TODO: Write this
// 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 "E": // Edit the messaage
if (this.CanEdit())
{
......@@ -4900,10 +5174,10 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
retObj.lastKeypress = getKeyWithESCChars(K_UPPER/*|K_NOCRLF|K_NOECHO|K_NOSPIN*/);
switch (retObj.lastKeypress)
{
case KEY_DEL: // Delete message
case enhReaderKeys.deleteMessage: // Delete message
console.crlf();
// Prompt the user for confirmation to delete the message.
// Note: this.DeleteMessage() will check to see if the user
// 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
......@@ -4912,7 +5186,7 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
// 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.DeleteMessage(pOffset);
var msgWasDeleted = this.PromptAndDeleteMessage(pOffset);
if (msgWasDeleted)
{
var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset);
......@@ -4932,6 +5206,26 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea)
}
}
break;