diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js index f008d84af0e07d63812af05ce7bd63ca9c1218e6..0ce8233d1d98a4e9ec0a0a272dd6e4066b1a589f 100644 --- a/xtrn/DDMsgReader/DDMsgReader.js +++ b/xtrn/DDMsgReader/DDMsgReader.js @@ -88,6 +88,15 @@ * (defaults to true). * Fix for setting colors for the key help lines so that the background * won't get un-done if the other help line colors have a N (normal) attribute. + * 2023-12-02 Eric Oulashin Version 1.90 Beta + * New: operator menu for read mode, with the option to add the author to the + * twit list, etc. + * Fix: When refreshing a rectangular area of a message, if it's a poll message, + * the background color for the voted responses was used for the non-selected + * responses. + * Removed the setting useScrollingInterfaceForANSIMessages. + * 2023-12-04 Eric Oulashin Version 1.90 + * Releasing this version */ "use strict"; @@ -192,8 +201,8 @@ var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a'); // Reader version information -var READER_VERSION = "1.89"; -var READER_DATE = "2023-11-30"; +var READER_VERSION = "1.90"; +var READER_DATE = "2023-12-04"; // Keyboard key codes for displaying on the screen var UP_ARROW = ascii(24); @@ -714,9 +723,13 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.WriteMsgListScreenTopHeader = DigDistMsgReader_WriteMsgListScreenTopHeader; this.ReadMessageEnhanced = DigDistMsgReader_ReadMessageEnhanced; this.ReadMessageEnhanced_Scrollable = DigDistMsgReader_ReadMessageEnhanced_Scrollable; + this.ShowHdrOrKludgeLines_Scrollable = DigDistMsgReader_ShowHdrOrKludgeLines_Scrollable; + this.ShowVoteInfo_Scrollable = DigDistMsgReader_ShowVoteInfo_Scrollable; this.ScrollableReaderNextReadableMessage = DigDistMsgReader_ScrollableReaderNextReadableMessage; this.ScrollReaderDetermineClickCoordAction = DigDistMsgReader_ScrollReaderDetermineClickCoordAction; this.ReadMessageEnhanced_Traditional = DigDistMsgReader_ReadMessageEnhanced_Traditional; + this.ShowReadModeOpMenuAndGetSelection = DigDistMsgReader_ShowReadModeOpMenuAndGetSelection; + this.CreateReadModeOpMenu = DigDistMsgReader_CreateReadModeOpMenu; this.EnhReaderPrepLast2LinesForPrompt = DigDistMsgReader_EnhReaderPrepLast2LinesForPrompt; this.LookForNextOrPriorNonDeletedMsg = DigDistMsgReader_LookForNextOrPriorNonDeletedMsg; this.PrintMessageInfo = DigDistMsgReader_PrintMessageInfo; @@ -969,12 +982,6 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) // the > key to go to the next sub-board. this.doingMultiSubBoardScan = false; - // An option for using the scrollable interface for messages with ANSI - // content - The sysop can set this to false if the sysop thinks the - // scrolling ANSI interface (using frame.js and scrollbar.js) doesn't - // look good enough - this.useScrollingInterfaceForANSIMessages = true; - // Whether or not to pause (with a message) after doing a new message scan this.pauseAfterNewMsgScan = true; @@ -1023,6 +1030,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) userSettings: CTRL_U, validateMsg: "A", // Only if the user is a sysop quickValUser: CTRL_Q, + operatorMenu: CTRL_O, threadView: "*" // TODO: Implement this }; //if (user.is_sysop) @@ -1499,6 +1507,17 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.pausePromptText = bbs.text(Pause); if (this.pausePromptText.toUpperCase().indexOf("@EXEC:") > -1) this.pausePromptText = "\x01n\x01c[ Press a key ] "; + + // Menu option values to use for the operator menu for reader mode + this.readerOpMenuOptValues = { + validateMsg: 0, + editAuthorUserAccount: 1, + showHdrLines: 2, + showKludgeLines: 3, + showTallyStats: 4, + quickValUser: 5, + addAuthorToTwitList: 6 + }; } // For the DigDistMsgReader class: Sets the subBoardCode property and also @@ -5106,6 +5125,25 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA var msgReaderObj = this; var msgHasAttachments = msgHdrHasAttachmentFlag(msgHeader); + + // For editing a user account. Only for the sysop. + function userEdit(msgHeader, pOffset, pReader) + { + console.attributes = "N"; + console.crlf(); + console.print("- Edit user " + msgHeader.from); + console.crlf(); + var editObj = editUser(msgHeader.from); + if (editObj.errorMsg.length != 0) + { + console.attributes = "N"; + console.crlf(); + console.print("\x01y\x01h" + editObj.errorMsg + "\x01n"); + console.crlf(); + console.pause(); + } + } + // User input loop var continueOn = true; while (continueOn) @@ -5628,51 +5666,16 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA case this.enhReaderKeys.showKludgeLines: if (user.is_sysop) { - // Save the original cursor position - var originalCurPos = console.getxy(); - - // Get an array of the extended header info/kludge lines and then - // allow the user to scroll through them. - var onlyKludgeLines = (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines); - var extdHdrInfoLines = this.GetExtdMsgHdrInfo(this.subBoardCode, msgHeader.number, onlyKludgeLines); - if (extdHdrInfoLines.length > 0) + writeMessage = this.ShowHdrOrKludgeLines_Scrollable(retObj.lastKeypress == this.enhReaderKeys.showKludgeLines, msgHeader, msgAreaWidth, msgAreaHeight); + // Display the scrollbar for the message to refresh it on the screen + if (this.userSettings.useEnhReaderScrollbar) { - if (this.userSettings.useEnhReaderScrollbar) - { - // Calculate information for the scrollbar for the kludge lines - var infoFractionShown = this.msgAreaHeight / extdHdrInfoLines.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; - // Display the kludge lines and let the user scroll through them - this.DisplayEnhancedReaderWholeScrollbar(this.msgAreaTop, numInfoSolidScrollBlocks); - } - scrollTextLines(extdHdrInfoLines, 0, this.colors["msgBodyColor"], true, this.msgAreaLeft, - this.msgAreaTop, msgAreaWidth, msgAreaHeight, 1, console.screen_rows, - this.userSettings.useEnhReaderScrollbar, msgInfoScrollbarUpdateFn); - // Display the scrollbar for the message to refresh it on the screen - if (this.userSettings.useEnhReaderScrollbar) - { - solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); - this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); - } - else - { - // TODO - } - writeMessage = true; // We want to refresh the message on the screen + solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); + this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); } else { - // There are no header/kludge lines for this message - var msgText = onlyKludgeLines ? this.text.noKludgeLinesForThisMsgText : this.text.noHdrLinesForThisMsgText; - this.DisplayEnhReaderError(replaceAtCodesInStr(msgText), msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr); - console.gotoxy(originalCurPos); - writeMessage = false; + // TODO } } else // The user is not a sysop @@ -5770,30 +5773,17 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA case this.enhReaderKeys.userEdit: // Edit the user who wrote the message if (user.is_sysop) { - console.attributes = "N"; - console.crlf(); - console.print("- Edit user " + msgHeader.from); - console.crlf(); - var editObj = editUser(msgHeader.from); - if (editObj.errorMsg.length != 0) - { - console.attributes = "N"; - console.crlf(); - console.print("\x01y\x01h" + editObj.errorMsg + "\x01n"); - console.crlf(); - console.pause(); - } - + userEdit(msgHeader, pOffset, this); // Refresh things on the screen console.clear("\x01n"); // Display the message header and key help line again - this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1); - this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); + pReader.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1); + pReader.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); // Display the scrollbar again to refresh it on the screen - if (this.userSettings.useEnhReaderScrollbar) + if (pReader.userSettings.useEnhReaderScrollbar) { - solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); - this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); + retObj.solidBlockStartRow = pReader.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); + pReader.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); } else { @@ -5920,33 +5910,10 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA writeMessage = false; 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 DVRetObj = this.ShowVoteInfo_Scrollable(msgHeader, msgAreaWidth, msgAreaHeight); + writeMessage = DVRetObj.writeMessage; + if (DVRetObj.hasVoteProps) { - 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 - if (this.userSettings.useEnhReaderScrollbar) - { - 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; - // Display the vote info lines and let the user scroll through them - this.DisplayEnhancedReaderWholeScrollbar(this.msgAreaTop, numInfoSolidScrollBlocks); - } - else - { - // TODO - } - scrollTextLines(voteInfo, 0, this.colors["msgBodyColor"], true, this.msgAreaLeft, this.msgAreaTop, msgAreaWidth, - msgAreaHeight, 1, console.screen_rows, this.userSettings.useEnhReaderScrollbar, msgInfoScrollbarUpdateFn); // Display the scrollbar for the message to refresh it on the screen if (this.userSettings.useEnhReaderScrollbar) { @@ -5957,13 +5924,6 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA { // TODO } - 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 @@ -6206,6 +6166,126 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA this.RefreshMsgAreaRectangle(msgInfo.messageLines, topMsgLineIdx, userSettingsRetObj.optionBoxTopLeftX, userSettingsRetObj.optionBoxTopLeftY, userSettingsRetObj.optionBoxWidth, userSettingsRetObj.optionBoxHeight); } break; + case this.enhReaderKeys.operatorMenu: // Operator menu + writeMessage = false; + if (user.is_sysop) + { + var opRetObj = this.ShowReadModeOpMenuAndGetSelection(); + // Refresh the message area where the option menu was + this.RefreshMsgAreaRectangle(msgInfo.messageLines, topMsgLineIdx, opRetObj.menuTopLeftX, opRetObj.menuTopLeftY, opRetObj.menuWidth, opRetObj.menuHeight); + if (opRetObj.chosenOption != null) + { + switch (opRetObj.chosenOption) + { + case this.readerOpMenuOptValues.validateMsg: // Validate the message + if (this.subBoardCode != "mail" && msg_area.sub[this.subBoardCode].is_moderated) + { + var message = ""; + if (this.ValidateMsg(this.subBoardCode, msgHeader.number)) + { + message = "\x01n\x01cMessage 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 = "\x01n\x01y\x01hMessage validation failed!"; + this.DisplayEnhReaderError(message, msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr); + writeMessage = true; + } + break; + case this.readerOpMenuOptValues.editAuthorUserAccount: // Edit the local user account + userEdit(msgHeader, pOffset, this); + // Refresh things on the screen + console.clear("\x01n"); + // Display the message header and key help line again + pReader.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1); + pReader.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); + // Display the scrollbar again to refresh it on the screen + if (pReader.userSettings.useEnhReaderScrollbar) + { + retObj.solidBlockStartRow = pReader.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); + pReader.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); + } + else + { + // TODO + } + writeMessage = true; // We want to refresh the message on the screen + break; + case this.readerOpMenuOptValues.showHdrLines: // Show header or kludge lines + case this.readerOpMenuOptValues.showKludgeLines: + writeMessage = this.ShowHdrOrKludgeLines_Scrollable(opRetObj.chosenOption == this.readerOpMenuOptValues.showKludgeLines, msgHeader, msgAreaWidth, msgAreaHeight); + // Display the scrollbar for the message to refresh it on the screen + if (this.userSettings.useEnhReaderScrollbar) + { + solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); + this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); + } + else + { + // TODO + } + break; + case this.readerOpMenuOptValues.showTallyStats: // Show tally/vote stats/info + var DVRetObj = this.ShowVoteInfo_Scrollable(msgHeader, msgAreaWidth, msgAreaHeight); + writeMessage = DVRetObj.writeMessage; + if (DVRetObj.hasVoteProps) + { + // Display the scrollbar for the message to refresh it on the screen + if (this.userSettings.useEnhReaderScrollbar) + { + solidBlockStartRow = this.msgAreaTop + Math.floor(numNonSolidScrollBlocks * fractionToLastPage); + this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); + } + else + { + // TODO + } + } + break; + case this.readerOpMenuOptValues.quickValUser: // Quick validate the user + var valRetObj = quickValidateLocalUser(msgHeader.from, this.scrollingReaderInterface && console.term_supports(USER_ANSI), this.quickUserValSetIndex); + if (valRetObj.needWholeScreenRefresh) + { + this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1); + if (this.userSettings.useEnhReaderScrollbar) + this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks); + else + { + // TODO + } + this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); + } + else + { + writeMessage = false; // Don't refresh the whole message + if (valRetObj.refreshBottomLine) + this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); + if (valRetObj.optionBoxTopLeftX > 0 && valRetObj.optionBoxTopLeftY > 0 && valRetObj.optionBoxWidth > 0 && valRetObj.optionBoxHeight > 0) + this.RefreshMsgAreaRectangle(msgInfo.messageLines, topMsgLineIdx, valRetObj.optionBoxTopLeftX, valRetObj.optionBoxTopLeftY, valRetObj.optionBoxWidth, valRetObj.optionBoxHeight); + } + break; + case this.readerOpMenuOptValues.addAuthorToTwitList: // Add author to twit list + var promptTxt = format("Add %s to twit list", msgHeader.from); + if (this.EnhReaderPromptYesNo(promptTxt, msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr, solidBlockStartRow, numSolidScrollBlocks, true)) + { + var statusMsg = "\x01n" + addToTwitList(msgHeader.from) ? "\x01w\x01hSuccessfully updated the twit list" : "\x01y\x01hFailed to update the twit list!" + writeWithPause(1, console.screen_rows, statusMsg, ERROR_PAUSE_WAIT_MS, "\x01n", true); + console.attributes = "N"; + this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea); + } + break; + } + } + } + break; case this.enhReaderKeys.quit: // Quit case KEY_ESC: // Normally, if quitFromReaderGoesToMsgList is enabled, then do that @@ -6228,6 +6308,114 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA return retObj; } +// Helper method for ReadMessageEnhanced_Scrollable(): Shows header or kludge lines for the scrollable interface. For the sysop. +function DigDistMsgReader_ShowHdrOrKludgeLines_Scrollable(pOnlyKludgeLines, msgHeader, msgAreaWidth, msgAreaHeight) +{ + var msgReaderObj = this; + // This is a scrollbar update function for use when viewing the header info/kludge lines. + function msgInfoScrollbarUpdateFn(pFractionToLastPage) + { + var infoSolidBlockStartRow = msgReaderObj.msgAreaTop + Math.floor(numNonSolidInfoScrollBlocks * pFractionToLastPage); + if (infoSolidBlockStartRow != lastInfoSolidBlockStartRow) + msgReaderObj.UpdateEnhancedReaderScrollbar(infoSolidBlockStartRow, lastInfoSolidBlockStartRow, numInfoSolidScrollBlocks); + lastInfoSolidBlockStartRow = infoSolidBlockStartRow; + console.gotoxy(1, console.screen_rows); + } + + var writeMessage = false; + + // Save the original cursor position + var originalCurPos = console.getxy(); + + // Get an array of the extended header info/kludge lines and then + // allow the user to scroll through them. + var extdHdrInfoLines = this.GetExtdMsgHdrInfo(this.subBoardCode, msgHeader.number, pOnlyKludgeLines); + if (extdHdrInfoLines.length > 0) + { + if (this.userSettings.useEnhReaderScrollbar) + { + // Calculate information for the scrollbar for the kludge lines + var infoFractionShown = this.msgAreaHeight / extdHdrInfoLines.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; + // Display the kludge lines and let the user scroll through them + this.DisplayEnhancedReaderWholeScrollbar(this.msgAreaTop, numInfoSolidScrollBlocks); + } + scrollTextLines(extdHdrInfoLines, 0, this.colors["msgBodyColor"], true, this.msgAreaLeft, + this.msgAreaTop, msgAreaWidth, msgAreaHeight, 1, console.screen_rows, + this.userSettings.useEnhReaderScrollbar, msgInfoScrollbarUpdateFn); + writeMessage = true; // We want to refresh the message on the screen + } + else + { + // There are no header/kludge lines for this message + var msgText = pOnlyKludgeLines ? this.text.noKludgeLinesForThisMsgText : this.text.noHdrLinesForThisMsgText; + this.DisplayEnhReaderError(replaceAtCodesInStr(msgText), msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr); + console.gotoxy(originalCurPos); + } + + return writeMessage; +} +// For the DDMsgReader class: Shows vote/tally information, for the scrollable interface. +function DigDistMsgReader_ShowVoteInfo_Scrollable(pMsgHeader, pMsgAreaWidth, pMsgAreaHeight) +{ + var msgReaderObj = this; + // This is a scrollbar update function for use when viewing the header info/kludge lines. + function msgInfoScrollbarUpdateFn(pFractionToLastPage) + { + var infoSolidBlockStartRow = msgReaderObj.msgAreaTop + Math.floor(numNonSolidInfoScrollBlocks * pFractionToLastPage); + if (infoSolidBlockStartRow != lastInfoSolidBlockStartRow) + msgReaderObj.UpdateEnhancedReaderScrollbar(infoSolidBlockStartRow, lastInfoSolidBlockStartRow, numInfoSolidScrollBlocks); + lastInfoSolidBlockStartRow = infoSolidBlockStartRow; + console.gotoxy(1, console.screen_rows); + } + + var retObj = { + hasVoteProps: pMsgHeader.hasOwnProperty("total_votes") && pMsgHeader.hasOwnProperty("upvotes"), + writeMessage: false + }; + + // Save the original cursor position + var originalCurPos = console.getxy(); + if (retObj.hasVoteProps) + { + var voteInfo = this.GetUpvoteAndDownvoteInfo(pMsgHeader); + // 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 + if (this.userSettings.useEnhReaderScrollbar) + { + var infoFractionShown = this.pMsgAreaHeight / voteInfo.length; + if (infoFractionShown > 1) + infoFractionShown = 1.0; + var numInfoSolidScrollBlocks = Math.floor(this.pMsgAreaHeight * infoFractionShown); + if (numInfoSolidScrollBlocks == 0) + numInfoSolidScrollBlocks = 1; + var numNonSolidInfoScrollBlocks = this.pMsgAreaHeight - numInfoSolidScrollBlocks; + var lastInfoSolidBlockStartRow = this.msgAreaTop; + // Display the vote info lines and let the user scroll through them + this.DisplayEnhancedReaderWholeScrollbar(this.msgAreaTop, numInfoSolidScrollBlocks); + } + else + { + // TODO + } + scrollTextLines(voteInfo, 0, this.colors.msgBodyColor, true, this.msgAreaLeft, this.msgAreaTop, pMsgAreaWidth, + pMsgAreaHeight, 1, console.screen_rows, this.userSettings.useEnhReaderScrollbar, msgInfoScrollbarUpdateFn); + retObj.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); + } + return retObj; +} // Helper method for ReadMessageEnhanced_Scrollable(): Determines if there is a readable message after the // one at the given offset. // @@ -7119,8 +7307,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg writeMessage = true; } console.crlf(); - console.print(message); - console.attributes = "N"; + console.print(message + "\x01n"); console.crlf(); console.pause(); } @@ -7129,7 +7316,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg break; case this.enhReaderKeys.quickValUser: // Quick-validate the user if (user.is_sysop) - quickValidateLocalUser(msgHeader.from, this.scrollingReaderInterface && console.term_supports(USER_ANSI), this.quickUserValSetIndex); + quickValidateLocalUser(msgHeader.from, false, this.quickUserValSetIndex); else writeMessage = false; break; @@ -7206,6 +7393,125 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg writeMessage = true; } break; + case this.enhReaderKeys.operatorMenu: // Operator menu + if (user.is_sysop) + { + writeMessage = true; + writePromptText = true; + console.crlf(); + console.print("\x01w\x01h== Operator menu ==\x01n"); + console.crlf(); + var opRetObj = this.ShowReadModeOpMenuAndGetSelection(); + if (opRetObj.chosenOption != null) + { + switch (opRetObj.chosenOption) + { + case this.readerOpMenuOptValues.validateMsg: // Validate the message + if (this.subBoardCode != "mail" && msg_area.sub[this.subBoardCode].is_moderated) + { + var message = ""; + if (this.ValidateMsg(this.subBoardCode, msgHeader.number)) + { + message = "\x01n\x01cMessage 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; + } + else + message = "\x01n\x01y\x01hMessage validation failed!"; + console.crlf(); + console.print(message + "\x01n"); + console.crlf(); + console.pause(); + } + break; + case this.readerOpMenuOptValues.editAuthorUserAccount: // Edit author's user account + console.attributes = "N"; + console.crlf(); + console.print("- Edit user " + msgHeader.from); + console.crlf(); + var editObj = editUser(msgHeader.from); + if (editObj.errorMsg.length != 0) + { + console.attributes = "N"; + console.crlf(); + console.print("\x01y\x01h" + editObj.errorMsg + "\x01n"); + console.crlf(); + console.pause(); + } + console.attributes = "N"; + break; + case this.readerOpMenuOptValues.showHdrLines: // Show header or kludge lines + case this.readerOpMenuOptValues.showKludgeLines: + console.crlf(); + // Get an array of the extended header info/kludge lines and then + // display them. + var extdHdrInfoLines = this.GetExtdMsgHdrInfo(this.subBoardCode, msgHeader.number, (opRetObj.chosenOption == this.readerOpMenuOptValues.showKludgeLines)); + if (extdHdrInfoLines.length > 0) + { + console.crlf(); + for (var infoIter = 0; infoIter < extdHdrInfoLines.length; ++infoIter) + { + console.print(extdHdrInfoLines[infoIter]); + console.crlf(); + } + console.pause(); + } + else + { + // There are no kludge lines for this message + console.print(replaceAtCodesInStr(this.text.noKludgeLinesForThisMsgText)); + console.crlf(); + console.pause(); + } + break; + case this.readerOpMenuOptValues.showTallyStats: // Show tally stats + if (msgHeader.hasOwnProperty("total_votes") && msgHeader.hasOwnProperty("upvotes")) + { + console.attributes = "N"; + console.crlf(); + var voteInfo = this.GetUpvoteAndDownvoteInfo(msgHeader); + for (var voteInfoIdx = 0; voteInfoIdx < voteInfo.length; ++voteInfoIdx) + { + console.print(voteInfo[voteInfoIdx]); + console.crlf(); + } + } + else + { + console.print("\x01n\x01h\x01yThere is no voting information for this message\x01n"); + console.crlf(); + } + console.pause(); + break; + case this.readerOpMenuOptValues.quickValUser: // Quick-validate the user + quickValidateLocalUser(msgHeader.from, false, this.quickUserValSetIndex); + break; + case this.readerOpMenuOptValues.addAuthorToTwitList: // Add author to twit list + var promptTxt = format("Add %s to twit list", msgHeader.from); + if (!console.noyes(promptTxt)) + { + var statusMsg = "\x01n" + addToTwitList(msgHeader.from) ? "\x01w\x01hSuccessfully updated the twit list" : "\x01y\x01hFailed to update the twit list!" + console.print(statusMsg); + console.attributes = "N"; + console.crlf(); + console.pause(); + } + break; + } + } + } + else + { + writeMessage = false; + writePromptText = false; + } + break; case this.enhReaderKeys.quit: // Quit case KEY_ESC: // Normally, if quitFromReaderGoesToMsgList is enabled, then do that, except @@ -7232,6 +7538,105 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg return retObj; } +// For the DDMsgReader class: Does the operator mode for reading. +// +// Return value: An object with the following properties: +// menuTopLeftX: The horizontal component of the upper-left corner of the operator menu (for scrollable mode) +// menuTopLeftY: The vertical component of the upper-left corner of the operator menu (for scrollable mode) +// menuWidth: The width of the operator menu (for scrollable mode) +// menuHeight: The height of the operator menu (for scrollable mode) +// chosenOption: The user's chosen option from the menu (one of this.readerOpMenuOptValues), or null +// if the user quit/aborted +// lastUserInput: The user's last key input from the menu (empty string if there is none) +function DigDistMsgReader_ShowReadModeOpMenuAndGetSelection() +{ + var retObj = { + menuTopLeftX: 1, + menuTopLeftY: 1, + menuWidth: 0, + menuHeight: 0, + chosenOption: null, + lastUserInput: "" + }; + + // This is only for the sysop + if (!user.is_sysop) + return retObj; + + // If using scrollable mode, create the operator menu & display it + + var opMenu = this.CreateReadModeOpMenu(); + retObj.menuTopLeftX = opMenu.pos.x; + retObj.menuTopLeftY = opMenu.pos.y; + retObj.menuWidth = opMenu.size.width; + retObj.menuHeight = opMenu.size.height; + + //GetVal(pDraw, pSelectedItemIndexes) + retObj.chosenOption = opMenu.GetVal(); + if (typeof(opMenu.lastUserInput) === "string") + retObj.lastUserInput = opMenu.lastUserInput; + // If the user pressed one of the additional quit keys set up for the + // menu, make sure the chosen option is null (it should be anyway) + if (retObj.lastUserInput.toUpperCase() == "Q" || retObj.lastUserInput == KEY_ESC) // Quit + retObj.chosenOption = null; + + return retObj; +} + +// For the DDMsgReader class: Creates the operator menu for read mode. +// The menu should be created each time, because the options could be different for +// each sub-board (i.e., if the sub-board is moderated, then the 'Validate message' +// option will be available). +function DigDistMsgReader_CreateReadModeOpMenu() +{ + //var itemFormatStr = "\x01n\x01c\x01h%s\x01y"; + // We'll add the 'Validate message' option if the sub-board isn't email and is moderated + var subBoardIsModerated = (this.subBoardCode != "mail" && msg_area.sub[this.subBoardCode].is_moderated); + + var opMenuWidth = 35; + var opMenuHeight = 8; + if (subBoardIsModerated) + ++opMenuHeight; + var opMenuX = Math.floor(console.screen_columns/2) - Math.floor(opMenuWidth/2); + var opMenuY = this.msgAreaTop + 2; + var opMenu = new DDLightbarMenu(opMenuX, opMenuY, opMenuWidth, opMenuHeight); + opMenu.borderEnabled = true; + opMenu.allowANSI = this.scrollingReaderInterface && console.term_supports(USER_ANSI); + opMenu.topBorderText = "\x01n\x01w\x01hOperator menu (reader mode)"; + if (subBoardIsModerated) + opMenu.Add("&A: Validate the message", this.readerOpMenuOptValues.validateMsg); + // If the scrollbar/ANSI interface is being used, then add the menu items with hotkey characters. + // Otherwise don't use the hotkey characters (the menu will use numbered mode). + if (opMenu.allowANSI) + { + opMenu.Add("&U: Edit author's user account", this.readerOpMenuOptValues.editAuthorUserAccount); + opMenu.Add("&H: Show header lines", this.readerOpMenuOptValues.showHdrLines); + opMenu.Add("&K: Show kludge lines", this.readerOpMenuOptValues.showKludgeLines); + opMenu.Add("&T: Show tally stats", this.readerOpMenuOptValues.showTallyStats); + opMenu.Add("&A: Quick validate the user", this.readerOpMenuOptValues.quickValUser); + opMenu.Add("&I: Add author to twit list", this.readerOpMenuOptValues.addAuthorToTwitList); + // Use cyan for the item color, and cyan with blue background for selected item color + opMenu.colors.itemColor = "\x01n\x01c"; + opMenu.colors.selectedItemColor = "\x01n\x01c\x014"; + } + else + { + opMenu.Add("Edit author's user account", this.readerOpMenuOptValues.editAuthorUserAccount); + opMenu.Add("Show header lines", this.readerOpMenuOptValues.showHdrLines); + opMenu.Add("Show kludge lines", this.readerOpMenuOptValues.showKludgeLines); + opMenu.Add("Show tally stats", this.readerOpMenuOptValues.showTallyStats); + opMenu.Add("Quick validate the user", this.readerOpMenuOptValues.quickValUser); + opMenu.Add("Add author to twit list", this.readerOpMenuOptValues.addAuthorToTwitList); + // Use green for the item color and high cyan for the item number color + opMenu.colors.itemColor = "\x01n\x01g"; + opMenu.colors.itemNumColor = "\x01n\x01c\x01h"; + } + + opMenu.AddAdditionalQuitKeys("qQ" + this.enhReaderKeys.operatorMenu); + + return opMenu; +} + // For the ReadMessageEnhanced methods: This function converts a thread navigation // key character to its corresponding thread type value function keypressToThreadType(pKeypress, pEnhReaderKeys) @@ -8468,8 +8873,6 @@ function DigDistMsgReader_ReadConfigFile() this.msgListUseLightbarListInterface = (settingsObj.listInterfaceStyle.toUpperCase() == "LIGHTBAR"); if (typeof(settingsObj["readerInterfaceStyle"]) === "string") this.scrollingReaderInterface = (settingsObj.readerInterfaceStyle.toUpperCase() == "SCROLLABLE"); - if (typeof(settingsObj["readerInterfaceStyleForANSIMessages"]) === "string") - this.useScrollingInterfaceForANSIMessages = (settingsObj.readerInterfaceStyleForANSIMessages.toUpperCase() == "SCROLLABLE"); if (typeof(settingsObj["displayBoardInfoInHeader"]) === "boolean") this.displayBoardInfoInHeader = settingsObj.displayBoardInfoInHeader; if (typeof(settingsObj["promptToContinueListingMessages"]) === "boolean") @@ -10486,6 +10889,7 @@ function DigDistMsgReader_DisplayEnhancedReaderHelp(pDisplayChgAreaOpt, pDisplay keyHelpLines.push("\x01h\x01cDEL \x01g: \x01n\x01cDelete the current message"); keyHelpLines.push("\x01h\x01cCtrl-S \x01g: \x01n\x01cSave the message (to the BBS machine)"); keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.validateMsg + " \x01g: \x01n\x01cValidate the message"); + keyHelpLines.push("\x01h\x01cCtrl-O \x01g: \x01n\x01cShow operator menu"); var quickValUserLine = "\x01h\x01cCtrl-Q \x01g: \x01n\x01cQuick-validate user (must be local)"; if (this.quickUserValSetIndex >= 0 && this.quickUserValSetIndex < 10) quickValUserLine += "; Set index: " + this.quickUserValSetIndex; @@ -14027,7 +14431,7 @@ function DigDistMsgReader_ValidateMsg(pSubBoardCode, pMsgNum) var msgHdr = msgbase.get_msg_header(false, pMsgNum, false); if (msgHdr != null) { - if ((msgHdr.attr & MSG_VALIDATED) == 0) + if (!Boolean(msgHdr.attr & MSG_VALIDATED)) { msgHdr.attr |= MSG_VALIDATED; validationSuccessful = msgbase.put_msg_header(false, msgHdr.number, msgHdr); @@ -15259,7 +15663,7 @@ function DigDistMsgReader_CreateLightbarIndexedModeMenu(pNumMsgsWidth, pNumNewMs // Add additional keypresses for quitting the menu's input loop so we can // respond to these keys // TODO: Include Mm to allow the user to view the message list instead of read it from the indexed menu - indexedModeMenu.AddAdditionalQuitKeys(); + //indexedModeMenu.AddAdditionalQuitKeys(); for (var key in this.indexedModeMenuKeys) { if (/[a-zA-Z]/.test(this.indexedModeMenuKeys[key])) @@ -16909,7 +17313,7 @@ function DigDistMsgReader_RefreshMsgAreaRectangle(pTxtLines, pTopLineIdx, pTopLe //console.print(getAllEditLineAttrsUntilLineIdx(pTxtLines, txtLineIdx, true, txtLineStartIdx)); // Get the section of line (and make sure it can fill the needed width), and print it // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js - var lineText = substrWithAttrCodes(pTxtLines[txtLineIdx].replace(/[\r\n]+/g, ""), txtLineStartIdx, pWidth); + var lineText = "\x01n" + substrWithAttrCodes(pTxtLines[txtLineIdx].replace(/[\r\n]+/g, ""), txtLineStartIdx, pWidth); var printableTxtLen = console.strlen(lineText); if (printableTxtLen < pWidth) lineText += format("\x01n%*s", pWidth - printableTxtLen, ""); @@ -17782,6 +18186,20 @@ function scrollTextLines(pTxtLines, pTopLineIdx, pTxtAttrib, pWriteTxtLines, pTo topLineIdx: pTopLineIdx }; + /* + // Temporary + if (user.is_sysop) + { + console.print("\x01n\r\n"); + console.print("pWidth: " + pWidth + "\r\n"); + console.print("pHeight: " + pHeight + "\r\n"); + console.print("pUseScrollbar: " + pUseScrollbar + "\r\n"); + console.print("pScrollUpdateFn: " + typeof(pScrollUpdateFn) + "\r\n"); + console.pause(); + } + // End Temporary + */ + // Create an array of color/attribute codes for each line of // text, in case there are any such codes in the text lines, // so that the colors in the message are displayed properly. @@ -22533,7 +22951,12 @@ function quickValidateLocalUser(pUsername, pUseANSI, pQuickValSetIdx) console.print(menuHdrStr); } else + { retObj.needWholeScreenRefresh = true; + // Use green for the item color and high cyan for the item number color + valSetMenu.colors.itemColor = "\x01n\x01g"; + valSetMenu.colors.itemNumColor = "\x01n\x01c\x01h"; + } quickValidationValSet = valSetMenu.GetVal(); displayedMenu = true; console.attributes = "N"; @@ -22951,6 +23374,69 @@ function getMsgHdr(pSubCode, pByOffset, pNumOrOffset, pExpandFields, pIncludeVot return msgHdr; } +// Adds to the twit list. +// +// Parameters: +// pStr: A name/email or netmail address +// +// Return value: Boolean - Whether or not this was successful +function addToTwitList(pStr) +{ + if (typeof(pStr) !== "string") + return false; + + var wasSuccessful = true; + if (!entryExistsInTwitList(pStr)) + { + wasSuccessful = false; + var twitFile = new File(system.ctrl_dir + "twitlist.cfg"); + //if (twitFile.open(twitFile.exists ? "r+" : "w+")) + if (twitFile.open("a")) + { + wasSuccessful = twitFile.writeln(pStr); + twitFile.close(); + } + } + return wasSuccessful; +} +// Returns whether an entry exists in the twit list. +// +// Parameters: +// pStr: An entry to check in the twit list +// +// Return value: Boolean - Whether or not the given string exists in the twit list +function entryExistsInTwitList(pStr) +{ + if (typeof(pStr) !== "string") + return false; + + var entryExists = false; + var twitFile = new File(system.ctrl_dir + "twitlist.cfg"); + if (twitFile.open("r")) + { + while (!twitFile.eof && !entryExists) + { + //// Read the next line from the config file. + var fileLine = twitFile.readln(2048); + // fileLine should be a string, but I've seen some cases + // where for some reason it isn't. If it's not a string, + // then continue onto the next line. + if (typeof(fileLine) != "string") + continue; + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // See if this line matches the given string + entryExists = (pStr == skipsp(truncsp(fileLine))); + } + + twitFile.close(); + } + return entryExists; +} + /////////////////////////////////////////////////////////////////////////////////// // For debugging: Writes some text on the screen at a given location with a given pause. diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt index 942db9b39e298cc81fa2b0b76b12afcbac2bd461..386ffb712d61d15c99ce8e813bfe5b1fc953543a 100644 --- a/xtrn/DDMsgReader/readme.txt +++ b/xtrn/DDMsgReader/readme.txt @@ -1,6 +1,6 @@ Digital Distortion Message Reader - Version 1.89 - Release date: 2023-11-30 + Version 1.90 + Release date: 2023-12-04 by @@ -154,21 +154,10 @@ When displaying a message to the user, this script will honor the attribute code toggles set up under Synchronet's configuration program (SCFG), under Message Options > Extra Attribute Codes. -If a message contains ANSI codes and the scrollable reader interface is being -used, Digital Distortion Message Reader will try to use frame.js and -scrollbar.js if those are available on your BBS filesystem. The reason is that -frame.js is able to parse and display ANSI in a scrollable interface very well. -If frame.js and scrollbar.js are not available on your BBS system, the reader -will still display the message, but it might look garbled if it has ANSI codes. -So for best results when reading ANSI messages,ensure that you have the latest -frame.js and scrollbar.js in your sbbs/exec/load directory. frame.js and -scrollbar.js are included in Synchronet 3.16, and I believe in earlier versions -too. As of this writing, the latest versions of those files can be found in -Synchronet's CVS repository at the following URLs: -frame.js: -http://cvs.synchro.net/cgi-bin/viewcvs.cgi/exec/load/frame.js?view=log -scrollbar.js: -http://cvs.synchro.net/cgi-bin/viewcvs.cgi/exec/load/scrollbar.js?view=log +As the sysop, when reading a message, the hotkey Ctrl-O will show the operator +menu. Most of the operator menu items are already available, but the operator +menu also has the additional option to add the author of the message (the 'from' +name) to the twit list. 3. Installation & Setup diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt index 75ff3f1082cb5ced215818bd23fde2341ca832b6..cfc3554eb782d39f71753301b3f2eb143f8efc87 100644 --- a/xtrn/DDMsgReader/revision_history.txt +++ b/xtrn/DDMsgReader/revision_history.txt @@ -5,6 +5,12 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +1.90 2023-12-04 New: operator menu for read mode, with the option to add + the author to the twit list, etc. + Fix: When refreshing a rectangular area of a message, if + it's a poll message, the background color for the voted + responses was used for the non-selected responses. + Removed the setting useScrollingInterfaceForANSIMessages. 1.89 2023-11-30 New: User option to toggle whether to display the email 'replied' indicator (defaults to true). Fix for setting colors for the key help lines so that the