diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js index fc2dde7720aa4bbd484d17f96bd1daeed4d00726..f552c0125e2cede21ee6b82a89ff2edcdabab9c7 100644 --- a/xtrn/DDMsgReader/DDMsgReader.js +++ b/xtrn/DDMsgReader/DDMsgReader.js @@ -47,6 +47,10 @@ * 2022-07-09 Eric Oulashin Version 1.52 * Mouse click support for the bottom help lines in scrollable mode * (thanks to help from Nelgin) + * 2022-07-18 Eric Oulashin Version 1.53 + * Deleted messages can now be un-marked for deletion from the message + * list with the U key (if the user has delete permissions). Also, the reader now + * honors the system setting for whether users can view deleted messages. */ "use strict"; @@ -151,8 +155,8 @@ var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a'); // Reader version information -var READER_VERSION = "1.52"; -var READER_DATE = "2022-07-09"; +var READER_VERSION = "1.53"; +var READER_DATE = "2022-07-18"; // Keyboard key codes for displaying on the screen var UP_ARROW = ascii(24); @@ -749,8 +753,8 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.MessageIsLastFromUser = DigDistMsgReader_MessageIsLastFromUser; this.DisplayEnhReaderError = DigDistMsgReader_DisplayEnhReaderError; this.EnhReaderPromptYesNo = DigDistMsgReader_EnhReaderPromptYesNo; - this.PromptAndDeleteMessage = DigDistMsgReader_PromptAndDeleteMessage; - this.PromptAndDeleteSelectedMessages = DigDistMsgReader_PromptAndDeleteSelectedMessages; + this.PromptAndDeleteOrUndeleteMessage = DigDistMsgReader_PromptAndDeleteOrUndeleteMessage; + this.PromptAndDeleteOrUndeleteSelectedMessages = DigDistMsgReader_PromptAndDeleteOrUndeleteSelectedMessages; this.GetExtdMsgHdrInfo = DigDistMsgReader_GetExtdMsgHdrInfo; this.GetMsgInfoForEnhancedReader = DigDistMsgReader_GetMsgInfoForEnhancedReader; this.GetLastReadMsgIdxAndNum = DigDistMsgReader_GetLastReadMsgIdxAndNum; @@ -762,7 +766,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.ToggleSelectedMessage = DigDistMsgReader_ToggleSelectedMessage; this.MessageIsSelected = DigDistMsgReader_MessageIsSelected; this.AllSelectedMessagesCanBeDeleted = DigDistMsgReader_AllSelectedMessagesCanBeDeleted; - this.DeleteSelectedMessages = DigDistMsgReader_DeleteSelectedMessages; + this.DeleteOrUndeleteSelectedMessages = DigDistMsgReader_DeleteOrUndeleteSelectedMessages; this.NumSelectedMessages = DigDistMsgReader_NumSelectedMessages; this.ForwardMessage = DigDistMsgReader_ForwardMessage; this.VoteOnMessage = DigDistMsgReader_VoteOnMessage; @@ -927,9 +931,13 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) abortedText: "\x01n\x01y\x01h\x01iAborted\x01n", loadingPersonalMailText: "\x01n\x01cLoading %s...", msgDelConfirmText: "\x01n\x01h\x01yDelete\x01n\x01c message #\x01h%d\x01n\x01c: Are you sure", + msgUndelConfirmText: "\x01n\x01h\x01yUndelete\x01n\x01c message #\x01h%d\x01n\x01c: Are you sure", delSelectedMsgsConfirmText: "\x01n\x01h\x01yDelete selected messages: Are you sure", + undelSelectedMsgsConfirmText: "\x01n\x01h\x01yUndelete selected messages: Are you sure", msgDeletedText: "\x01n\x01cMessage #\x01h%d\x01n\x01c has been marked for deletion.", + msgUndeletedText: "\x01n\x01cMessage #\x01h%d\x01n\x01c has been unmarked for deletion.", selectedMsgsDeletedText: "\x01n\x01cSelected messages have been marked for deletion.", + selectedMsgsUndeletedText: "\x01n\x01cSelected messages have been unmarked for deletion.", cannotDeleteMsgText_notYoursNotASysop: "\x01n\x01h\x01wCannot delete message #\x01y%d \x01wbecause it's not yours or you're not a sysop.", cannotDeleteMsgText_notLastPostedMsg: "\x01n\x01h\x01g* \x01yCannot delete message #%d. You can only delete your last message in this area.\x01n", cannotDeleteAllSelectedMsgsText: "\x01n\x01y\x01h* Cannot delete all selected messages", @@ -999,6 +1007,17 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) }; if (user.is_sysop) this.enhReaderKeys.validateMsg = "A"; + // Some key bindings for the message list (not necessarily all of them) + this.msgListKeys = { + deleteMessage: KEY_DEL, + undeleteMessage: "U", + batchDelete: CTRL_D, + editMsg: "E", + goToMsg: "G", + chgMsgArea: "C", + quit: "Q", + showHelp: "?" + }; // Whether or not to display avatars this.displayAvatars = true; @@ -1570,13 +1589,15 @@ function msgNumToIdxFromMsgbase(pSubCode, pMsgNum) // pMsgIndex: The index (0-based) of the message header // pAttrib: Optional - An attribute to apply. If this is is not specified, // then the message header will be retrieved from the message base. -// pSubBoardCode: Optional - An internal sub-board code. If not specified, then -// this method will default to this.subBoardCode. -function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoardCode) +// pApply: Optional boolean - Whether or not to apply the attribute or remove it. Defaults to true. +// pSubBoardCode: Optional - An internal sub-board code. If not specified, then +// this method will default to this.subBoardCode. +function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pApply, pSubBoardCode) { if (typeof(pMsgIndex) != "number") return; + var applyAttr = (typeof(pApply) === "boolean" ? pApply : true); var subCode = (typeof(pSubBoardCode) == "string" ? pSubBoardCode : this.subBoardCode); var msgbase = new MsgBase(subCode); if (msgbase.open()) @@ -1588,7 +1609,10 @@ function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoar { if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex)) { - this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr | pAttrib; + if (applyAttr) + this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr | pAttrib; + else + this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].attr ^ pAttrib; var msgOffsetFromHdr = this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex].offset; msgbase.put_msg_header(true, msgOffsetFromHdr, this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex]); } @@ -1614,13 +1638,20 @@ function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoar // pMsgIndex: The index (0-based) of the message header // pAttrib: Optional - An attribute to apply. If this is is not specified, // then the message header will be retrieved from the message base. -function DigDistMsgReader_RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib) +// pApply: Optional boolean - Whether or not to apply the attribute or remove it. Defaults to true. +function DigDistMsgReader_RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib, pApply) { if (typeof(pMsgIndex) != "number") return; if ((pMsgIndex >= 0) && (pMsgIndex < this.hdrsForCurrentSubBoard.length)) - this.hdrsForCurrentSubBoard[pMsgIndex].attr = this.hdrsForCurrentSubBoard[pMsgIndex].attr | pAttrib; + { + var applyAttr = (typeof(pApply) === "boolean" ? pApply : true); + if (applyAttr) + this.hdrsForCurrentSubBoard[pMsgIndex].attr = this.hdrsForCurrentSubBoard[pMsgIndex].attr | pAttrib; + else + this.hdrsForCurrentSubBoard[pMsgIndex].attr = this.hdrsForCurrentSubBoard[pMsgIndex].attr ^ pAttrib; + } } // For the DigDistMsgReader class: Refreshes a message header in the saved message @@ -1630,12 +1661,14 @@ function DigDistMsgReader_RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib) // pMsgIndex: The index (0-based) of the message header // pAttrib: Optional - An attribute to apply. If this is is not specified, // then the message header will be retrieved from the message base. -// pSubBoardCode: Optional - An internal sub-board code. If not specified, then -// this method will default to this.subBoardCode. -function DigDistMsgReader_RefreshHdrInSavedArrays(pMsgIndex, pAttrib, pSubBoardCode) +// pApply: Optional boolean - Whether or not to apply the attribute or remove it. Defaults to true. +// pSubBoardCode: Optional - An internal sub-board code. If not specified, then +// this method will default to this.subBoardCode. +function DigDistMsgReader_RefreshHdrInSavedArrays(pMsgIndex, pAttrib, pApply, pSubBoardCode) { - this.RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pSubBoardCode); - this.RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib); + var applyAttr = (typeof(pApply) === "boolean" ? pApply : true); + this.RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, applyAttr, pSubBoardCode); + this.RefreshHdrInSubBoardHdrs(pMsgIndex, pAttrib, applyAttr); } // For the DigDistMsgReader class: Inputs search text from the user, then reads/lists @@ -3168,7 +3201,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // DeleteMessage() method, which will prompt the user for // confirmation and delete the message if confirmed. if (msgNum > 0) - this.PromptAndDeleteMessage(msgNum-1); + this.PromptAndDeleteOrUndeleteMessage(msgNum-1, null, true); // Refresh the top header on the screen for continuing to list // messages. @@ -3289,14 +3322,14 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) console.crlf(); if (this.NumSelectedMessages() > 0) { - // The PromptAndDeleteSelectedMessages() method will prompt the user for confirmation + // The PromptAndDeleteOrUndeleteSelectedMessages() method will prompt the user for confirmation // to delete the message and then delete it if confirmed. - this.PromptAndDeleteSelectedMessages(); + this.PromptAndDeleteOrUndeleteSelectedMessages(null, true); - // In case all messages were deleted, if that's the case, show - // an appropriate message and don't continue listing messages. + // In case all messages were deleted, if the user can't view deleted messages, + // show an appropriate message and don't continue listing messages. //if (this.NumMessages(true) == 0) - if (!this.NonDeletedMessagesExist()) + if (!this.NonDeletedMessagesExist() && !canViewDeletedMsgs()) { continueOn = false; // Note: The following doesn't seem to be necessary, since @@ -3506,7 +3539,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // and the user would have pressed one of the additional quit keys set // up for the menu. So look at the menu's lastUserInput and do the // appropriate thing. - else if ((lastUserInputUpper == "Q") || (lastUserInputUpper == KEY_ESC)) // Quit + else if ((lastUserInputUpper == this.msgListKeys.quit) || (lastUserInputUpper == KEY_ESC)) // Quit { continueOn = false; retObj.lastUserInput = "Q"; // So the reader will quit out @@ -3573,7 +3606,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) } } // DEL key: Delete a message - else if (lastUserInputUpper == KEY_DEL) + else if (lastUserInputUpper == this.msgListKeys.deleteMessage) { if (this.CanDelete() || this.CanDeleteLastMsg()) { @@ -3581,14 +3614,14 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) console.print("\x01n"); console.clearline(); - // The PromptAndDeleteMessage() method will prompt the user for confirmation + // The PromptAndDeleteOrUndeleteMessage() 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}); + this.PromptAndDeleteOrUndeleteMessage(this.lightbarListSelectedMsgIdx, { x: 1, y: console.screen_rows}, true); - // In case all messages were deleted, if that's the case, show - // an appropriate message and don't continue listing messages. + // In case all messages were deleted, if the user can't view deleted messages, + // show an appropriate message and don't continue listing messages. //if (this.NumMessages(true) == 0) - if (!this.NonDeletedMessagesExist()) + if (!this.NonDeletedMessagesExist() && !canViewDeletedMsgs()) continueOn = false; else { @@ -3600,7 +3633,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) } } // E: Edit a message - else if (lastUserInputUpper == "E") + else if (lastUserInputUpper == this.msgListKeys.editMsg) { if (this.CanEdit()) { @@ -3628,7 +3661,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) drawMenu = false; // No need to re-draw the menu } // G: Go to a specific message by # (highlight or place that message on the top) - else if (lastUserInputUpper == "G") + else if (lastUserInputUpper == this.msgListKeys.goToMsg) { // Move the cursor to the bottom of the screen and // prompt the user for a message number. @@ -3673,7 +3706,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) DisplayHelpLine(this.msgListLightbarModeHelpLine); } // C: Change to another message area (sub-board) - else if (lastUserInputUpper == "C") + else if (lastUserInputUpper == this.msgListKeys.chgMsgArea) { if (allowChgSubBoard && (this.subBoardCode != "mail")) { @@ -3711,7 +3744,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) else drawMenu = false; // No need to re-draw the menu } - else if (lastUserInputUpper == "?") // Show help + else if (lastUserInputUpper == this.msgListKeys.showHelp) // Show help { console.clear("\x01n"); this.DisplayMsgListHelp(allowChgSubBoard, true); @@ -3761,38 +3794,66 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // Ctrl-D: Batch delete (for selected messages) else if (lastUserInputUpper == CTRL_D) { - if (this.NumSelectedMessages() > 0) + if (this.CanDelete() || this.CanDeleteLastMsg()) { - console.gotoxy(1, console.screen_rows); - console.print("\x01n"); - console.clearline(); + if (this.NumSelectedMessages() > 0) + { + console.gotoxy(1, console.screen_rows); + console.print("\x01n"); + 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}); + // The PromptAndDeleteOrUndeleteSelectedMessages() method will prompt the user for confirmation + // to delete the message and then delete it if confirmed. + this.PromptAndDeleteOrUndeleteSelectedMessages({ x: 1, y: console.screen_rows}, true); - // 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; + // In case all messages were deleted, if the user can't view deleted messages, + // show an appropriate message and don't continue listing messages. + //if (this.NumMessages(true) == 0) + if (!this.NonDeletedMessagesExist() && !canViewDeletedMsgs()) + continueOn = false; + else + { + // There are still messages to list, so refresh the header & help lines + this.WriteMsgListScreenTopHeader(); + DisplayHelpLine(this.msgListLightbarModeHelpLine); + } + } else { - // There are still messages to list, so refresh the header & help lines - this.WriteMsgListScreenTopHeader(); + // There are no selected messages + writeWithPause(1, console.screen_rows, "\x01n\x01h\x01yThere are no selected messages.", + ERROR_PAUSE_WAIT_MS, "\x01n", true); + // Refresh the help line DisplayHelpLine(this.msgListLightbarModeHelpLine); } } - else + } + // U: Undelete message(s) + else if (lastUserInputUpper == this.msgListKeys.undeleteMessage) + { + if (this.CanDelete() || this.CanDeleteLastMsg()) { - // There are no selected messages - writeWithPause(1, console.screen_rows, "\x01n\x01h\x01yThere are no selected messages.", - ERROR_PAUSE_WAIT_MS, "\x01n", true); - // Refresh the help line + console.gotoxy(1, console.screen_rows); + console.print("\x01n"); + console.clearline(); + if (this.NumSelectedMessages() > 0) + { + // Multi-message undelete + this.PromptAndDeleteOrUndeleteSelectedMessages({ x: 1, y: console.screen_rows}, false); + } + else + { + // Single-message undelete + this.PromptAndDeleteOrUndeleteMessage(this.lightbarListSelectedMsgIdx, { x: 1, y: console.screen_rows}, false); + } + + // Refresh the header & help line. + this.WriteMsgListScreenTopHeader(); DisplayHelpLine(this.msgListLightbarModeHelpLine); } } // S: Sorting options + // TODO else if (lastUserInputUpper == "S") { // Refresh the help line @@ -3885,7 +3946,7 @@ function DigDistMsgReader_CreateLightbarMsgListMenu() // respond to these keys var additionalQuitKeys = "EeqQgGcCsS ?0123456789" + CTRL_A + CTRL_D; if (this.CanDelete() || this.CanDeleteLastMsg()) - additionalQuitKeys += KEY_DEL; + additionalQuitKeys += ("uU" + KEY_DEL); // U: Undelete message if (this.CanEdit()) additionalQuitKeys += "E"; msgListMenu.AddAdditionalQuitKeys(additionalQuitKeys); @@ -4213,10 +4274,11 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum, pRet // To access one of these, use brackets; i.e., msgHeader['to'] if (highlight) { - if (msgDeleted) - msgIndicatorChar = "\x01n\x01r\x01h\x01i" + this.colors.msgListHighlightBkgColor + "*\x01n"; - else if (this.MessageIsSelected(this.subBoardCode, msgNum-1)) + // For any indicator character next to the message, prioritize selected, then deleted, then attachments + if (this.MessageIsSelected(this.subBoardCode, msgNum-1)) msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + this.colors.msgListHighlightBkgColor + CHECK_CHAR + "\x01n"; + else if (msgDeleted) + msgIndicatorChar = "\x01n\x01r\x01h\x01i" + this.colors.msgListHighlightBkgColor + "*\x01n"; else if (msgHdrHasAttachmentFlag(pMsgHeader)) msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + this.colors.msgListHighlightBkgColor + "A\x01n"; var fromName = pMsgHeader.from; @@ -4243,10 +4305,11 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum, pRet } else { - if (msgDeleted) - msgIndicatorChar = "\x01n\x01r\x01h\x01i*\x01n"; - else if (this.MessageIsSelected(this.subBoardCode, msgNum-1)) + // For any indicator character next to the message, prioritize selected, then deleted, then attachments + if (this.MessageIsSelected(this.subBoardCode, msgNum-1)) msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + CHECK_CHAR + "\x01n"; + else if (msgDeleted) + msgIndicatorChar = "\x01n\x01r\x01h\x01i*\x01n"; else if (msgHdrHasAttachmentFlag(pMsgHeader)) msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + "A\x01n"; @@ -4556,7 +4619,7 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea) // expanded fields and saves the attributes with that header. var saveRetObj = applyAttrsInMsgHdrInMessagbase(this.subBoardCode, msgHeader.number, MSG_READ); if (this.SearchTypePopulatesSearchResults() && saveRetObj.saveSucceeded) - this.RefreshHdrInSavedArrays(pOffset, MSG_READ); + this.RefreshHdrInSavedArrays(pOffset, MSG_READ, true); } // If not reading personal email and not doing a search, then update the @@ -4693,15 +4756,15 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA var promptPos = this.EnhReaderPrepLast2LinesForPrompt(); // Prompt the user for confirmation to delete the message. - // Note: this.PromptAndDeleteMessage() will check to see if the user + // Note: this.PromptAndDeleteOrUndeleteMessage() 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. + // If the message was deleted and the user can't view deleted messages, + // 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.PromptAndDeleteMessage(pOffset, promptPos, true, this.msgAreaWidth, true); - if (msgWasDeleted) + var msgWasDeleted = this.PromptAndDeleteOrUndeleteMessage(pOffset, promptPos, true, true, this.msgAreaWidth, true); + if (msgWasDeleted && !canViewDeletedMsgs()) { var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset); continueOn = msgSearchObj.continueInputLoop; @@ -4740,7 +4803,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA // 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 + // this.DeleteOrUndeleteSelectedMessages() to mark the selected messages // as deleted. // Returns an object with the following properties: // deletedAll: Boolean - Whether or not all messages were successfully marked @@ -4859,7 +4922,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA //retObj.msgNotReadable = replyRetObj.msgWasDeleted; var msgWasDeleted = replyRetObj.msgWasDeleted; //if (retObj.msgNotReadable) - if (msgWasDeleted) + if (msgWasDeleted && !canViewDeletedMsgs()) { var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset); continueOn = msgSearchObj.continueInputLoop; @@ -5733,15 +5796,15 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg 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 + // Note: this.PromptAndDeleteOrUndeleteMessage() 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.PromptAndDeleteMessage(pOffset); - if (msgWasDeleted) + var msgWasDeleted = this.PromptAndDeleteOrUndeleteMessage(pOffset, null, true); + if (msgWasDeleted && !canViewDeletedMsgs()) { var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset); continueOn = msgSearchObj.continueInputLoop; @@ -5769,7 +5832,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg // 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 + // this.DeleteOrUndeleteSelectedMessages() to mark the selected messages // as deleted. // Returns an object with the following properties: // deletedAll: Boolean - Whether or not all messages were successfully marked @@ -5850,7 +5913,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg retObj.userReplied = replyRetObj.postSucceeded; //retObj.msgNotReadable = replyRetObj.msgWasDeleted; var msgWasDeleted = replyRetObj.msgWasDeleted; - if (msgWasDeleted) + if (msgWasDeleted && !canViewDeletedMsgs()) { var msgSearchObj = this.LookForNextOrPriorNonDeletedMsg(pOffset); continueOn = msgSearchObj.continueInputLoop; @@ -7865,7 +7928,10 @@ function DigDistMsgReader_ReadConfigFile() (setting == "abortedText") || (setting == "loadingPersonalMailText") || (setting == "msgDelConfirmText") || + (setting == "msgUndelConfirmText") || + (setting == "selectedMsgsUndeletedText") || (setting == "msgDeletedText") || + (setting == "msgUndeletedText") || (setting == "cannotDeleteMsgText_notYoursNotASysop") || (setting == "cannotDeleteMsgText_notLastPostedMsg") || (setting == "msgEditConfirmText") || @@ -9549,7 +9615,7 @@ function DigDistMsgReader_DoPrivateReply(pMsgHdr, pMsgIdx, pReplyMode) // refresh the header in the search results, if there are any search // results. if (!console.noyes(bbs.text(DeleteMailQ).replace("%s", pMsgHdr.from))) - retObj.msgWasDeleted = this.PromptAndDeleteMessage(pMsgIdx, null, null, null, false); + retObj.msgWasDeleted = this.PromptAndDeleteOrUndeleteMessage(pMsgIdx, null, true, null, null, false); } return retObj; @@ -9686,7 +9752,7 @@ function DigDistMsgReader_DisplayEnhancedReaderHelp(pDisplayChgAreaOpt, pDisplay keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.vote + " \x01g: \x01n\x01cVote on the message"); keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.closePoll + " \x01g: \x01n\x01cClose a poll"); } - keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.showVotes + " \x01g: \x01n\x01cShow vote stats for the message"); + keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.showVotes + " \x01g: \x01n\x01cShow vote (tally) stats for the message"); keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.quit + " \x01g: \x01n\x01cQuit back to the BBS"); for (var idx = 0; idx < keyHelpLines.length; ++idx) { @@ -10282,7 +10348,7 @@ function DigDistMsgReader_EnhReaderPromptYesNo(pQuestion, pMessageLines, pTopLin return yesNoResponse; } -// For the DigDistMsgReader class: Allows the user to delete a message. Checks +// For the DigDistMsgReader class: Allows the user to delete or undelete a message. Checks // whether the message was posted by the user and prompt for confirmation to // delete it. Checks for delete or delete_last permission. If the sub-board has // delete_last permission enabled, this checks whether the message is the user's @@ -10292,16 +10358,16 @@ function DigDistMsgReader_EnhReaderPromptYesNo(pQuestion, pMessageLines, pTopLin // pOffset: The offset of the message to be deleted // pPromptLoc: Optional - An object containing x and y properties for the location // on the console of the prompt/error messages +// pDelete: Optional boolean: If true (default), deletes messages; if false, undeletes messages. // pClearPromptRowAtFirstUse: Optional - A boolean to specify whether or not to // clear the remainder of the prompt row the first // time text is written in that row. // pPromptRowWidth: Optional - The width of the prompt row (if pProptLoc is valid) -// pConfirmDelete: Optional boolean - Whether or not to confirm deleting the -// message. Defaults to true. +// pConfirm: Optional boolean - Whether or not to confirm deleting/undeleting the message. Defaults to true. // // Return value: Boolean - Whether or not the message was deleted -function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearPromptRowAtFirstUse, - pPromptRowWidth, pConfirmDelete) +function DigDistMsgReader_PromptAndDeleteOrUndeleteMessage(pOffset, pPromptLoc, pDelete, pClearPromptRowAtFirstUse, + pPromptRowWidth, pConfirm) { // Sanity checking if ((pOffset == null) || (typeof(pOffset) != "number")) @@ -10319,37 +10385,63 @@ function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearProm var promptLocValid = ((pPromptLoc != null) && (typeof(pPromptLoc) == "object") && (typeof(pPromptLoc.x) == "number") && (typeof(pPromptLoc.y) == "number")); + var deleteMsg = (typeof(pDelete) === "boolean" ? pDelete : true); + + var opSucceeded = false; + var msgNum = pOffset + 1; - var msgWasDeleted = false; var msgHeader = this.GetMsgHdrByIdx(pOffset, false, msgbase); - // Only let the user delete one of their own messages or the user - // is a sysop. - var cannotDeleteError = replaceAtCodesInStr(format(this.text.cannotDeleteMsgText_notYoursNotASysop, msgNum)); - var canDeleteMessage = false; - if (this.CanDelete()) - { - if (msgHeader != null) - canDeleteMessage = user.is_sysop || userHandleAliasNameMatch(msgHeader.from) || this.readingPersonalEmail; + + var errorMessage = ""; + var continueOn = false; + if (deleteMsg) + { + // If it's already marked for deletion, then nothing needs to be done + if ((msgHeader.attr & MSG_DELETE) == MSG_DELETE) + opSucceeded = true; else - canDeleteMessage = false; + { + // The message is not marked for deletion. + if (this.CanDelete()) + { + if (msgHeader != null) + continueOn = user.is_sysop || userHandleAliasNameMatch(msgHeader.from) || this.readingPersonalEmail; + else + continueOn = false; + } + else if (this.CanDeleteLastMsg()) + { + continueOn = user.is_sysop || this.MessageIsLastFromUser(pOffset); + if (!continueOn) + errorMessage = replaceAtCodesInStr(format(this.text.cannotDeleteMsgText_notLastPostedMsg, msgNum)); + } + else + errorMessage = replaceAtCodesInStr(format(this.text.cannotDeleteMsgText_notYoursNotASysop, msgNum)); + } } - else if (this.CanDeleteLastMsg()) + else { - canDeleteMessage = user.is_sysop || this.MessageIsLastFromUser(pOffset); - if (!canDeleteMessage) - cannotDeleteError = replaceAtCodesInStr(format(this.text.cannotDeleteMsgText_notLastPostedMsg, msgNum)); + // Undeleting a message marked for deletion + if ((msgHeader.attr & MSG_DELETE) == MSG_DELETE) + continueOn = true; + else + opSucceeded = true; // No action needed } - if (canDeleteMessage) + if (continueOn) { - // Determine whether or not to delete the message. First, if we are to + // Determine whether or not to delete/undelete the message. First, if we are to // have the user confirm whether to delete the message, then ask the // user to confirm first. If we're not to have the user confirm, then // go ahead and delete the message. - var deleteMsg = true; // True in case of not confirming deletion - var confirmDeleteMsg = (typeof(pConfirmDelete) == "boolean" ? pConfirmDelete : true); - if (confirmDeleteMsg) + continueOn = true; // True in case of not confirming deletion + var confirmOp = (typeof(pConfirm) == "boolean" ? pConfirm : true); + if (confirmOp) { - var delConfirmText = "\x01n" + replaceAtCodesInStr(format(this.text.msgDelConfirmText, msgNum)); + var confirmText = "\x01n"; + if (deleteMsg) + confirmText += replaceAtCodesInStr(format(this.text.msgDelConfirmText, msgNum)); + else + confirmText += replaceAtCodesInStr(format(this.text.msgUndelConfirmText, msgNum)); if (promptLocValid) { // If the caller wants to clear the remainder of the row where the prompt @@ -10358,7 +10450,7 @@ function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearProm { // Adding 5 to the prompt text to account for the ? and "[X] " that // will be added when console.noyes() is called - var promptTxtLen = console.strlen(delConfirmText) + 5; + var promptTxtLen = console.strlen(confirmText) + 5; var numCharsRemaining = 0; if (typeof(pPromptRowWidth) == "number") numCharsRemaining = pPromptRowWidth - promptTxtLen; @@ -10372,36 +10464,51 @@ function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearProm // Move the cursor to the prompt location console.gotoxy(pPromptLoc); } - deleteMsg = !console.noyes(delConfirmText); + continueOn = !console.noyes(confirmText); } - // If we are to delete the message, then delete it. - if (deleteMsg) + // If we are to delete/undelete the message, then do it. + if (continueOn) { - // TODO: Also, allow toggling the delete flag rather than always just - // marking the message for deletion. - // hdr.attr = hdr.attr ^ MSG_DELETE; // Will toggle the delete flag - //msgWasDeleted = msgbase.remove_msg(true, msgHeader.offset); - msgWasDeleted = msgbase.remove_msg(false, msgHeader.number); - if (msgWasDeleted) + if (deleteMsg) + { + //opSucceeded = msgbase.remove_msg(true, msgHeader.offset); + opSucceeded = msgbase.remove_msg(false, msgHeader.number); + } + else { - // Delete any vote response messages for this message - var voteDelRetObj = deleteVoteMsgs(msgbase, msgHeader.number, msgHeader.id, (this.subBoardCode == "mail")); - if (!voteDelRetObj.allVoteMsgsDeleted) + var tmpMsgHdr = msgbase.get_msg_header(false, msgHeader.number, false); + if (tmpMsgHdr != null) + { + tmpMsgHdr.attr = tmpMsgHdr.attr ^ MSG_DELETE; + opSucceeded = msgbase.put_msg_header(false, msgHeader.number, tmpMsgHdr); + } + else + opSucceeded = false; + } + if (opSucceeded) + { + // Delete/undelete any vote response messages for this message + // Delete/undelete vote message headers + var voteDelRetObj = toggleVoteMsgsDeleted(msgbase, msgHeader.number, msgHeader.id, deleteMsg, (this.subBoardCode == "mail")); + // In case there are search results or saved message headers, refresh the header in + // those arrays to enable the deleted attribute. + this.RefreshHdrInSavedArrays(pOffset, MSG_DELETE, deleteMsg); + if (!voteDelRetObj.allVoteMsgsAffected) { console.print("\x01n"); console.crlf(); - console.print("\x01y\x01h* Failed to delete all vote response messages for message " + msgNum + "\x01n"); + console.print("\x01y\x01h* Failed to " + (deleteMsg ? "delete" : "undelete") + " all vote response messages for message " + msgNum + "\x01n"); console.crlf(); console.pause(); } - // In case there are search results or saved message headers, refresh the header in - // those arrays to enable the deleted attribute. - this.RefreshHdrInSavedArrays(pOffset, MSG_DELETE); - // Output a message saying the message has been marked for deletion + // Output a message saying the message has been marked for deletion/undeletion if (promptLocValid) console.gotoxy(pPromptLoc); - console.print("\x01n" + replaceAtCodesInStr(format(this.text.msgDeletedText, msgNum))); + if (deleteMsg) + console.print("\x01n" + replaceAtCodesInStr(format(this.text.msgDeletedText, msgNum))); + else + console.print("\x01n" + replaceAtCodesInStr(format(this.text.msgUndeletedText, msgNum))); if (promptLocValid) console.inkey(K_NOSPIN|K_NOCRLF|K_NOECHO, ERROR_PAUSE_WAIT_MS); else @@ -10414,19 +10521,22 @@ function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearProm } else { - if (promptLocValid) - console.gotoxy(pPromptLoc); - console.print(cannotDeleteError); - if (promptLocValid) - console.inkey(K_NOSPIN|K_NOCRLF|K_NOECHO, ERROR_PAUSE_WAIT_MS); - else + if (errorMessage.length > 0) { - console.crlf(); - console.pause(); + if (promptLocValid) + console.gotoxy(pPromptLoc); + console.print(errorMessage); + if (promptLocValid) + console.inkey(K_NOSPIN|K_NOCRLF|K_NOECHO, ERROR_PAUSE_WAIT_MS); + else + { + console.crlf(); + console.pause(); + } } } msgbase.close(); - return msgWasDeleted; + return opSucceeded; } // For the DigDistMsgReader class: Allows the user to batch delete selected messages. @@ -10435,44 +10545,58 @@ function DigDistMsgReader_PromptAndDeleteMessage(pOffset, pPromptLoc, pClearProm // Parameters: // pPromptLoc: Optional - An object containing x and y properties for the location // on the console of the prompt/error messages +// pDelete: Optional boolean: If true (default), deletes messages; if false, undeletes messages. // pClearPromptRowAtFirstUse: Optional - A boolean to specify whether or not to // clear the remainder of the prompt row the first // time text is written in that row. // pPromptRowWidth: Optional - The width of the prompt row (if pProptLoc is valid) -// pConfirmDelete: Optional boolean - Whether or not to confirm deleting the -// message. Defaults to true. +// pConfirm: Optional boolean - Whether or not to confirm deleting/undeleting the messages. Defaults to true. // // Return value: Boolean - Whether or not all messages were deleted -function DigDistMsgReader_PromptAndDeleteSelectedMessages(pPromptLoc, pClearPromptRowAtFirstUse, - pPromptRowWidth, pConfirmDelete) +function DigDistMsgReader_PromptAndDeleteOrUndeleteSelectedMessages(pPromptLoc, pDelete, pClearPromptRowAtFirstUse, + pPromptRowWidth, pConfirm) { - var promptLocValid = ((pPromptLoc != null) && (typeof(pPromptLoc) == "object") && - (typeof(pPromptLoc.x) == "number") && (typeof(pPromptLoc.y) == "number")); + var promptLocValid = ((pPromptLoc != null) && (typeof(pPromptLoc) === "object") && + (typeof(pPromptLoc.x) === "number") && (typeof(pPromptLoc.y) === "number")); - var allMsgsWereDeleted = false; + var doDelete = (typeof(pDelete) === "boolean" ? pDelete : true); + var allMsgsOpSuccessful = false; - // If the user is allowed to delete all selected messages, then go ahead - // and let the user do it. - if (this.AllSelectedMessagesCanBeDeleted()) + var errorMsg = ""; // In case anything goes wrong + var continueOn = true; // Whether or not to continue with deletion/undeletion, depending on user confirmation etc. + if (doDelete) + { + // If not all the messages can be deleted, then don't allow it. + continueOn = this.AllSelectedMessagesCanBeDeleted(); + if (!this.AllSelectedMessagesCanBeDeleted()) + { + continueOn = false; + errorMsg = replaceAtCodesInStr(this.text.cannotDeleteAllSelectedMsgsText); + } + } + if (continueOn) { // Determine whether or not to delete the message. First, if we are to // have the user confirm whether to delete the message, then ask the // user to confirm first. If we're not to have the user confirm, then // go ahead and delete the message. - var deleteMsgs = true; // True in case of not confirming deletion - var confirmDeleteMsgs = (typeof(pConfirmDelete) == "boolean" ? pConfirmDelete : true); - if (confirmDeleteMsgs) + var confirmDoIt = (typeof(pConfirm) === "boolean" ? pConfirm : true); + if (confirmDoIt) { if (promptLocValid) { - var delMsgsPromptText = replaceAtCodesInStr(this.text.delSelectedMsgsConfirmText); + var promptText = ""; + if (doDelete) + promptText = replaceAtCodesInStr(this.text.delSelectedMsgsConfirmText); + else + promptText = replaceAtCodesInStr(this.text.undelSelectedMsgsConfirmText); // If the caller wants to clear the remainder of the row where the prompt // text will be, then do it. if (pClearPromptRowAtFirstUse) { // Adding 5 to the prompt text to account for the ? and "[X] " that // will be added when console.noyes() is called - var promptTxtLen = console.strlen(delMsgsPromptText) + 5; + var promptTxtLen = console.strlen(promptText) + 5; var numCharsRemaining = 0; if (typeof(pPromptRowWidth) == "number") numCharsRemaining = pPromptRowWidth - promptTxtLen; @@ -10485,21 +10609,22 @@ function DigDistMsgReader_PromptAndDeleteSelectedMessages(pPromptLoc, pClearProm } // Move the cursor to the prompt location console.gotoxy(pPromptLoc); - deleteMsgs = !console.noyes(delMsgsPromptText); + continueOn = !console.noyes(promptText); } } - // If we are to delete the messages, then delete it. - if (deleteMsgs) + // If we are to delete/undelete the messages, then do so. + if (continueOn) { - var deleteRetObj = this.DeleteSelectedMessages(); - allMsgsWereDeleted = deleteRetObj.deletedAll; + // TODO: Return status & error message + var deleteRetObj = this.DeleteOrUndeleteSelectedMessages(doDelete); + allMsgsOpSuccessful = deleteRetObj.opSuccessful; // If all selected messages were successfully deleted, then output // a success message. Otherwise, output an error. var statusMsg = ""; - if (deleteRetObj.deletedAll) - statusMsg = "\x01n\x01cAll selected messages were deleted."; + if (deleteRetObj.opSuccessful) + statusMsg = "\x01n\x01cAll selected messages were " + (doDelete ? "deleted." : "undeleted."); else - statusMsg = "\x01n\x01h\x01y* Failure to delete all selected messages"; + statusMsg = "\x01n\x01h\x01y* Failure to " + (doDelete ? "delete" : "undelete") + " all selected messages"; if (promptLocValid) { console.gotoxy(pPromptLoc); @@ -10518,9 +10643,10 @@ function DigDistMsgReader_PromptAndDeleteSelectedMessages(pPromptLoc, pClearProm } } } - else + + // If there was an error, then display it + if (errorMsg.length > 0) { - // The user is not allowed to delete all selected messages if (promptLocValid) console.gotoxy(pPromptLoc); console.print(replaceAtCodesInStr(this.text.cannotDeleteAllSelectedMsgsText)); @@ -10532,7 +10658,8 @@ function DigDistMsgReader_PromptAndDeleteSelectedMessages(pPromptLoc, pClearProm console.pause(); } } - return allMsgsWereDeleted; + + return allMsgsOpSuccessful; } /////////////////////////////////////////////////////////////////////////////////// @@ -13108,33 +13235,43 @@ function DigDistMsgReader_AllSelectedMessagesCanBeDeleted() } // For the DigDistMsgReader class: Marks the 'selected messages' (in -// this.selecteMessages) as deleted. Returns an object with the following +// this.selecteMessages) as deleted, or not deleted. +// +// Parameters: +// pDelete: Boolean - Whether or not the message should be marked deleted. Defaults to true. +// If false, the message will be marked not deleted (if it is marked as deleted). +// +// Return value: An object with the following // properties: -// deletedAll: Boolean - Whether or not all messages were successfully marked +// opSuccessful: 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. -function DigDistMsgReader_DeleteSelectedMessages() +function DigDistMsgReader_DeleteOrUndeleteSelectedMessages(pDelete) { var retObj = { - deletedAll: true, + opSuccessful: false, failureList: {} }; + var markAsDeleted = (typeof(pDelete) === "boolean" ? pDelete : true); + var msgBase = null; var msgHdr = null; - var msgWasDeleted = false; for (var subBoardCode in this.selectedMessages) { msgBase = new MsgBase(subBoardCode); if (msgBase.open()) { - // Allow the user to delete the messages if they're the sysop, they're + // If deleting messages, then check whether the user is the sysop, they're // reading their personal mail, or the sub-board allows deleting messages. - if (user.is_sysop || (subBoardCode == "mail") || ((msgBase.cfg.settings & SUB_DEL) == SUB_DEL)) + var canContinue = true; + if (markAsDeleted) + canContinue = (user.is_sysop || (subBoardCode == "mail") || ((msgBase.cfg.settings & SUB_DEL) == SUB_DEL)); + if (canContinue) { for (var msgIdx in this.selectedMessages[subBoardCode]) { @@ -13165,54 +13302,80 @@ function DigDistMsgReader_DeleteSelectedMessages() // the message index to the return object. if (msgHdr != null) { - //msgWasDeleted = msgBase.remove_msg(true, msgHdr.offset); - msgWasDeleted = msgBase.remove_msg(false, msgHdr.number); + if (markAsDeleted) + { + // remove_msg() just marks a message for deletion + //retObj.opSuccessful = msgBase.remove_msg(true, msgHdr.offset); + retObj.opSuccessful = msgBase.remove_msg(false, msgHdr.number); + } + else + { + // If the message is marked deleted, unmark it. + if ((msgHdr.attr & MSG_DELETE) == MSG_DELETE) + { + var tmpMsgHdr = msgBase.get_msg_header(false, msgHdr.number, false); + if (tmpMsgHdr != null) + { + tmpMsgHdr.attr = tmpMsgHdr.attr ^ MSG_DELETE; + retObj.opSuccessful = msgBase.put_msg_header(false, msgHdr.number, tmpMsgHdr); + } + else + retObj.opSuccessful = false; + } + else + retObj.opSuccessful = true; // No change necessary + } } else - msgWasDeleted = false; - if (msgWasDeleted) + retObj.opSuccessful = false; + if (retObj.opSuccessful) { - // Refresh the message header in the header arrays (if it - // exists there) and remove the message index from the - // selectedMessages object - this.RefreshHdrInSavedArrays(msgIdxNumber, MSG_DELETE, subBoardCode); - delete this.selectedMessages[subBoardCode][msgIdx]; - - // Delete any vote response messages that may exist for this message - var voteDelRetObj = deleteVoteMsgs(msgBase, msgHdr.number, msgHdr.id, (subBoardCode == "mail")); - // TODO: If the main messages was deleted, does it matter if vote response messages - // are deleted? - if (!voteDelRetObj.allVoteMsgsDeleted) + // Refresh the message header in the header arrays (if it exists there) and + // remove the message index from the selectedMessages object. Also, delete + // or undelete any vote response messages that may exist for this message. + this.RefreshHdrInSavedArrays(msgIdxNumber, MSG_DELETE, markAsDeleted, subBoardCode); + var voteDelRetObj = toggleVoteMsgsDeleted(msgBase, msgHdr.number, msgHdr.id, markAsDeleted, (subBoardCode == "mail")); + if (!voteDelRetObj.allVoteMsgsAffected) { - retObj.deletedAll = false; + retObj.opSuccessful = false; if (!retObj.failureList.hasOwnProperty(subBoardCode)) retObj.failureList[subBoardCode] = []; retObj.failureList[subBoardCode].push(msgIdxNumber); } + // If deleting and the user can't view deleted messages, then remove the message from this.selectedMessages + if (markAsDeleted && !canViewDeletedMsgs()) + delete this.selectedMessages[subBoardCode][msgIdx]; } else { - retObj.deletedAll = false; + retObj.opSuccessful = false; if (!retObj.failureList.hasOwnProperty(subBoardCode)) retObj.failureList[subBoardCode] = []; retObj.failureList[subBoardCode].push(msgIdxNumber); } } - // If the sub-board index array no longer has any properties (i.e., - // all messages in the sub-board were marked as deleted), then remove - // the sub-board property from this.selectedMessages - if (Object.keys(this.selectedMessages[subBoardCode]).length == 0) - delete this.selectedMessages[subBoardCode]; + if (markAsDeleted) + { + // If the sub-board index array no longer has any properties (i.e., + // all messages in the sub-board were marked as deleted) and the user + // can't view deleted messages, then remove the sub-board property from + // this.selectedMessages + if (Object.keys(this.selectedMessages[subBoardCode]).length == 0 && !canViewDeletedMsgs()) + delete this.selectedMessages[subBoardCode]; + } } else { - // The user doesn't have permission to delete messages - // in this sub-board. - // Create an entry in retObj.failureList indexed by the - // sub-board code to indicate failure to delete all - // messages in the sub-board. - retObj.deletedAll = false; - retObj.failureList[subBoardCode] = []; + if (markAsDeleted && !canContinue) + { + // The user doesn't have permission to delete messages + // in this sub-board. + // Create an entry in retObj.failureList indexed by the + // sub-board code to indicate failure to delete all + // messages in the sub-board. + retObj.opSuccessful = false; + retObj.failureList[subBoardCode] = []; + } } msgBase.close(); @@ -13223,7 +13386,7 @@ function DigDistMsgReader_DeleteSelectedMessages() // Create an entry in retObj.failureList indexed by the // sub-board code to indicate failure to delete all messages // in the sub-board. - retObj.deletedAll = false; + retObj.opSuccessful = false; retObj.failureList[subBoardCode] = []; } } @@ -17022,25 +17185,26 @@ function numReadableMsgs(pMsgbase, pSubBoardCode) return numMsgs; } -// Deletes vote messages (messages that have voting response data for a message with +// Marks or unmarks vote messages as deleted (messages that have voting response data for a message with // a given message number). // // Parameters: // pMsgbase: A MessageBase object containing the messages to be deleted // pMsgNum: The number of the message for which vote messages should be deleted // pMsgID: The ID of the message for which vote messages should be deleted +// pDoDelete: Boolean: If true, then mark for deletion. If false, then remove the deleted attribute. // pIsMailSub: Boolean - Whether or not it's the personal email area // // Return value: An object containing the following properties: // numVoteMsgs: The number of vote messages for the given message number -// numVoteMsgsDeleted: The number of vote messages that were deleted -// allVoteMsgsDeleted: Boolean - Whether or not all vote messages were deleted -function deleteVoteMsgs(pMsgbase, pMsgNum, pMsgID, pIsEmailSub) +// toggleVoteMsgsAffected: The number of vote messages that were deleted/undeleted +// allVoteMsgsAffected: Boolean - Whether or not all vote messages were deleted/undeleted +function toggleVoteMsgsDeleted(pMsgbase, pMsgNum, pMsgID, pDoDelete, pIsEmailSub) { var retObj = { numVoteMsgs: 0, - numVoteMsgsDeleted: 0, - allVoteMsgsDeleted: true + toggleVoteMsgsAffected: 0, + allVoteMsgsAffected: true }; if ((pMsgbase === null) || !pMsgbase.is_open) @@ -17066,10 +17230,26 @@ function deleteVoteMsgs(pMsgbase, pMsgNum, pMsgID, pIsEmailSub) if (isVoteMsg && (msgHdrs[msgHdrsProp].thread_back == pMsgNum) || (msgHdrs[msgHdrsProp].reply_id == pMsgID)) { ++retObj.numVoteMsgs; - var msgWasDeleted = pMsgbase.remove_msg(false, msgHdrs[msgHdrsProp].number); - retObj.allVoteMsgsDeleted = (retObj.allVoteMsgsDeleted && msgWasDeleted); - if (msgWasDeleted) - ++retObj.numVoteMsgsDeleted; + var msgWasAffected = false; + if (pDoDelete) + msgWasAffected = pMsgbase.remove_msg(false, msgHdrs[msgHdrsProp].number); + else + { + var tmpMsgHdr = pMsgbase.get_msg_header(false, msgHdrs[msgHdrsProp].number, false); + if (tmpMsgHdr != null) + { + if ((tmpMsgHdr.attr & MSG_DELETE) == MSG_DELETE) + { + tmpMsgHdr.attr = tmpMsgHdr.attr ^ MSG_DELETE; + msgWasAffected = pMsgbase.put_msg_header(false, msgHdrs[msgHdrsProp].number, tmpMsgHdr); + } + else + msgWasAffected = true; // No action needed + } + } + retObj.allVoteMsgsAffected = (retObj.allVoteMsgsAffected && msgWasAffected); + if (msgWasAffected) + ++retObj.toggleVoteMsgsAffected; } } } @@ -18371,6 +18551,14 @@ function charStr(pChar, pNumTimes) return str; } +// Returns whether the logged-in user can view deleted messages. +function canViewDeletedMsgs() +{ + var usersVDM = ((system.settings & SYS_USRVDELM) == SYS_USRVDELM); + var sysopVDM = ((system.settings & SYS_SYSVDELM) == SYS_SYSVDELM); + return (usersVDM || (user.is_sysop && sysopVDM)); +} + // For debugging: Writes some text on the screen at a given location with a given pause. // // Parameters: diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt index a7d9f2d57c2894fb652824099a08a951d6384858..e638653875374e1247c9f5f1920d5e79e084d231 100644 --- a/xtrn/DDMsgReader/readme.txt +++ b/xtrn/DDMsgReader/readme.txt @@ -1,6 +1,6 @@ Digital Distortion Message Reader - Version 1.52 - Release date: 2022-07-09 + Version 1.53 + Release date: 2022-07-18 by diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt index 1a9b33c0de42f2d510594717ae340091e7eb08ac..a3b9fcda7da3da17ed09bc2624e68a686cc7cfd7 100644 --- a/xtrn/DDMsgReader/revision_history.txt +++ b/xtrn/DDMsgReader/revision_history.txt @@ -5,6 +5,10 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +1.53 2022-07-18 Deleted messages can now be un-marked for deletion from + the message list (if the user has delete permissions). + Also, the reader now honors the system setting for whether + users can view deleted messages. 1.52 2022-07-09 Mouse click support for the bottom help lines in scrolling mode (thanks to help from Nelgin) 1.51 2022-07-05 Graphic is now only used when using the scrollable