From d4c3e67644b3f598a56144c9709f419f14c1b6ec Mon Sep 17 00:00:00 2001 From: Eric Oulashin <nightfox@synchro.net> Date: Tue, 10 Oct 2023 19:13:12 +0000 Subject: [PATCH] DDMsgReader: Improved new-to-you scan speed (and hopefully overall speed), indexed newscan improvements, etc. --- xtrn/DDMsgReader/DDMsgReader.cfg | 7 + xtrn/DDMsgReader/DDMsgReader.js | 1851 ++++++++++++++++--------- xtrn/DDMsgReader/DefaultTheme.cfg | 3 + xtrn/DDMsgReader/readme.txt | 86 +- xtrn/DDMsgReader/revision_history.txt | 19 + 5 files changed, 1281 insertions(+), 685 deletions(-) diff --git a/xtrn/DDMsgReader/DDMsgReader.cfg b/xtrn/DDMsgReader/DDMsgReader.cfg index c29bb0602f..8343d11009 100644 --- a/xtrn/DDMsgReader/DDMsgReader.cfg +++ b/xtrn/DDMsgReader/DDMsgReader.cfg @@ -67,5 +67,12 @@ quickUserValSetIndex=-1 ; PC. This could be a boolean (true/false) or the string "ask" to prompt every time saveAllHdrsWhenSavingMsgToBBSPC=false +; Default for the user setting for whether or not to use indexed mode for +; doing a newscan. For newscan only (not new to-you). If enabled, a newscan +; will appear as a menu listing the various sub-boards and how many total +; messages and number of new messages they have. This is the default for a +; user setting; users can toggle this for themselves as they like. +useIndexedModeForNewscan=false + ; The theme file name (for colors, strings, etc.) themeFilename=DefaultTheme.cfg diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js index 9c7ea938cc..2ea2ecc942 100644 --- a/xtrn/DDMsgReader/DDMsgReader.js +++ b/xtrn/DDMsgReader/DDMsgReader.js @@ -150,11 +150,29 @@ * Bug fix for going to a specific message in the message list (especially for lightbar mode) * 2023-09-20 Eric Oulashin Version 1.79 * Fixed poll voting for single-answer polls + * 2023-09-22 Eric Oulashin Version 1.80 beta + * Improved speed of new-to-you scans, and to an extent (hopefully) overall speed + * Bug fix: Setting reverseListOrder to "ask" in the .cfg file works properly again. + * Bug fix: When listing messages in reverse order, the selected menu index + * (for lightbar mode) is now correct. + * Bug fix: If the user is allowed to read deleted messages, then allow + * the left & right arrow keys to to the next/previous message if it's deleted. + * Small fixes for indexed scanning mode. + * New: For personal email, unread emails will have an 'unread' message indicator + * in the message list as a U between the message number and the 'from' name. + * New user setting: "Quit from reader to message list": When enabled, + * quitting from reader mode goes to the message list instead of exiting + * out of DDMsgReader fully. + * New user setting: Enter/selection from indexed mode menu shows message list + * (instead of going into reader mode) + * New user setting: List messages in reverse order + * 2023-10-10 Eric Oulashin Version 1.80 + * Releasing this version */ "use strict"; -// TODO: To make the new-to-you scan faster, Digital Man said checking messagebas.last_msg against the user's new +// TODO: To make the new-to-you scan faster, Digital Man said checking messagebase.last_msg against the user's new // scan pointer for that sub is the optimization to do // TODO: In the message list, add the ability to search with / similar to my area chooser. @@ -259,8 +277,8 @@ var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a'); // Reader version information -var READER_VERSION = "1.79"; -var READER_DATE = "2023-09-20"; +var READER_VERSION = "1.80"; +var READER_DATE = "2023-10-10"; // Keyboard key codes for displaying on the screen var UP_ARROW = ascii(24); @@ -364,11 +382,12 @@ var DOT_CHAR = "\xF9"; var CHECK_CHAR = "\xFB"; var THIN_RECTANGLE_LEFT = "\xDD"; var THIN_RECTANGLE_RIGHT = "\xDE"; + var BLOCK1 = "\xB0"; // Dimmest block var BLOCK2 = "\xB1"; var BLOCK3 = "\xB2"; var BLOCK4 = "\xDB"; // Brightest block -var MID_BLOCK = "\xDC"; +var MID_BLOCK = ascii(220); var TALL_UPPER_MID_BLOCK = "\xFE"; var UPPER_CENTER_BLOCK = "\xDF"; var LOWER_CENTER_BLOCK = "\xDC"; @@ -861,7 +880,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.PromptForMsgNum = DigDistMsgReader_PromptForMsgNum; this.ParseMsgAtCodes = DigDistMsgReader_ParseMsgAtCodes; this.ReplaceMsgAtCodeFormatStr = DigDistMsgReader_ReplaceMsgAtCodeFormatStr; - this.FindNextNonDeletedMsgIdx = DigDistMsgReader_FindNextNonDeletedMsgIdx; + this.FindNextReadableMsgIdx = DigDistMsgReader_FindNextReadableMsgIdx; this.ChangeSubBoard = DigDistMsgReader_ChangeSubBoard; this.EnhancedReaderChangeSubBoard = DigDistMsgReader_EnhancedReaderChangeSubBoard; this.ReplyToMsg = DigDistMsgReader_ReplyToMsg; @@ -870,7 +889,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) this.DisplayEnhancedMsgHdr = DigDistMsgReader_DisplayEnhancedMsgHdr; this.DisplayAreaChgHdr = DigDistMsgReader_DisplayAreaChgHdr; this.DisplayEnhancedReaderWholeScrollbar = DigDistMsgReader_DisplayEnhancedReaderWholeScrollbar; - this.UpdateEnhancedReaderScollbar = DigDistMsgReader_UpdateEnhancedReaderScollbar; + this.UpdateEnhancedReaderScrollbar = DigDistMsgReader_UpdateEnhancedReaderScrollbar; this.MessageIsDeleted = DigDistMsgReader_MessageIsDeleted; this.MessageIsLastFromUser = DigDistMsgReader_MessageIsLastFromUser; this.DisplayEnhReaderError = DigDistMsgReader_DisplayEnhReaderError; @@ -1012,10 +1031,6 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) // (will only be used for ANSI terminals). this.scrollingReaderInterface = true; - // reverseListOrder stores whether or not to arrange the message list descending - // by date. - this.reverseListOrder = false; - // displayBoardInfoInHeader specifies whether or not to display // the message group and sub-board lines in the header at the // top of the screen (an additional 2 lines). @@ -1189,16 +1204,22 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs) if (pScriptArgs.hasOwnProperty("usernum") && user.is_sysop) this.personalMailUserNum = pScriptArgs.usernum; } - // Read the settings from the config file - this.cfgFileSuccessfullyRead = false; - this.ReadConfigFile(); this.userSettings = { twitList: [], // Whether or not to use the scrollbar in the enhanced message reader useEnhReaderScrollbar: true, - // Whether or not to use indexed reader mode for doing a newscan - useIndexedModeForNewscan: false + // Whether or not to use indexed mode for doing a newscan + useIndexedModeForNewscan: false, + // Whether or not to list messages in reverse order + listMessagesInReverse: false, + // Whether or not quitting from the reader goes to the message list (instead of exiting altogether) + quitFromReaderGoesToMsgList: false, + // Whether or not the enter key in the indexed newscan menu shows the message list (rather than going to reader mode) + enterFromIndexMenuShowsMsgList: false }; + // Read the settings from the config file (some settings could set user settings) + this.cfgFileSuccessfullyRead = false; + this.ReadConfigFile(); this.ReadUserSettingsFile(false); // Set any other values specified by the command-line parameters // Reader start mode - Read or list mode @@ -1598,7 +1619,11 @@ function DigDistMsgReader_SetSubBoardCode(pSubCode) // For the DigDistMsgReader class: Populates the hdrsForCurrentSubBoard // array with message headers from the current sub-board. Filters out // messages that are deleted, unvalidated, private, and voting messages. -function DigDistMsgReader_PopulateHdrsForCurrentSubBoard() +// +// Parameters: +// pStartIdx: Optional - The index of the first message to retrieve. Only used if pEndIdx is also valid. +// pEndIdx: Optional - One past the index of the last message to retrieve. Only used if pStartIdx is also valid. +function DigDistMsgReader_PopulateHdrsForCurrentSubBoard(pStartIdx, pEndIdx) { if (this.subBoardCode == "mail") { @@ -1613,11 +1638,25 @@ function DigDistMsgReader_PopulateHdrsForCurrentSubBoard() { // First get all headers in a temporary array, then filter them into // this.hdrsForCurrentSubBoard. - // Note: get_all_msg_headers() was added in Synchronet 3.16. DDMsgReader requires a minimum - // of 3.18, so we're okay to use it. - // The first parameter is whether to include votes (the parameter was introduced in Synchronet 3.17+). - // We used to pass false here. - tmpHdrs = msgbase.get_all_msg_headers(true); + if (typeof(pStartIdx) === "number" && pStartIdx >= 0 && typeof(pEndIdx) === "number" && pEndIdx <= msgbase.total_msgs) + { + tmpHdrs = {}; + for (var i = pStartIdx; i < pEndIdx; ++i) + { + // Get message header by index; Don't expand fields, include votes + var msgHdr = msgbase.get_msg_header(true, i, false, true); + if (msgHdr != null) + tmpHdrs[msgHdr.number] = msgHdr; + } + } + else + { + // Note: get_all_msg_headers() was added in Synchronet 3.16. DDMsgReader requires a minimum + // of 3.18, so we're okay to use it. + // The first parameter is whether to include votes (the parameter was introduced in Synchronet 3.17+). + // We used to pass false here. + tmpHdrs = msgbase.get_all_msg_headers(true, false); // Include votes, don't expand fields + } msgbase.close(); } @@ -1713,37 +1752,13 @@ function DigDistMsgReader_GetMsgIdx(pHdrOrMsgNum) msgIdx = this.hdrsForCurrentSubBoardByMsgNum[msgNum]; else { - msgIdx = msgNumToIdxFromMsgbase(this.subBoardCode, msgNum); + msgIdx = absMsgNumToIdx(this.subBoardCode, msgNum); if (msgIdx != -1) this.hdrsForCurrentSubBoardByMsgNum[msgNum] = msgIdx; } } else - msgIdx = msgNumToIdxFromMsgbase(this.subBoardCode, msgNum); - return msgIdx; -} - -// Given a sub-board code and message number, this function gets the index -// of that message from the Synchronet messagebase. Returns -1 if not found. -// -// Parameters: -// pSubCode: The sub-board code -// pMsgNum: The message number -// -// Return value: The index of the message, or -1 if not found. -function msgNumToIdxFromMsgbase(pSubCode, pMsgNum) -{ - var msgIdx = -1; - - var msgbase = new MsgBase(pSubCode); - if (msgbase.open()) - { - var msgHdr = msgbase.get_msg_header(false, pMsgNum, false); - if (msgHdr != null) - msgIdx = msgHdr.offset; - msgbase.close(); - } - + msgIdx = absMsgNumToIdx(this.subBoardCode, msgNum); return msgIdx; } @@ -1789,7 +1804,7 @@ function DigDistMsgReader_RefreshSearchResultMsgHdr(pMsgIndex, pAttrib, pApply, else { var msgHeader = this.GetMsgHdrByIdx(pMsgIndex); - if (this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex)) + if (msgHeader != null && this.msgSearchHdrs[this.subBoardCode].indexed.hasOwnProperty(pMsgIndex)) { this.msgSearchHdrs[this.subBoardCode].indexed[pMsgIndex] = msgHeader; if (writeHdrToMsgbase) @@ -2116,6 +2131,8 @@ function DigDistMsgReader_ClearSearchData() // pInitialModeOverride: Optional (numeric) to override the initial mode in this // function (READER_MODE_READ or READER_MODE_LIST). If not // specified, defaults to this.startMode. +// pOutputSubBoardPopulationMsgs: Optional (boolean): Whether or not to output sub-board +// population messages // // Return value: An object with the following properties: // stoppedReading: Boolean - Whether or not the user stopped reading. @@ -2124,7 +2141,7 @@ function DigDistMsgReader_ReadOrListSubBoard(pSubBoardCode, pStartingMsgOffset, pAllowChgArea, pReturnOnNextAreaNav, pPauseOnNoMsgSrchResults, pPromptToGoNextIfNoResults, - pInitialModeOverride) + pInitialModeOverride, pOutputSubBoardPopulationMsgs) { var retObj = { stoppedReading: false @@ -2166,17 +2183,8 @@ function DigDistMsgReader_ReadOrListSubBoard(pSubBoardCode, pStartingMsgOffset, // specified. If there are no messages to read in the current sub-board, then // just return. var pauseOnNoSearchResults = (typeof(pPauseOnNoMsgSrchResults) == "boolean" ? pPauseOnNoMsgSrchResults : true); - /* - // TODO: If doing a new-to-you scan, then maybe this could be optimized - if (this.searchType == SEARCH_TO_USER_NEW_SCAN) - { - var subCode = pSubBoardCode != null ? pSubBoardCode : this.subBoardCode; - // Temporary - //if (user.is_sysop) console.print("\x01nNew-to-user scan for " + subCode + "\r\n"); - // End Temporary - } - */ - if (!this.PopulateHdrsIfSearch_DispErrorIfNoMsgs(true, true, pauseOnNoSearchResults)) + var outputPopulateHdrsMsgs = (typeof(pOutputSubBoardPopulationMsgs) === "boolean" ? pOutputSubBoardPopulationMsgs : true); + if (!this.PopulateHdrsIfSearch_DispErrorIfNoMsgs(true, outputPopulateHdrsMsgs, pauseOnNoSearchResults)) { retObj.stoppedReading = false; return retObj; @@ -2455,7 +2463,8 @@ function searchTypeRequiresSearchText(pSearchType) // scope: "S" for sub-board, "G" for group, or "A" for all. // If this is not specified, the user will be prompted for the // scan scope. -function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar) +// pOutputMessages: Optional boolean: Whether or not to output scan status messages. Defaults to true. +function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar, pOutputMessages) { var scanScopeChar = ""; if ((typeof(pScanScopeChar) == "string") && /^[SGA]$/.test(pScanScopeChar)) @@ -2472,13 +2481,14 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar { console.crlf(); //console.print(replaceAtCodesInStr(this.text.msgScanAbortedText)); - //console.crlf(); console.putmsg(this.text.msgScanAbortedText); console.crlf(); console.pause(); return; } } + + var outputMessages = (typeof(pOutputMessages) === "boolean" ? pOutputMessages : true); // Do some logging if verbose logging is enabled if (gCmdLineArgVals.verboselogging) @@ -2562,13 +2572,17 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar this.setSubBoardCode(subBoardsToScan[subCodeIdx]); // Needs to be set before getting the last read/scan pointer index if (msg_area.sub[this.subBoardCode].can_read && ((msg_area.sub[this.subBoardCode].scan_cfg & pScanCfgOpt) == pScanCfgOpt)) { - // If running a new message scan (not new-to-you or search), output which sub-board that is currently being scanned - if (pScanMode == SCAN_NEW || pScanMode == SCAN_BACK) + // If we are to output status messages, then do so and + if (outputMessages) { - var statusText = format(this.text.scanningSubBoardText, subBoardGrpAndName(this.subBoardCode)); - console.print("\x01n" + replaceAtCodesAndRemoveCRLFs(statusText) + "\x01n"); - console.crlf(); - console.line_counter = 0; // Prevent pausing for screen output and when displaying a message + // If we're running a new message scan, then output which sub-board that is currently being scanned + if (pScanMode == SCAN_NEW || pScanMode == SCAN_BACK || pScanMode == SCAN_UNREAD) + { + var statusText = format(this.text.scanningSubBoardText, subBoardGrpAndName(this.subBoardCode)); + console.print("\x01n" + replaceAtCodesAndRemoveCRLFs(statusText) + "\x01n"); + console.crlf(); + console.line_counter = 0; // Prevent pausing for screen output and when displaying a message + } } var grpIndex = msg_area.sub[this.subBoardCode].grp_index; @@ -2580,16 +2594,19 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar var msgbase = new MsgBase(this.subBoardCode); if (msgbase.open()) { - // Get a filtered list of messages for this sub-board - this.PopulateHdrsForCurrentSubBoard(); - //this.setSubBoardCode(subBoardsToScan[subCodeIdx]); // Needs to be set before getting the last read/scan pointer index + // TODO: This commented-out section of code may be redundant and no longer necessary. + // Perhaps this should be refactored? GetScanPtrMsgIdx() and FindNextReadableMsgIdx() + // may be relying on the fact that we poulate an array of cached message headers, + // filtered where deleted & unreadable ones are removed, so scan_ptr and last_read may + // not be quite accurate when we're using our cached header arrays. + /* // If the current sub-board contains only deleted messages, // or if the user has already read the last message in this // sub-board, then skip it. var scanPtrMsgIdx = this.GetScanPtrMsgIdx(); - var nonDeletedMsgsExist = (this.FindNextNonDeletedMsgIdx(scanPtrMsgIdx-1, true) > -1); + var readableMsgsExist = (this.FindNextReadableMsgIdx(scanPtrMsgIdx-1, true) > -1); var userHasReadLastMessage = false; if (this.subBoardCode != "mail") { @@ -2604,13 +2621,15 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar } } } - if (!nonDeletedMsgsExist || userHasReadLastMessage) + if (!readableMsgsExist || userHasReadLastMessage) { if (msgbase != null) msgbase.close(); continue; } + */ + var scanPtrMsgIdx = this.GetScanPtrMsgIdx(); // In the switch cases below, bbs.curgrp and bbs.cursub are // temporarily changed the user's sub-board to the current // sub-board so that certain @-codes (such as @GRP-L@, etc.) @@ -2625,7 +2644,21 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar // the scan pointer index is -1 (one unread message) or if it points to // a message within the number of messages in the sub-board. var totalNumMsgs = msgbase.total_msgs; - if ((totalNumMsgs > 0) && ((scanPtrMsgIdx == -1) || (scanPtrMsgIdx < totalNumMsgs-1))) + // If the user's scan_ptr for the sub-board isn't the 'last message' special value, + // then check the user's scan_ptr against the message number of the last readable + // message (i.e., the last message in the sub-board could be a vote header, which + // isn't readable) + var scanPtrBeforeLastReadableMsg = false; + if (!subBoardScanPtrIsLatestMsgSpecialVal(this.subBoardCode)) + { + var lastReadableMsgHdr = getLastReadableMsgHdrInMsgbase(msgbase, this.subBoardCode); + if (lastReadableMsgHdr != null) + scanPtrBeforeLastReadableMsg = (msg_area.sub[this.subBoardCode].scan_ptr < lastReadableMsgHdr.number); + else + scanPtrBeforeLastReadableMsg = (msg_area.sub[this.subBoardCode].scan_ptr < msgbase.last_msg); + } + //if ((totalNumMsgs > 0) && ((scanPtrMsgIdx == -1) || (scanPtrMsgIdx < totalNumMsgs-1))) + if (totalNumMsgs > 0 && scanPtrBeforeLastReadableMsg) { bbs.curgrp = grpIndex; bbs.cursub = subIndex; @@ -2639,7 +2672,7 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar // the user to change to a different message area, don't pause // when there's no search results in a sub-board, and return // instead of going to the next sub-board via navigation. - var readRetObj = this.ReadOrListSubBoard(null, startMsgIdx, false, true, false); + var readRetObj = this.ReadOrListSubBoard(null, startMsgIdx, false, true, false, null, null, false); // If the user stopped reading & decided to quit, then exit the // message scan loops. if (readRetObj.stoppedReading) @@ -2659,7 +2692,7 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar // in a sub-board, and return instead of going to the next // sub-board via navigation. this.searchType = SEARCH_TO_USER_CUR_MSG_AREA; - var readRetObj = this.ReadOrListSubBoard(null, 0, false, true, false); + var readRetObj = this.ReadOrListSubBoard(null, 0, false, true, false, null, null, false); // If the user stopped reading & decided to quit, then exit the // message scan loops. if (readRetObj.stoppedReading) @@ -2678,13 +2711,17 @@ function DigDistMsgReader_MessageAreaScan(pScanCfgOpt, pScanMode, pScanScopeChar // in a sub-board, and return instead of going to the next // sub-board via navigation. this.searchType = SEARCH_TO_USER_NEW_SCAN; - var readRetObj = this.ReadOrListSubBoard(null, 0, false, true, false); - // If the user stopped reading & decided to quit, then exit the - // message scan loops. - if (readRetObj.stoppedReading) + var anyUnreadToUser = anyUnreadMsgsToUserWithMsgbase(msgbase, this.subBoardCode); + if (anyUnreadToUser) { - continueNewScan = false; - userAborted = true; + var readRetObj = this.ReadOrListSubBoard(null, 0, false, true, false, null, null, false); + // If the user stopped reading & decided to quit, then exit the + // message scan loops. + if (readRetObj.stoppedReading) + { + continueNewScan = false; + userAborted = true; + } } break; default: @@ -2879,15 +2916,15 @@ function DigDistMsgReader_ReadMessages(pSubBoardCode, pStartingMsgOffset, pRetur if ((testMsgHdr == null) || (((testMsgHdr.attr & MSG_DELETE) == MSG_DELETE) && !canViewDeletedMsgs())) { // First try going forward - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(msgIndex, true); + var readableMsgIdx = this.FindNextReadableMsgIdx(msgIndex, true); // If a non-deleted message was not found, then try going backward. - if (nonDeletedMsgIdx == -1) - nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(msgIndex, false); + if (readableMsgIdx == -1) + readableMsgIdx = this.FindNextReadableMsgIdx(msgIndex, false); // If a non-deleted message was found, then set msgIndex to it. // Otherwise, tell the user there are no messages in this sub-board // and return. - if (nonDeletedMsgIdx > -1) - msgIndex = nonDeletedMsgIdx; + if (readableMsgIdx > -1) + msgIndex = readableMsgIdx; else { console.clear("\x01n"); @@ -2944,9 +2981,9 @@ function DigDistMsgReader_ReadMessages(pSubBoardCode, pStartingMsgOffset, pRetur // If the user's next action in the last iteration was to go to the // previous message, then go backwards; otherwise, go forward. if (previousNextAction == ACTION_GO_PREVIOUS_MSG) - msgIndex = this.FindNextNonDeletedMsgIdx(msgIndex, false); + msgIndex = this.FindNextReadableMsgIdx(msgIndex, false); else - msgIndex = this.FindNextNonDeletedMsgIdx(msgIndex, true); + msgIndex = this.FindNextReadableMsgIdx(msgIndex, true); continueOn = ((msgIndex >= 0) && (msgIndex < this.NumMessages())); } else if (readMsgRetObj.nextAction == ACTION_QUIT) // Quit @@ -3030,14 +3067,14 @@ function DigDistMsgReader_ReadMessages(pSubBoardCode, pStartingMsgOffset, pRetur else if (readMsgRetObj.nextAction == ACTION_GO_FIRST_MSG) // Go to the first message { // Go to the first message that's not marked as deleted. This passes -1 as the - // starting message index because FindNextNonDeletedMsgIdx() will increment it + // starting message index because FindNextReadableMsgIdx() will increment it // before searching in order to find the "next" message. - msgIndex = this.FindNextNonDeletedMsgIdx(-1, true); + msgIndex = this.FindNextReadableMsgIdx(-1, true); } else if (readMsgRetObj.nextAction == ACTION_GO_LAST_MSG) // Go to the last message { // Go to the last message that's not marked as deleted - msgIndex = this.FindNextNonDeletedMsgIdx(this.NumMessages(), false); + msgIndex = this.FindNextReadableMsgIdx(this.NumMessages(), false); } else if (readMsgRetObj.nextAction == ACTION_CHG_MSG_AREA) // Change message area, if allowed { @@ -3068,16 +3105,16 @@ function DigDistMsgReader_ReadMessages(pSubBoardCode, pStartingMsgOffset, pRetur if ((testMsgHdr == null) || (((testMsgHdr.attr & MSG_DELETE) == MSG_DELETE) && !canViewDeletedMsgs())) { // First try going forward - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(msgIndex, true); + var readableMsgIdx = this.FindNextReadableMsgIdx(msgIndex, true); // If a non-deleted message was not found, then try going backward. - if (nonDeletedMsgIdx == -1) - nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(msgIndex, false); + if (readableMsgIdx == -1) + readableMsgIdx = this.FindNextReadableMsgIdx(msgIndex, false); // If a non-deleted message was found, then set msgIndex to it. // Otherwise, return. // Note: If there are no messages in the chosen sub-board at all, // then the error would have already been shown. - if (nonDeletedMsgIdx > -1) - msgIndex = nonDeletedMsgIdx; + if (readableMsgIdx > -1) + msgIndex = readableMsgIdx; else { if (this.NumMessages() != 0) @@ -3150,19 +3187,6 @@ function DigDistMsgReader_ReadMessages(pSubBoardCode, pStartingMsgOffset, pRetur } else { - // If this.reverseListOrder is the string "ASK", the user will be prompted - // on the last line of the screen for whether they want to list the - // messages in reverse order. So, erase the help line on the bottom of - // the screen. - if ((typeof(this.reverseListOrder) == "string") && (this.reverseListOrder.toUpperCase() == "ASK")) - { - if (this.scrollingReaderInterface && console.term_supports(USER_ANSI)) - { - console.gotoxy(1, console.screen_rows); - console.cleartoeol("\x01n"); - } - } - // List messages var listRetObj = this.ListMessages(null, pAllowChgArea); // If the user wants to quit, then stop the input loop. @@ -3259,14 +3283,6 @@ function DigDistMsgReader_ListMessages(pSubBoardCode, pAllowChgSubBoard) // messages. this.SetMsgListPauseTextAndLightbarHelpLine(); - // If this.reverseListOrder is the string "ASK", prompt the user for whether - // they want to list the messages in reverse order. - if ((typeof(this.reverseListOrder) == "string") && (this.reverseListOrder.toUpperCase() == "ASK")) - { - if (numMessages(bbs.cursub_code) > 0) - this.reverseListOrder = !console.noyes("\x01n\x01cList in reverse (newest on top)"); - } - // List the messages using the lightbar or traditional interface, depending on // what this.msgListUseLightbarListInterface is set to. The lightbar interface requires ANSI. if (this.msgListUseLightbarListInterface && canDoHighASCIIAndANSI()) @@ -3371,7 +3387,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) console.gotoxy(curpos); // Prompt the user whether or not to continue or to read a message // (by message number). - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) retvalObj = this.PromptContinueOrReadMsg((this.tradListTopMsgIdx == this.NumMessages()-1), lastScreen, allowChgSubBoard); else retvalObj = this.PromptContinueOrReadMsg((this.tradListTopMsgIdx == 0), lastScreen, allowChgSubBoard); @@ -3390,7 +3406,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // this.tradListTopMsgIdx in order to do so. if (retvalObj.userInput == "P") { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) { this.tradListTopMsgIdx += this.tradMsgListNumLines; // If we go past the beginning, then we need to reset @@ -3412,7 +3428,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // this.tradListTopMsgIdx appropriately. else if (retvalObj.userInput == "N") { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) this.tradListTopMsgIdx -= this.tradMsgListNumLines; else this.tradListTopMsgIdx += this.tradMsgListNumLines; @@ -3420,7 +3436,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // First page else if (retvalObj.userInput == "F") { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) this.tradListTopMsgIdx = this.NumMessages() - 1; else this.tradListTopMsgIdx = 0; @@ -3428,7 +3444,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // Last page else if (retvalObj.userInput == "L") { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) { this.tradListTopMsgIdx = (this.NumMessages() % this.tradMsgListNumLines) - 1; // If this.tradListTopMsgIdx is now invalid (below 0), then adjust it @@ -3479,8 +3495,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard) // The message header could have the "isBogus" property, for instance, if // it's a vote message (introduced in Synchronet 3.17). var tmpMsgHdr = this.GetMsgHdrByIdx(msgNum-1); - var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false); - if (!hdrIsBogus) + if (tmpMsgHdr != null) var returnObj = this.EditExistingMsg(msgNum-1); else { @@ -3788,10 +3803,14 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // The user choice a message to read this.lightbarListSelectedMsgIdx = msgListMenu.selectedItemIdx; msgHeader = this.GetMsgHdrByIdx(this.lightbarListSelectedMsgIdx, this.showScoresInMsgList); - this.PrintMessageInfo(msgHeader, true, this.lightbarListSelectedMsgIdx+1); + // TODO: Is this commented-out code necessary? In indexed newscan mode, when reading a + // message, then switching to the list, then selecting a message to read, this is re-printing + // the message info line from the message list and it's appearing over the help line at the + // bottom of the screen. + //if (msgHeader != null) + // this.PrintMessageInfo(msgHeader, true, this.lightbarListSelectedMsgIdx+1); console.gotoxy(this.lightbarListCurPos); // Make sure the cursor is still in the right place - var hdrIsBogus = (msgHeader.hasOwnProperty("isBogus") ? msgHeader.isBogus : false); - if (!hdrIsBogus) + if (msgHeader != null) { // Allow the user to read the current message. var readMsg = true; @@ -3873,8 +3892,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // it's a vote message (introduced in Synchronet 3.17). //GetMsgHdrByIdx(pMsgIdx, pExpandFields) var tmpMsgHdr = this.GetMsgHdrByIdx(+(userInput-1), false); - var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false); - if (!hdrIsBogus) + if (tmpMsgHdr != null) { // Confirm with the user whether to read the message var readMsg = true; @@ -3953,8 +3971,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // The message header could have the "isBogus" property, for instance, if // it's a vote message (introduced in Synchronet 3.17). var tmpMsgHdr = this.GetMsgHdrByIdx(this.lightbarListSelectedMsgIdx, false); - var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false); - if (!hdrIsBogus) + if (tmpMsgHdr != null) { // Ask the user if they really want to edit the message console.gotoxy(1, console.screen_rows); @@ -4104,7 +4121,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) DisplayHelpLine(this.msgListLightbarModeHelpLine); } // Ctrl-D: Batch delete (for selected messages) - else if (lastUserInputUpper == CTRL_D) + else if (lastUserInputUpper == this.msgListKeys.batchDelete) // CTRL_D { if (this.CanDelete() || this.CanDeleteLastMsg()) { @@ -4116,7 +4133,14 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard) // 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); + var delSuccessful = this.PromptAndDeleteOrUndeleteSelectedMessages({ x: 1, y: console.screen_rows}, true); + // If successfully deleted, have the menu draw only the check character column in the + // next iteration + if (delSuccessful) + { + this.selectedMessages = {}; + drawMenu = true; + } // In case all messages were deleted, if the user can't view deleted messages, // show an appropriate message and don't continue listing messages. @@ -4299,6 +4323,12 @@ function DigDistMsgReader_CreateLightbarMsgListMenu() additionalQuitKeys += this.msgListKeys.editMsg; msgListMenu.AddAdditionalQuitKeys(additionalQuitKeys); + // Add additional keypresses for PageUp, PageDown, HOME (first page), and END (last page) + msgListMenu.AddAdditionalPageUpKeys("Pp"); // Previous page + msgListMenu.AddAdditionalPageDownKeys("Nn"); // Next page + msgListMenu.AddAdditionalFirstPageKeys("Ff"); // First page + msgListMenu.AddAdditionalLastPageKeys("Ll"); // Last page + // Change the menu's NumItems() and GetItem() function to reference // the message list in this object rather than add the menu items // to the menu @@ -4306,9 +4336,10 @@ function DigDistMsgReader_CreateLightbarMsgListMenu() msgListMenu.NumItems = function() { return this.msgReader.NumMessages(); }; + msgListMenu.readingPersonalEmail = this.subBoardCode.toLowerCase() == "mail"; msgListMenu.GetItem = function(pItemIndex) { var menuItemObj = this.MakeItemWithRetval(-1); - var itemIdx = (this.msgReader.reverseListOrder ? this.msgReader.NumMessages() - pItemIndex - 1 : pItemIndex); + var itemIdx = (this.msgReader.userSettings.listMessagesInReverse ? this.msgReader.NumMessages() - pItemIndex - 1 : pItemIndex); // In order to get vote score information (displayed if the user's terminal is wide // enough), the 2nd parameter to GetMsgHdrByIdx() should be true. var msgHdr = this.msgReader.GetMsgHdrByIdx(itemIdx, this.msgReader.showScoresInMsgList); @@ -4320,33 +4351,35 @@ function DigDistMsgReader_CreateLightbarMsgListMenu() menuItemObj.retval = msgHdr.number; if (this.msgReader.subBoardCode != "mail") menuItemObj.useAltColors = userHandleAliasNameMatch(msgHdr.to); - // If the message is marked as deleted, ensure the correct color is used - // for the mark character in the menu + // For any indicator character next to the message, prioritize deleted, then selected, + // then unread, then attachments + // First, to ensure the correct status character color is used, copy the menu item colors + // in any of these cases; then change the attributes for the 2nd color (selected). + if (this.msgReader.MessageIsSelected(this.msgReader.subBoardCode, pItemIndex) || (msgHdr.attr & MSG_DELETE) == MSG_DELETE || (msgHdr.attr & MSG_READ) == 0 || msgHdrHasAttachmentFlag(msgHdr)) + { + menuItemObj.itemColor = []; + for (var i = 0; i < this.colors.itemColor.length; ++i) + menuItemObj.itemColor.push(this.colors.itemColor[i]); + menuItemObj.itemSelectedColor = []; + for (var i = 0; i < this.colors.selectedItemColor.length; ++i) + menuItemObj.itemSelectedColor.push(this.colors.selectedItemColor[i]); + } + // Change the color + // Deleted if ((msgHdr.attr & MSG_DELETE) == MSG_DELETE) { - var fromColor = this.msgReader.colors.msgListFromColor; - var toColor = this.msgReader.colors.msgListToColor; - var subjColor = this.msgReader.colors.msgListSubjectColor; - if ((this.msgReader.subBoardCode != "mail") && (userHandleAliasNameMatch(msgHdr.to))) - { - fromColor = this.msgReader.colors.msgListToUserFromColor; - toColor = this.msgReader.colors.msgListToUserToColor; - subjColor = this.msgReader.colors.msgListToUserSubjectColor; - } - menuItemObj.itemColor = [{start: msgListIdxes.msgNumStart, end: msgListIdxes.msgNumEnd, attrs: this.msgReader.colors.msgListMsgNumColor}, - {start: msgListIdxes.selectMarkStart, end: msgListIdxes.selectMarkEnd, attrs: "\x01r\x01h\x01i"}, - {start: msgListIdxes.fromNameStart, end: msgListIdxes.fromNameEnd, attrs: fromColor}, - {start: msgListIdxes.toNameStart, end: msgListIdxes.toNameEnd, attrs: toColor}, - {start: msgListIdxes.subjStart, end: msgListIdxes.subjEnd, attrs: subjColor}, - {start: msgListIdxes.dateStart, end: msgListIdxes.dateEnd, attrs: this.msgReader.colors.msgListDateColor}, - {start: msgListIdxes.timeStart, end: msgListIdxes.timeEnd, attrs: this.msgReader.colors.msgListTimeColor}]; - menuItemObj.itemSelectedColor = [{start: msgListIdxes.msgNumStart, end: msgListIdxes.msgNumEnd, attrs: this.msgReader.colors.msgListMsgNumHighlightColor}, - {start: msgListIdxes.selectMarkStart, end: msgListIdxes.selectMarkEnd, attrs: "\x01r\x01h\x01i" + this.msgReader.colors.msgListHighlightBkgColor}, - {start: msgListIdxes.fromNameStart, end: msgListIdxes.fromNameEnd, attrs: this.msgReader.colors.msgListFromHighlightColor}, - {start: msgListIdxes.toNameStart, end: msgListIdxes.toNameEnd, attrs: this.msgReader.colors.msgListToHighlightColor}, - {start: msgListIdxes.subjStart, end: msgListIdxes.subjEnd, attrs: this.msgReader.colors.msgListSubjHighlightColor}, - {start: msgListIdxes.dateStart, end: msgListIdxes.dateEnd, attrs: this.msgReader.colors.msgListDateHighlightColor}, - {start: msgListIdxes.timeStart, end: msgListIdxes.timeEnd, attrs: this.msgReader.colors.msgListTimeHighlightColor}]; + if (menuItemObj.itemColor.length >= 2) + menuItemObj.itemColor[1].attrs = "\x01r\x01h\x01i"; + if (menuItemObj.itemSelectedColor.length >= 2) + menuItemObj.itemSelectedColor[1].attrs = "\x01r\x01h\x01i" + this.msgReader.colors.msgListHighlightBkgColor; + } + // Selected, unread, or has attachments + else if (this.msgReader.MessageIsSelected(this.msgReader.subBoardCode, pItemIndex) || (msgHdr.attr & MSG_READ) == 0 || msgHdrHasAttachmentFlag(msgHdr)) + { + if (menuItemObj.itemColor.length >= 2) + menuItemObj.itemColor[1].attrs = "\x01n" + this.msgReader.colors.selectedMsgMarkColor; + if (menuItemObj.itemSelectedColor.length >= 2) + menuItemObj.itemSelectedColor[1].attrs = "\x01n" + this.msgReader.colors.selectedMsgMarkColor + this.msgReader.colors.msgListHighlightBkgColor; } } return menuItemObj; @@ -4424,9 +4457,12 @@ function DigDistMsgReader_CreateLightbarMsgGrpMenu() }; // Set the currently selected item to the current group + msgGrpMenu.SetSelectedItemIdx(msg_area.sub[this.subBoardCode].grp_index); + /* msgGrpMenu.selectedItemIdx = msg_area.sub[this.subBoardCode].grp_index; if (msgGrpMenu.selectedItemIdx >= msgGrpMenu.topItemIdx+msgGrpMenu.GetNumItemsPerPage()) msgGrpMenu.topItemIdx = msgGrpMenu.selectedItemIdx - msgGrpMenu.GetNumItemsPerPage() + 1; + */ return msgGrpMenu; } @@ -4509,14 +4545,20 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx) // Set the currently selected item to the current group if (msg_area.sub[this.subBoardCode].grp_index == pGrpIdx) { + subBoardMenu.SetSelectedItemIdx(msg_area.sub[this.subBoardCode].index); + /* subBoardMenu.selectedItemIdx = msg_area.sub[this.subBoardCode].index; if (subBoardMenu.selectedItemIdx >= subBoardMenu.topItemIdx+subBoardMenu.GetNumItemsPerPage()) subBoardMenu.topItemIdx = subBoardMenu.selectedItemIdx - subBoardMenu.GetNumItemsPerPage() + 1; + */ } else { + subBoardMenu.SetSelectedItemIdx(0); + /* subBoardMenu.selectedItemIdx = 0; subBoardMenu.topItemIdx = 0; + */ } return subBoardMenu; @@ -4524,8 +4566,11 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx) // For the DigDistMsgLister class: Adjusts lightbar menu indexes for a message list menu function DigDistMsgReader_AdjustLightbarMsgListMenuIdxes(pMsgListMenu) { + pMsgListMenu.SetSelectedItemIdx(this.lightbarListSelectedMsgIdx); + /* pMsgListMenu.selectedItemIdx = this.lightbarListSelectedMsgIdx; pMsgListMenu.topItemIdx = this.lightbarListTopMsgIdx; + */ // In the DDLightbarMenu class, the top index on the last page should // allow for displaying a full page of items. So if @@ -4622,11 +4667,14 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum, pRet // To access one of these, use brackets; i.e., msgHeader['to'] if (highlight) { - // For any indicator character next to the message, prioritize selected, then deleted, then attachments + // For any indicator character next to the message, prioritize selected, then deleted, + // then unread, 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 (this.readingPersonalEmail && (pMsgHeader.attr & MSG_READ) == 0) + msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + this.colors.msgListHighlightBkgColor + "U\x01n"; else if (msgHdrHasAttachmentFlag(pMsgHeader)) msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + this.colors.msgListHighlightBkgColor + "A\x01n"; var fromName = pMsgHeader.from; @@ -4644,22 +4692,25 @@ function DigDistMsgReader_PrintMessageInfo(pMsgHeader, pHighlight, pMsgNum, pRet } else { - msgHdrStr += format(format(this.sMsgInfoFormatHighlightStr, msgNum, msgIndicatorChar, + msgHdrStr += format(this.sMsgInfoFormatHighlightStr, msgNum, msgIndicatorChar, fromName.substr(0, this.FROM_LEN), pMsgHeader.to.substr(0, this.TO_LEN), pMsgHeader.subject.substr(0, this.SUBJ_LEN), - sDate, sTime)); + sDate, sTime); } } else { - // For any indicator character next to the message, prioritize selected, then deleted, then attachments + // For any indicator character next to the message, prioritize selected, then deleted, + // then unread, 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 (this.readingPersonalEmail && (pMsgHeader.attr & MSG_READ) == 0) + msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + "U\x01n"; else if (msgHdrHasAttachmentFlag(pMsgHeader)) - msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + "A\x01n"; + msgIndicatorChar = "\x01n" + this.colors.selectedMsgMarkColor + "A\x01n"; // Determine whether to use the normal, "to-user", or "from-user" format string. // The differences are the colors. Then, output the message information line. @@ -4777,7 +4828,7 @@ function DigDistMsgReader_PromptContinueOrReadMsg(pStart, pEnd, pAllowChgSubBoar // 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.getkey(K_NOECHO); - if (userInput != CTRL_D) + if (userInput != this.enhReaderKeys.batchDelete) // CTRL_D { console.ungetstr(userInput); userInput = console.getkeys(allowedKeys, this.HighestMessageNum()).toString(); @@ -4798,8 +4849,7 @@ function DigDistMsgReader_PromptContinueOrReadMsg(pStart, pEnd, pAllowChgSubBoar // The message header could have the "isBogus" property, for instance, if // it's a vote message (introduced in Synchronet 3.17). var tmpMsgHdr = this.GetMsgHdrByIdx(+(userInput-1), false); - var hdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false); - if (!hdrIsBogus) + if (tmpMsgHdr != null) { // Confirm with the user whether to read the message var readMsg = true; @@ -4922,6 +4972,19 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea) retObj.msgNotReadable = !isReadableMsgHdr(msgHeader, this.subBoardCode); if (retObj.msgNotReadable) return retObj; + + // Mark the message as read if it was written to the current user + if (userHandleAliasNameMatch(msgHeader.to) && ((msgHeader.attr & MSG_READ) == 0)) + { + // Using applyAttrsInMsgHdrInMessagbase(), which loads the header without + // 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, true); + } + + // Updating message pointers etc. + updateScanPtrAndOrLastRead(this.subBoardCode, msgHeader, this.doingMsgScan); // Update the message list index variables so that the message list is in // the right spot for the message currently being read @@ -4962,34 +5025,6 @@ function DigDistMsgReader_ReadMessageEnhanced(pOffset, pAllowChgArea) else retObj = this.ReadMessageEnhanced_Traditional(msgHeader, allowChgMsgArea, messageText, pOffset, getMsgBodyRetObj.pmode); - // Mark the message as read if it was written to the current user - if (userNameHandleAliasMatch(msgHeader.to) && ((msgHeader.attr & MSG_READ) == 0)) - { - // Using applyAttrsInMsgHdrInMessagbase(), which loads the header without - // 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, true); - } - - // If not reading personal email and not doing a search, then update the - // scan & last read message pointers. - if ((this.subBoardCode != "mail") && (this.searchType == SEARCH_NONE)) - { - if (typeof(msg_area.sub[this.subBoardCode].scan_ptr) === "number") - { - if (msg_area.sub[this.subBoardCode].scan_ptr != 0xffffffff && msg_area.sub[this.subBoardCode].scan_ptr < msgHeader.number) - msg_area.sub[this.subBoardCode].scan_ptr = msgHeader.number; - } - else - msg_area.sub[this.subBoardCode].scan_ptr = msgHeader.number; - //if (msgHeader.number > GetScanPtrOrLastMsgNum(this.subBoardCode)) - // msg_area.sub[this.subBoardCode].scan_ptr = msgHeader.number; - msg_area.sub[this.subBoardCode].last_read = msgHeader.number; - //if (msgHeader.number > msg_area.sub[this.subBoardCode].last_read) - // msg_area.sub[this.subBoardCode].last_read = msgHeader.number; - } - return retObj; } // Helper method for ReadMessageEnhanced() - Does the scrollable reader interface @@ -5013,7 +5048,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA fractionToLastPage = pFractionToLastPage; solidBlockStartRow = msgReaderObj.msgAreaTop + Math.floor(numNonSolidScrollBlocks * pFractionToLastPage); if (solidBlockStartRow != solidBlockLastStartRow) - msgReaderObj.UpdateEnhancedReaderScollbar(solidBlockStartRow, solidBlockLastStartRow, numSolidScrollBlocks); + msgReaderObj.UpdateEnhancedReaderScrollbar(solidBlockStartRow, solidBlockLastStartRow, numSolidScrollBlocks); solidBlockLastStartRow = solidBlockStartRow; console.gotoxy(1, console.screen_rows); } @@ -5022,7 +5057,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA { var infoSolidBlockStartRow = msgReaderObj.msgAreaTop + Math.floor(numNonSolidInfoScrollBlocks * pFractionToLastPage); if (infoSolidBlockStartRow != lastInfoSolidBlockStartRow) - msgReaderObj.UpdateEnhancedReaderScollbar(infoSolidBlockStartRow, lastInfoSolidBlockStartRow, numInfoSolidScrollBlocks); + msgReaderObj.UpdateEnhancedReaderScrollbar(infoSolidBlockStartRow, lastInfoSolidBlockStartRow, numInfoSolidScrollBlocks); lastInfoSolidBlockStartRow = infoSolidBlockStartRow; console.gotoxy(1, console.screen_rows); } @@ -5164,10 +5199,8 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA case this.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); + var selected = this.EnhReaderPromptYesNo("Select this message", msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr, solidBlockStartRow, numSolidScrollBlocks, true); + this.ToggleSelectedMessage(this.subBoardCode, pOffset, selected); writeMessage = false; // No need to refresh the message break; case this.enhReaderKeys.batchDelete: @@ -5525,7 +5558,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA // if we don't find one, we'll still want to return from this // function (with message index -1) so that this script can go // onto the previous message sub-board/group. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, false); + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, false); // As a screen redraw optimization: Only return if there is a valid new // message offset or the user is allowed to change to a different sub-board. // Otherwise, don't return, and don't refresh the message on the screen. @@ -6192,7 +6225,17 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA } break; case this.enhReaderKeys.quit: // Quit - retObj.nextAction = ACTION_QUIT; + case KEY_ESC: + // Normally, if quitFromReaderGoesToMsgList is enabled, then do that + if (this.userSettings.quitFromReaderGoesToMsgList) + { + console.attributes = "N"; + console.crlf(); + console.print("Loading..."); + retObj.nextAction = ACTION_DISPLAY_MSG_LIST; + } + else + retObj.nextAction = ACTION_QUIT; continueOn = false; break; default: @@ -6228,11 +6271,10 @@ function DigDistMsgReader_ScrollableReaderNextReadableMessage(pOffset, pMsgInfo, nextAction: ACTION_NONE }; - // Look for a later message that isn't marked for deletion. Even - // if we don't find one, we'll still want to return from this - // function (with message index -1) so that this script can go - // onto the next message sub-board/group. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, true); + // Look for the next readable message. Even if we don't find one, we'll still + // want to return from this function (with message index -1) so that this script + // can go onto the next message sub-board/group. + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, true); // Note: Unlike the left arrow key, we want to exit this method when // navigating to the next message, regardless of whether or not the // user is allowed to change to a different sub-board, so that processes @@ -6696,7 +6738,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg // if we don't find one, we'll still want to return from this // function (with message index -1) so that this script can go // onto the previous message sub-board/group. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, false); + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, false); var goToPrevMessage = false; if ((retObj.newMsgOffset > -1) || allowChgMsgArea) { @@ -6724,7 +6766,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg // if we don't find one, we'll still want to return from this // function (with message index -1) so that this script can go // onto the next message sub-board/group. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, true); + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, true); // Note: Unlike the left arrow key, we want to exit this method when // navigating to the next message, regardless of whether or not the // user is allowed to change to a different sub-board, so that processes @@ -7148,7 +7190,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg // in their twit list, change the message currently being viewed. if (this.MsgHdrFromOrToInUserTwitlist(msgHeader)) { - var newReadableMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, true); + var newReadableMsgOffset = this.FindNextReadableMsgIdx(pOffset, true); if (newReadableMsgOffset > -1) { retObj.newMsgOffset = newReadableMsgOffset; @@ -7183,7 +7225,18 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg } break; case this.enhReaderKeys.quit: // Quit - retObj.nextAction = ACTION_QUIT; + case KEY_ESC: + // Normally, if quitFromReaderGoesToMsgList is enabled, then do that, except + // in indexed mode, allow going back to the indexed mode menu. + if (this.userSettings.quitFromReaderGoesToMsgList && !this.indexedMode) + { + console.attributes = "N"; + console.crlf(); + console.print("Loading..."); + retObj.nextAction = ACTION_DISPLAY_MSG_LIST; + } + else + retObj.nextAction = ACTION_QUIT; continueOn = false; break; default: @@ -7276,7 +7329,7 @@ function DigDistMsgReader_LookForNextOrPriorNonDeletedMsg(pOffset) // Look for a later message that isn't marked for deletion. // If none is found, then look for a prior message that isn't // marked for deletion. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, true); + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, true); if (retObj.newMsgOffset > -1) { retObj.continueInputLoop = false; @@ -7285,7 +7338,7 @@ function DigDistMsgReader_LookForNextOrPriorNonDeletedMsg(pOffset) else { // No later message found, so look for a prior message. - retObj.newMsgOffset = this.FindNextNonDeletedMsgIdx(pOffset, false); + retObj.newMsgOffset = this.FindNextReadableMsgIdx(pOffset, false); if (retObj.newMsgOffset > -1) { retObj.continueInputLoop = false; @@ -7401,12 +7454,12 @@ function DigDistMsgReader_GoToPrevSubBoardForEnhReader(pAllowChgMsgArea, pPrompt else { // Look for the last message not marked as deleted - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(this.NumMessages(), false); + var readableMsgIdx = this.FindNextReadableMsgIdx(this.NumMessages(), false); // If a non-deleted message was found, then set retObj.msgIndex to it. // Otherwise, tell the user there are no messages in this sub-board // and return. - if (nonDeletedMsgIdx > -1) - retObj.msgIndex = nonDeletedMsgIdx; + if (readableMsgIdx > -1) + retObj.msgIndex = readableMsgIdx; else retObj.msgIndex = this.NumMessages() - 1; // Shouldn't get here var newLastRead = this.IdxToAbsMsgNum(retObj.msgIndex); @@ -7540,15 +7593,15 @@ function DigDistMsgReader_GoToNextSubBoardForEnhReader(pAllowChgMsgArea, pPrompt { // Set the index of the message to display - Look for the // first message not marked as deleted - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(this.NumMessages()-1, true); + var readableMsgIdx = this.FindNextReadableMsgIdx(this.NumMessages()-1, true); // If a non-deleted message was found, then set retObj.msgIndex to it. // Otherwise, tell the user there are no messages in this sub-board // and return. - if (nonDeletedMsgIdx > -1) + if (readableMsgIdx > -1) { - retObj.msgIndex = nonDeletedMsgIdx; + retObj.msgIndex = readableMsgIdx; retObj.changedMsgArea = true; - var newLastRead = this.IdxToAbsMsgNum(nonDeletedMsgIdx); + var newLastRead = this.IdxToAbsMsgNum(readableMsgIdx); if (newLastRead > -1) msg_area.sub[this.subBoardCode].last_read = newLastRead; } @@ -7636,9 +7689,9 @@ function DigDistMsgReader_SetUpTraditionalMsgListVars() lastReadMsgIdx = 0; } var pageNum = findPageNumOfItemNum(lastReadMsgIdx+1, this.tradMsgListNumLines, this.NumMessages(), - this.reverseListOrder); + this.userSettings.listMessagesInReverse); this.CalcTraditionalMsgListTopIdx(pageNum); - if (!this.reverseListOrder && (this.tradListTopMsgIdx > lastReadMsgIdx)) + if (!this.userSettings.listMessagesInReverse && (this.tradListTopMsgIdx > lastReadMsgIdx)) this.tradListTopMsgIdx -= this.tradMsgListNumLines; } @@ -7668,10 +7721,10 @@ function DigDistMsgReader_SetUpLightbarMsgListVars() } } var pageNum = findPageNumOfItemNum(lastReadMsgIdx+1, this.lightbarMsgListNumLines, this.NumMessages(), - this.reverseListOrder); + this.userSettings.listMessagesInReverse); this.CalcLightbarMsgListTopIdx(pageNum); var initialCursorRow = 0; - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) initialCursorRow = this.lightbarMsgListStartScreenRow+(this.lightbarListTopMsgIdx-lastReadMsgIdx); else { @@ -7679,7 +7732,14 @@ function DigDistMsgReader_SetUpLightbarMsgListVars() this.lightbarListTopMsgIdx -= this.lightbarMsgListNumLines; initialCursorRow = this.lightbarMsgListStartScreenRow+(lastReadMsgIdx-this.lightbarListTopMsgIdx); } - this.lightbarListSelectedMsgIdx = lastReadMsgIdx; + if (this.userSettings.listMessagesInReverse) + { + this.lightbarListSelectedMsgIdx = this.NumMessages() - lastReadMsgIdx - 1; + if (this.lightbarListSelectedMsgIdx < 0) + this.lightbarListSelectedMsgIdx = 0; + } + else + this.lightbarListSelectedMsgIdx = lastReadMsgIdx; this.lightbarListCurPos = { x: 1, y: initialCursorRow }; } @@ -7753,7 +7813,7 @@ function DigDistMsgReader_ListScreenfulOfMessages(pTopIndex, pMaxLines) var curpos = console.getxy(); var msgIndex = 0; - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) { var endIndex = pTopIndex - pMaxLines + 1; // The index of the last message to display for (msgIndex = pTopIndex; (msgIndex >= 0) && (msgIndex >= endIndex); --msgIndex) @@ -7928,6 +7988,7 @@ function DigDistMsgReader_DisplayTraditionalMsgListHelp(pDisplayHeader, pChgSubB console.print("\x01n" + this.colors.tradInterfaceHelpScreenColor + " 1,2,3\r\n"); console.print("\x01n" + this.colors.tradInterfaceHelpScreenColor + " 1 2 3\r\n"); console.print("\x01n" + this.colors.tradInterfaceHelpScreenColor + " 1,2,10-20\r\n"); + console.print("\x01n\x01h\x01cCTRL-U" + this.colors.tradInterfaceHelpScreenColor + ": Change your user settings\r\n"); console.print("\x01n\x01h\x01cCTRL-D" + this.colors.tradInterfaceHelpScreenColor + ": Batch delete selected messages\r\n"); console.print("\x01n\x01h\x01cQ" + this.colors.tradInterfaceHelpScreenColor + ": Quit\r\n"); if (this.indexedMode) @@ -8026,6 +8087,7 @@ function DigDistMsgReader_DisplayLightbarMsgListHelp(pDisplayHeader, pChgSubBoar console.print("\x01n\x01h\x01cSpacebar" + this.colors.tradInterfaceHelpScreenColor + ": Select message (for batch delete, etc.)\r\n"); console.print("\x01n\x01h\x01cCTRL-A" + this.colors.tradInterfaceHelpScreenColor + ": Select/de-select all messages\r\n"); console.print("\x01n\x01h\x01cCTRL-D" + this.colors.tradInterfaceHelpScreenColor + ": Batch delete selected messages\r\n"); + console.print("\x01n\x01h\x01cCTRL-U" + this.colors.tradInterfaceHelpScreenColor + ": Change your user settings\r\n"); console.print("\x01n\x01h\x01cQ" + this.colors.tradInterfaceHelpScreenColor + ": Quit\r\n"); if (this.indexedMode) console.print(" Currently in indexed mode; quitting will quit back to the index list.\r\n"); @@ -8046,8 +8108,10 @@ function DigDistMsgReader_DisplayMessageListNotesHelp() this.colors["tradInterfaceHelpScreenColor"], "\x01n\x01k\x01h") console.print(this.colors["tradInterfaceHelpScreenColor"]); console.print("If a message has been marked for deletion, it will appear with a blinking\r\n"); - console.print("red asterisk (\x01n\x01h\x01r\x01i*" + "\x01n" + this.colors["tradInterfaceHelpScreenColor"] + ") in"); - console.print(" after the message number in the message list."); + console.print("red asterisk (\x01n\x01h\x01r\x01i*" + "\x01n" + this.colors.tradInterfaceHelpScreenColor + ") in"); + console.print(" after the message number in the message list.\r\n"); + console.print("For a message written to you, a U between the message number and 'from' name\r\n"); + console.print("means the message is unread."); } // For the DigDistMsgReader Class: Sets the traditional UI pause prompt text // strings, sLightbarModeHelpLine, the text string for the lightbar help line, @@ -8407,14 +8471,6 @@ function DigDistMsgReader_ReadConfigFile() this.quickUserValSetIndex = numberVal; if (settingsObj.hasOwnProperty("listInterfaceStyle") && typeof(settingsObj.listInterfaceStyle) === "string") this.msgListUseLightbarListInterface = (settingsObj.listInterfaceStyle.toUpperCase() == "LIGHTBAR"); - if (settingsObj.hasOwnProperty("reverseListOrder")) - { - var valType = typeof(settingsObj.reverseListOrder); - if (valType === "boolean") - this.reverseListOrder = settingsObj.reverseListOrder; - else if (valType === "string") - this.reverseListOrder = (settingsObj.reverseListOrder.toUpperCase() == "ASK"); - } if (typeof(settingsObj["readerInterfaceStyle"]) === "string") this.scrollingReaderInterface = (settingsObj.readerInterfaceStyle.toUpperCase() == "SCROLLABLE"); if (typeof(settingsObj["readerInterfaceStyleForANSIMessages"]) === "string") @@ -8469,8 +8525,13 @@ function DigDistMsgReader_ReadConfigFile() if (!file_exists(themeFilename)) themeFilename = gStartupPath + settingsObj.themeFilename; } - if (settingsObj.hasOwnProperty("saveAllHdrsWhenSavingMsgToBBSPC")) - this.saveAllHdrsWhenSavingMsgToBBSPC = settingsObj["saveAllHdrsWhenSavingMsgToBBSPC"]; + if (typeof(settingsObj["saveAllHdrsWhenSavingMsgToBBSPC"]) === "boolean") + this.saveAllHdrsWhenSavingMsgToBBSPC = settingsObj.saveAllHdrsWhenSavingMsgToBBSPC; + // User setting defaults + if (typeof(settingsObj["reverseListOrder"] === "boolean")) + this.userSettings.listMessagesInReverse = settingsObj.reverseListOrder; + if (typeof(settingsObj["useIndexedModeForNewscan"]) === "boolean") + this.userSettings.useIndexedModeForNewscan = settingsObj.useIndexedModeForNewscan; } else { @@ -8590,10 +8651,20 @@ function DigDistMsgReader_ReadUserSettingsFile(pOnlyTwitlist) var userSettingsFile = new File(gUserSettingsFilename); if (userSettingsFile.open("r")) { - //var behavior = userSettingsFile.iniGetObject("BEHAVIOR"); this.userSettings.useEnhReaderScrollbar = userSettingsFile.iniGetValue("BEHAVIOR", "useEnhReaderScrollbar", true); this.userSettings.useIndexedModeForNewscan = userSettingsFile.iniGetValue("BEHAVIOR", "useIndexedModeForNewscan", false); + this.userSettings.listMessagesInReverse = userSettingsFile.iniGetValue("BEHAVIOR", "listMessagesInReverse", false); + this.userSettings.quitFromReaderGoesToMsgList = userSettingsFile.iniGetValue("BEHAVIOR", "quitFromReaderGoesToMsgList", false); + this.userSettings.enterFromIndexMenuShowsMsgList = userSettingsFile.iniGetValue("BEHAVIOR", "enterFromIndexMenuShowsMsgList", false); + //var behavior = userSettingsFile.iniGetObject("BEHAVIOR"); userSettingsFile.close(); + /* + for (var prop in behavior) + { + var propUpper = prop.toUpperCase(); + //if (propUpper == "USEENHREADERSCROLLBAR" && typeof(behavior[prop]) === "boolean") + } + */ } } } @@ -8610,6 +8681,9 @@ function DigDistMsgReader_WriteUserSettingsFile() { userSettingsFile.iniSetValue("BEHAVIOR", "useEnhReaderScrollbar", this.userSettings.useEnhReaderScrollbar); userSettingsFile.iniSetValue("BEHAVIOR", "useIndexedModeForNewscan", this.userSettings.useIndexedModeForNewscan); + userSettingsFile.iniSetValue("BEHAVIOR", "listMessagesInReverse", this.userSettings.listMessagesInReverse); + userSettingsFile.iniSetValue("BEHAVIOR", "quitFromReaderGoesToMsgList", this.userSettings.quitFromReaderGoesToMsgList); + userSettingsFile.iniSetValue("BEHAVIOR", "enterFromIndexMenuShowsMsgList", this.userSettings.enterFromIndexMenuShowsMsgList); userSettingsFile.close(); writeSucceeded = true; } @@ -8649,10 +8723,17 @@ function DigDistMsgReader_EditExistingMsg(pMsgIndex) // Only let the user edit the message if they're a sysop or // if they wrote the message. var msgHeader = this.GetMsgHdrByIdx(pMsgIndex, false, msgbase); + if (msgHeader == null) + { + console.print("\x01n\x01h\x01wInvalid message header\x01n"); + console.crlf(); + console.pause(); + return returnObj; + } if (!user.is_sysop && (msgHeader.from != user.name) && (msgHeader.from != user.alias) && (msgHeader.from != user.handle)) { console.print("\x01n\x01h\x01wCannot edit message #\x01y" + +(pMsgIndex+1) + - " \x01wbecause it's not yours or you're not a sysop."); + " \x01wbecause it's not yours or you're not a sysop.\x01n"); console.crlf(); console.pause(); returnObj.userCannotEdit = true; @@ -8702,8 +8783,7 @@ function DigDistMsgReader_EditExistingMessageOldWay(pMsgbase, pOrigMsgHdr, pMsgI //var originalMsgBody = pMsgbase.get_msg_body(true, pMsgIndex, false, false, true, true); var originalMsgBody; var tmpMsgHdr = this.GetMsgHdrByIdx(pMsgIndex, false, pMsgbase); - var msgHdrIsBogus = (tmpMsgHdr.hasOwnProperty("isBogus") ? tmpMsgHdr.isBogus : false); - if (msgHdrIsBogus) + if (tmpMsgHdr == null) originalMsgBody = pMsgbase.get_msg_body(true, pMsgIndex, false, false, true, true); else originalMsgBody = pMsgbase.get_msg_body(false, tmpMsgHdr.number, false, false, true, true); @@ -8993,7 +9073,7 @@ function DigDistMsgReader_NumMessages(pMsgbase, pCheckDeletedAttributes) var totalNumMsgs = msgbase.total_msgs; for (var msgIdx = 0; msgIdx < totalNumMsgs; ++msgIdx) { - if (isReadableMsgHdr(msgbase.get_msg_header(true, msgIdx, false), this.subBoardCode)) + if (isReadableMsgHdr(msgbase.get_msg_index(true, msgIdx, false), this.subBoardCode)) ++numMsgs; } } @@ -9006,7 +9086,7 @@ function DigDistMsgReader_NumMessages(pMsgbase, pCheckDeletedAttributes) for (var msgIdx = 0; msgIdx < originalNumMsgs; ++msgIdx) { msgHdr = this.GetMsgHdrByIdx(msgIdx); - if ((msgHdr.attr & MSG_DELETE) == MSG_DELETE) + if (msgHdr == null || (msgHdr.attr & MSG_DELETE) == MSG_DELETE) { --numMsgs; if (numMsgs < 0) @@ -9040,7 +9120,7 @@ function DigDistMsgReader_NonDeletedMessagesExist() for (var msgIdx = 0; (msgIdx < numMsgs) && !messagesExist; ++msgIdx) { msgHdr = this.GetMsgHdrByIdx(msgIdx); - if ((msgHdr.attr & MSG_DELETE) == 0) + if (msgHdr != null && (msgHdr.attr & MSG_DELETE) == 0) { messagesExist = true; break; @@ -9114,8 +9194,7 @@ function DigDistMsgReader_GetMsgHdrByIdx(pMsgIdx, pExpandFields, pMsgbase) var expandFields = (typeof(pExpandFields) == "boolean" ? pExpandFields : false); var msgHdr = null; - if (this.msgSearchHdrs.hasOwnProperty(this.subBoardCode) && - (this.msgSearchHdrs[this.subBoardCode].indexed.length > 0)) + if (this.msgSearchHdrs.hasOwnProperty(this.subBoardCode) && (this.msgSearchHdrs[this.subBoardCode].indexed.length > 0)) { if ((pMsgIdx >= 0) && (pMsgIdx < this.msgSearchHdrs[this.subBoardCode].indexed.length)) { @@ -9137,8 +9216,6 @@ function DigDistMsgReader_GetMsgHdrByIdx(pMsgIdx, pExpandFields, pMsgbase) } else msgHdr = getHdrFromMsgbase(pMsgbase, this.subBoardCode, true, pMsgIdx, pExpandFields); - if (msgHdr == null) - msgHdr = getBogusMsgHdr(); return msgHdr; } @@ -9178,8 +9255,6 @@ function DigDistMsgReader_GetMsgHdrByMsgNum(pMsgNum, pExpandFields) msgbase.close(); } } - if (msgHdr == null) - msgHdr = getBogusMsgHdr(); return msgHdr; } @@ -9203,8 +9278,6 @@ function DigDistMsgReader_GetMsgHdrByAbsoluteNum(pMsgNum, pExpandFields, pGetVot msgHdr = msgbase.get_msg_header(false, pMsgNum, expandFields, getVoteInfo); msgbase.close(); } - if (msgHdr == null) - msgHdr = getBogusMsgHdr(); return msgHdr; } function DumpMsgHdr(pSubCode, pMsgNum, pExpandFields, pGetVoteInfo) @@ -9233,14 +9306,7 @@ function DumpMsgHdr(pSubCode, pMsgNum, pExpandFields, pGetVoteInfo) // Return value: The message's index. On error, returns -1. function DigDistMsgReader_AbsMsgNumToIdx(pMsgNum) { - var msgIdx = -1; - var msgbase = new MsgBase(this.subBoardCode); - if (msgbase.open()) - { - msgIdx = absMsgNumToIdx(msgbase, pMsgNum); - msgbase.close(); - } - return msgIdx; + return absMsgNumToIdx(this.subBoardCode, pMsgNum); } // For the DigDistMsgReader class: Takes a message index and returns @@ -9262,66 +9328,6 @@ function DigDistMsgReader_IdxToAbsMsgNum(pMsgIdx) return msgIdx; } -// This function takes an absolute message number for a given messagebase objectand -// and returns the message index (offset). On error, returns -1. -// -// Parameters: -// pMsgbase: The messagebase object from which to retrieve the message header -// pMsgNum: The absolute message number -// -// Return value: The message's index, or -1 on error. -function absMsgNumToIdx(pMsgbase, pMsgNum) -{ - if ((pMsgbase == null) || (typeof(pMsgbase) != "object")) - return -1; - if (!pMsgbase.is_open) - return -1; - - if (typeof(pMsgNum) != "number") - return -1; - - var messageIdx = 0; - // If pMsgNum is 0xffffffff (0xffffffff, or ~0), that is a special value - // for the user's scan_ptr meaning it should point to the latest message - // in the messagebase. - if (pMsgNum == 0xffffffff) - messageIdx = pMsgbase.total_msgs - 1; // Or this.NumMessages() - 1 but can't because this isn't a class member function - else - { - var msgHdr = pMsgbase.get_msg_header(false, pMsgNum, false); - if ((msgHdr == null) && gCmdLineArgVals.verboselogging) - { - writeToSysAndNodeLog("Message area " + pMsgbase.cfg.code + ": Tried to get message header for absolute message number " + - pMsgNum + " but got a null header object."); - } - messageIdx = (msgHdr != null ? msgHdr.offset : -1); - } - return messageIdx; -} - -// Takes a message index and returns the message's absolute message number. -// -// Parameters: -// pMsgbase: The messagebase object from which to retrieve the message header -// pMsgIdx: The message index -// -// Return value: The absolue message number, or -1 on error. -function idxToAbsMsgNum(pMsgbase, pMsgIdx) -{ - if ((pMsgbase == null) || (typeof(pMsgbase) != "object")) - return -1; - if (!pMsgbase.is_open) - return -1; - - var msgHdr = pMsgbase.get_msg_header(true, pMsgIdx, false); - if ((msgHdr == null) && gCmdLineArgVals.verboselogging) - { - writeToSysAndNodeLog("Tried to get message header for message offset " + - pMsgIdx + " but got a null header object."); - } - return (msgHdr != null ? msgHdr.number : -1); -} - // Prompts the user for a message number and keeps repeating if they enter and // invalid message number. It is assumed that the cursor has already been // placed where it needs to be for the prompt text, but a cursor position is @@ -9797,16 +9803,19 @@ function findDashJustifyIndex(pAtCodeStr) } // Finds the offset (index) of the next message prior to or after a given offset -// that is not marked as deleted. If none is found, the return value will be -1. +// that is readable (not marked as deleted or the user can read deleted messages, +// etc.). If none is found, the return value will be -1. // // Parameters: // pOffset: The message offset to search prior/after // pForward: Boolean - Whether or not to search forward (true) or backward (false). // If this is not specified, the default will be true (forward). +// pUseCachedHdrs: Optional boolean - Whether or not to use the cached message headers +// (which are filtered with deleted messages removed, etc). Defaults to true. // // Return value: The index of the next message prior/later that is not marked // as deleted, or -1 if none is found. -function DigDistMsgReader_FindNextNonDeletedMsgIdx(pOffset, pForward) +function DigDistMsgReader_FindNextReadableMsgIdx(pOffset, pForward, pUseCachedHdrs) { // Sanity checking for the parameters & other things if (typeof(pOffset) != "number") @@ -9816,35 +9825,62 @@ function DigDistMsgReader_FindNextNonDeletedMsgIdx(pOffset, pForward) if (!msgbase.open()) return -1; + var useCachedHdrs = (typeof(pUseCachedHdrs) === "boolean" ? pUseCachedHdrs : true); var newMsgIdx = -1; if (searchForward) { - // Search forward for a message that isn't marked for deletion. - var numOfMessages = this.NumMessages(msgbase); - if (pOffset < numOfMessages - 1) + // Search forward for a message that isn't marked for deletion (and check if the user + // can read deleted messages) + if (useCachedHdrs) { - var hdrIsBogus; - for (var messageIdx = pOffset+1; (messageIdx < numOfMessages) && (newMsgIdx == -1); ++messageIdx) + var numOfMessages = this.NumMessages(msgbase); + if (pOffset < numOfMessages - 1) { - var nextMsgHdr = this.GetMsgHdrByIdx(messageIdx, false, msgbase); - hdrIsBogus = (nextMsgHdr.hasOwnProperty("isBogus") ? nextMsgHdr.isBogus : false); - if ((nextMsgHdr != null) && !hdrIsBogus && ((nextMsgHdr.attr & MSG_DELETE) == 0)) - newMsgIdx = messageIdx; + var hdrIsBogus; + for (var messageIdx = pOffset+1; (messageIdx < numOfMessages) && (newMsgIdx == -1); ++messageIdx) + { + var nextMsgHdr = this.GetMsgHdrByIdx(messageIdx, false, msgbase); + if (nextMsgHdr != null && !isVoteHdr(nextMsgHdr)) + { + var canRead = true; + if ((nextMsgHdr.attr & MSG_DELETE) == MSG_DELETE) + canRead = canViewDeletedMsgs(); + if (canRead) + newMsgIdx = messageIdx; + } + } } } + else + { + // TODO + } } else { - // Search backward for a message that isn't marked for deletion. + // Search backward for a message that isn't marked for deletion (and check if the user + // can read deleted messages) if (pOffset > 0) { - var hdrIsBogus; - for (var messageIdx = pOffset-1; (messageIdx >= 0) && (newMsgIdx == -1); --messageIdx) + if (useCachedHdrs) { - var prevMsgHdr = this.GetMsgHdrByIdx(messageIdx, false, msgbase); - hdrIsBogus = (prevMsgHdr.hasOwnProperty("isBogus") ? prevMsgHdr.isBogus : false); - if ((prevMsgHdr != null) && !hdrIsBogus && ((prevMsgHdr.attr & MSG_DELETE) == 0)) - newMsgIdx = messageIdx; + var hdrIsBogus; + for (var messageIdx = pOffset-1; (messageIdx >= 0) && (newMsgIdx == -1); --messageIdx) + { + var prevMsgHdr = this.GetMsgHdrByIdx(messageIdx, false, msgbase); + if (prevMsgHdr != null && !isVoteHdr(prevMsgHdr)) + { + var canRead = true; + if ((prevMsgHdr.attr & MSG_DELETE) == MSG_DELETE) + canRead = canViewDeletedMsgs(); + if (canRead) + newMsgIdx = messageIdx; + } + } + } + else + { + // TODO } } } @@ -10401,7 +10437,7 @@ function DigDistMsgReader_DisplayEnhancedReaderHelp(pDisplayChgAreaOpt, pDisplay "\x01h\x01cPageUp\x01g/\x01cPageDown \x01g: \x01n\x01cScroll up\x01g/\x01cdown a page in the message", "\x01h\x01cHOME \x01g: \x01n\x01cGo to the top of the message", "\x01h\x01cEND \x01g: \x01n\x01cGo to the bottom of the message"]; - keyHelpLines.push("\x01h\x01cCtrl-U \x01g: \x01n\x01cUser settings (including personal twit list)"); + keyHelpLines.push("\x01h\x01cCtrl-U \x01g: \x01n\x01cChange your user settings"); if (user.is_sysop) { keyHelpLines.push("\x01h\x01cDEL \x01g: \x01n\x01cDelete the current message"); @@ -10510,7 +10546,7 @@ function DigDistMsgReader_DisplayEnhancedMsgHdr(pMsgHdr, pDisplayMsgNum, pStartS // For the set of enhanced header lines, choose the regular set or the set with // the highlighted 'to' user, dependin on whether the message was written to the // logged-in (reading) user. - var enhMsgHdrLines = (userNameHandleAliasMatch(pMsgHdr.to) ? this.enhMsgHeaderLinesToReadingUser : this.enhMsgHeaderLines); + var enhMsgHdrLines = (userHandleAliasNameMatch(pMsgHdr.to) ? this.enhMsgHeaderLinesToReadingUser : this.enhMsgHeaderLines); if (enhMsgHdrLines == null) return; if ((enhMsgHdrLines.length == 0) || (this.enhMsgHeaderWidth == 0)) @@ -10665,38 +10701,38 @@ function DigDistMsgReader_DisplayAreaChgHdr(pStartScreenRow, pClearRowsFirst) // pNumSolidBlocks: The number of solid/bright blocks to write function DigDistMsgReader_DisplayEnhancedReaderWholeScrollbar(pSolidBlockStartRow, pNumSolidBlocks) { - //console.attributes = "N"; - var numSolidBlocksWritten = 0; - var wroteBrightBlockColor = false; - var wroteDimBlockColor = false; - for (var screenY = this.msgAreaTop; screenY <= this.msgAreaBottom; ++screenY) - { - console.gotoxy(this.msgAreaRight+1, screenY); - if ((screenY >= pSolidBlockStartRow) && (numSolidBlocksWritten < pNumSolidBlocks)) - { - if (!wroteBrightBlockColor) - { - //console.print("\x01h\x01w"); - console.print("\x01n" + this.colors.scrollbarScrollBlockColor); - wroteBrightBlockColor = true; - wroteDimBlockColor = false; - } - //console.print(BLOCK2); - console.print(this.text.scrollbarScrollBlockChar); - ++numSolidBlocksWritten; - } - else - { - if (!wroteDimBlockColor) - { - //console.print("\x01h\x01k"); - console.print("\x01n" + this.colors.scrollbarBGColor); - wroteDimBlockColor = true; - } - //console.print(BLOCK1); - console.print(this.text.scrollbarBGChar); - } - } + //console.attributes = "N"; + var numSolidBlocksWritten = 0; + var wroteBrightBlockColor = false; + var wroteDimBlockColor = false; + for (var screenY = this.msgAreaTop; screenY <= this.msgAreaBottom; ++screenY) + { + console.gotoxy(this.msgAreaRight+1, screenY); + if ((screenY >= pSolidBlockStartRow) && (numSolidBlocksWritten < pNumSolidBlocks)) + { + if (!wroteBrightBlockColor) + { + //console.print("\x01h\x01w"); + console.print("\x01n" + this.colors.scrollbarScrollBlockColor); + wroteBrightBlockColor = true; + wroteDimBlockColor = false; + } + console.print(BLOCK2); + //console.print(this.text.scrollbarScrollBlockChar); // TODO: This doesn't seem to be working + ++numSolidBlocksWritten; + } + else + { + if (!wroteDimBlockColor) + { + //console.print("\x01h\x01k"); + console.print("\x01n" + this.colors.scrollbarBGColor); + wroteDimBlockColor = true; + } + console.print(BLOCK1); + //console.print(this.text.scrollbarBGChar); // TODO: This doesn't seem to be working + } + } } // For the DigDistMsgReader class: Updates the scrollbar for a message, for use @@ -10707,7 +10743,7 @@ function DigDistMsgReader_DisplayEnhancedReaderWholeScrollbar(pSolidBlockStartRo // pNewStartRow: The new (current) start row for solid/bright blocks // pOldStartRow: The old start row for solid/bright blocks // pNumSolidBlocks: The number of solid/bright blocks -function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRow, pNumSolidBlocks) +function DigDistMsgReader_UpdateEnhancedReaderScrollbar(pNewStartRow, pOldStartRow, pNumSolidBlocks) { // Calculate the difference in the start row. If the difference is positive, // then the solid block section has moved down; if the diff is negative, the @@ -10727,8 +10763,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK1); - console.print(this.text.scrollbarBGChar); + console.print(BLOCK1); + //console.print(this.text.scrollbarBGChar); // TODO: This doesn't seem to be working } // Write solid blocks in the new locations //console.print("\x01w"); @@ -10736,8 +10772,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK2); - console.print(this.text.scrollbarScrollBlockChar); + console.print(BLOCK2); + //console.print(this.text.scrollbarScrollBlockChar); // TODO: This doesn't seem to be working } } else @@ -10749,8 +10785,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pOldStartRow; screenY < pNewStartRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK1); - console.print(this.text.scrollbarBGChar); + console.print(BLOCK1); + //console.print(this.text.scrollbarBGChar); // TODO: This doesn't seem to be working } // Write bright blocks on the bottom //console.print("\x01w"); @@ -10758,8 +10794,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = oldLastRow+1; screenY <= newLastRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK2); - console.print(this.text.scrollbarScrollBlockChar); + console.print(BLOCK2); + //console.print(this.text.scrollbarScrollBlockChar); // TODO: This doesn't seem to be working } } } @@ -10775,8 +10811,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK1); - console.print(this.text.scrollbarBGChar); + console.print(BLOCK1); + //console.print(this.text.scrollbarBGChar); // TODO: This doesn't seem to be working } // Write solid blocks in the new locations //console.print("\x01w"); @@ -10784,8 +10820,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK2); - console.print(this.text.scrollbarScrollBlockChar); + console.print(BLOCK2); + //console.print(this.text.scrollbarScrollBlockChar); // TODO: This doesn't seem to be working } } else @@ -10798,8 +10834,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pNewStartRow; screenY < endRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK2); - console.print(this.text.scrollbarScrollBlockChar); + console.print(BLOCK2); + //console.print(this.text.scrollbarScrollBlockChar); // TODO: This doesn't seem to be working } // Write dim blocks on the bottom //console.print("\x01k"); @@ -10808,8 +10844,8 @@ function DigDistMsgReader_UpdateEnhancedReaderScollbar(pNewStartRow, pOldStartRo for (var screenY = pNewStartRow+pNumSolidBlocks; screenY < endRow; ++screenY) { console.gotoxy(this.msgAreaRight+1, screenY); - //console.print(BLOCK1); - console.print(this.text.scrollbarBGChar); + console.print(BLOCK1); + //console.print(this.text.scrollbarBGChar); // TODO: This doesn't seem to be working } } } @@ -10867,14 +10903,14 @@ function DigDistMsgReader_MessageIsLastFromUser(pOffset) // is, then look for the last message posted by the logged-in user and // if found, see if that message has the same offset as the offset // passed in. - var msgHdr = msgbase.get_msg_header(true, pOffset, false); - if (userHandleAliasNameMatch(msgHdr["to"])) + var msgHdr = msgbase.get_msg_index(true, pOffset, false); + if (userHandleAliasNameMatch(msgHdr.to)) { var lastMsgOffsetFromUser = -1; for (var msgOffset = msgbase.total_msgs-1; (msgOffset >= pOffset) && (lastMsgOffsetFromUser == -1); --msgOffset) { - msgHdr = msgbase.get_msg_header(true, msgOffset, false); - if (userHandleAliasNameMatch(msgHdr["to"])) + msgHdr = msgbase.get_msg_index(true, msgOffset, false); + if (userHandleAliasNameMatch(msgHdr.to)) lastMsgOffsetFromUser = msgOffset; } // See if the passed-in offset is the last message we found from @@ -11110,9 +11146,14 @@ function DigDistMsgReader_PromptAndDeleteOrUndeleteMessage(pOffset, pPromptLoc, var msgNum = pOffset + 1; var msgHeader = this.GetMsgHdrByIdx(pOffset, false, msgbase); - - var errorMessage = ""; - var continueOn = false; + if (msgHeader == null) + { + msgbase.close(); + return false; + } + + var errorMessage = ""; + var continueOn = false; if (deleteMsg) { // If it's already marked for deletion, then nothing needs to be done @@ -12032,19 +12073,24 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn if (subBoardInfo.numPosts > 0) { var msgIdx = msgBase.total_msgs-1; - msgHeader = msgBase.get_msg_header(true, msgIdx, false); + msgHeader = msgBase.get_msg_index(true, msgIdx, false); while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code) && (msgIdx >= 0)) - msgHeader = msgBase.get_msg_header(true, --msgIdx, true); - if (this.msgAreaList_lastImportedMsg_showImportTime) - subBoardInfo.newestPostDate = msgHeader.when_imported_time; - else + msgHeader = msgBase.get_msg_index(true, --msgIdx, true); + if (msgHeader != null) + msgHeader = msgBase.get_msg_header(true, msgIdx, false); + if (msgHeader != null) { - //subBoardInfo.newestPostDate = msgHeader.when_written_time; - var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); - if (msgWrittenLocalTime != -1) - subBoardInfo.newestPostDate = msgWrittenTimeToLocalBBSTime(msgHeader); + if (this.msgAreaList_lastImportedMsg_showImportTime) + subBoardInfo.newestPostDate = msgHeader.when_imported_time; else - subBoardInfo.newestPostDate = msgHeader.when_written_time; + { + //subBoardInfo.newestPostDate = msgHeader.when_written_time; + var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); + if (msgWrittenLocalTime != -1) + subBoardInfo.newestPostDate = msgWrittenTimeToLocalBBSTime(msgHeader); + else + subBoardInfo.newestPostDate = msgHeader.when_written_time; + } } } } @@ -12136,29 +12182,34 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn if (numMsgs > 0) { var msgIdx = msgBase.total_msgs-1; - msgHeader = msgBase.get_msg_header(true, msgIdx, false); + msgHeader = msgBase.get_msg_index(true, msgIdx, false); while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code) && (msgIdx >= 0)) - msgHeader = msgBase.get_msg_header(true, --msgIdx, true); - // Construct the date & time strings of the latest post - if (this.msgAreaList_lastImportedMsg_showImportTime) - { - newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time); - newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time); - } - else + msgHeader = msgBase.get_msg_index(true, --msgIdx, true); + if (msgHeader != null) + msgHeader = msgBase.get_msg_header(true, msgIdx, false); + if (msgHeader != null) { - //newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); - //newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); - var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); - if (msgWrittenLocalTime != -1) + // Construct the date & time strings of the latest post + if (this.msgAreaList_lastImportedMsg_showImportTime) { - newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalTime); - newestDate.time = strftime("%H:%M:%S", msgWrittenLocalTime); + newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time); + newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time); } else { - newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); - newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + //newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); + //newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); + if (msgWrittenLocalTime != -1) + { + newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalTime); + newestDate.time = strftime("%H:%M:%S", msgWrittenLocalTime); + } + else + { + newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); + newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + } } } } @@ -12272,43 +12323,52 @@ function DigDistMsgReader_GetMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight) var msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code); if (msgBase.open()) { - var newestDate = {}; // For storing the date of the newest post + // For storing the date of the newest post + var newestDate = { + date: "", + time: "" + }; // Get the date & time when the last message was imported. - //numReadableMsgs(pMsgbase, pSubBoardCode) //var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code); var numMsgs = msgBase.total_msgs; if (numMsgs > 0) { - // Get the header of the last message in the sub-board + // Get the header of the last readable message in the sub-board. This uses + // get_msg_index to get message indexes to quickly check to see if it's + // readable, then when a readable one is found, this uses get_msg_header + // to get the full header with the properties needed. var msgHeader = null; var msgOffset = msgBase.total_msgs - 1; - while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code) && (msgOffset >= 0)) - msgHeader = msgBase.get_msg_header(true, --msgOffset, true); - // Construct the date & time strings of the latest post - if (this.msgAreaList_lastImportedMsg_showImportTime) + while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code) && msgOffset >= 0) + msgHeader = msgBase.get_msg_index(true, msgOffset--, false); + if (msgHeader != null) + msgHeader = msgBase.get_msg_header(true, msgOffset, false, false); + if (msgHeader != null) { - newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time); - newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time); - } - else - { - //newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); - //newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); - var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); - if (msgWrittenLocalTime != -1) + // Construct the date & time strings of the latest post + if (this.msgAreaList_lastImportedMsg_showImportTime) { - newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalTime); - newestDate.time = strftime("%H:%M:%S", msgWrittenLocalTime); + newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time); + newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time); } else { - newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); - newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + //newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); + //newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader); + if (msgWrittenLocalTime != -1) + { + newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalTime); + newestDate.time = strftime("%H:%M:%S", msgWrittenLocalTime); + } + else + { + newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time); + newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time); + } } } } - else - newestDate.date = newestDate.time = ""; // Print the sub-board information line. subBoardStr += (currentSub ? this.colors.areaChooserMsgAreaMarkColor + "*" : " "); @@ -13235,7 +13295,7 @@ function DigDistMsgReader_GetLastReadMsgIdxAndNum(pMailStartFromFirst) // message index to the last index in this.hdrsForCurrentSubBoard.length. if (retObj.lastReadMsgIdx == -1) { - var msgIdxAccordingToMsgbase = absMsgNumToIdx(msgbase, msg_area.sub[this.subBoardCode].last_read); + var msgIdxAccordingToMsgbase = absMsgNumToIdxWithMsgbaseObj(msgbase, msg_area.sub[this.subBoardCode].last_read); if ((this.hdrsForCurrentSubBoard.length > 0) && (msgIdxAccordingToMsgbase >= this.hdrsForCurrentSubBoard.length)) { retObj.lastReadMsgIdx = this.hdrsForCurrentSubBoard.length - 1; @@ -13249,12 +13309,12 @@ function DigDistMsgReader_GetLastReadMsgIdxAndNum(pMailStartFromFirst) if ((retObj.lastReadMsgIdx < 0) || (retObj.lastReadMsgIdx >= msgbase.total_msgs)) { // Look for the first message not marked as deleted - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(0, true); + var readableMsgIdx = this.FindNextReadableMsgIdx(0, true); // If a non-deleted message was found, then set the last read // pointer to it. - if (nonDeletedMsgIdx > -1) + if (readableMsgIdx > -1) { - var newLastRead = this.IdxToAbsMsgNum(nonDeletedMsgIdx); + var newLastRead = this.IdxToAbsMsgNum(readableMsgIdx); if (newLastRead > -1) msg_area.sub[this.subBoardCode].last_read = newLastRead; else @@ -13284,24 +13344,23 @@ function DigDistMsgReader_GetScanPtrMsgIdx() // the user hasn't read messages in the sub-board yet. In that case, // just use 0. Otherwise, get the user's scan pointer message index. var msgIdx = 0; - // Check for 4294967295 (0xffffffff, or ~0): That is a special value - // for the user's scan_ptr meaning it should point to the latest message - // in the messagebase. - if (msg_area.sub[this.subBoardCode].scan_ptr != 0xffffffff) + // If the user's scan_ptr for the sub-board isn't the 'last message' + // special value, then use it + if (!subBoardScanPtrIsLatestMsgSpecialVal(this.subBoardCode)) msgIdx = this.GetMsgIdx(msg_area.sub[this.subBoardCode].scan_ptr); // Sanity checking for msgIdx var msgbase = new MsgBase(this.subBoardCode); if (msgbase.open()) { - if ((msgIdx < 0) || (msgIdx >= msgbase.total_msgs) || (msg_area.sub[this.subBoardCode].scan_ptr == 0xffffffff)) + if ((msgIdx < 0) || (msgIdx >= msgbase.total_msgs) || subBoardScanPtrIsLatestMsgSpecialVal(this.subBoardCode)) { msgIdx = -1; // Look for the first message not marked as deleted - var nonDeletedMsgIdx = this.FindNextNonDeletedMsgIdx(0, true); + var readableMsgIdx = this.FindNextReadableMsgIdx(0, true); // If a non-deleted message was found, then set the scan pointer to it. - if (nonDeletedMsgIdx > -1) + if (readableMsgIdx > -1) { - var newLastRead = this.IdxToAbsMsgNum(nonDeletedMsgIdx); + var newLastRead = this.IdxToAbsMsgNum(readableMsgIdx); if (newLastRead > -1) msg_area.sub[this.subBoardCode].scan_ptr = newLastRead; else @@ -13403,7 +13462,7 @@ function DigDistMsgReader_FindThreadNextOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = pMsgHdr.offset+1; (messageIdx < numOfMessages) && (nextMsgOffset == -1); ++messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (((nextMsgHdr.attr & MSG_DELETE) == 0) && (typeof(nextMsgHdr.thread_id) == "number") && (nextMsgHdr.thread_id == pMsgHdr.thread_id)) + if (nextMsgHdr != null && ((nextMsgHdr.attr & MSG_DELETE) == 0) && (typeof(nextMsgHdr.thread_id) == "number") && (nextMsgHdr.thread_id == pMsgHdr.thread_id)) nextMsgOffset = nextMsgHdr.offset; } } @@ -13414,7 +13473,7 @@ function DigDistMsgReader_FindThreadNextOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = this.GetMsgIdx(pMsgHdr.number)+1; (messageIdx < numOfMessages) && (nextMsgOffset == -1); ++messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if ((((nextMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && (typeof(nextMsgHdr.thread_id) == "number") && (nextMsgHdr.thread_id == pMsgHdr.thread_id)) + if (nextMsgHdr != null && (((nextMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && (typeof(nextMsgHdr.thread_id) == "number") && (nextMsgHdr.thread_id == pMsgHdr.thread_id)) { //nextMsgOffset = nextMsgHdr.offset; nextMsgOffset = this.GetMsgIdx(nextMsgHdr.number); @@ -13499,7 +13558,7 @@ function DigDistMsgReader_FindThreadNextOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = pMsgHdr.offset+1; (messageIdx < numOfMessages) && (nextMsgOffset == -1); ++messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (msgHdrMatch(nextMsgHdr)) + if (nextMsgHdr != null && msgHdrMatch(nextMsgHdr)) nextMsgOffset = nextMsgHdr.offset; } } @@ -13510,7 +13569,7 @@ function DigDistMsgReader_FindThreadNextOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = this.GetMsgIdx(pMsgHdr.number)+1; (messageIdx < numOfMessages) && (nextMsgOffset == -1); ++messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (msgHdrMatch(nextMsgHdr)) + if (nextMsgHdr != null && msgHdrMatch(nextMsgHdr)) { //nextMsgOffset = nextMsgHdr.offset; nextMsgOffset = this.GetMsgIdx(nextMsgHdr.number); @@ -13593,7 +13652,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = pMsgHdr.offset-1; (messageIdx >= 0) && (nextMsgOffset == -1); --messageIdx) { prevMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (((prevMsgHdr.attr & MSG_DELETE) == 0) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) + if (prevMsgHdr != null && ((prevMsgHdr.attr & MSG_DELETE) == 0) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) nextMsgOffset = prevMsgHdr.offset; } } @@ -13604,7 +13663,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = this.GetMsgIdx(pMsgHdr.number)-1; (messageIdx >= 0) && (nextMsgOffset == -1); --messageIdx) { prevMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if ((((prevMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) + if (prevMsgHdr != null && (((prevMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) { //nextMsgOffset = prevMsgHdr.offset; nextMsgOffset = this.GetMsgIdx(prevMsgHdr.number); @@ -13666,7 +13725,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = pMsgHdr.offset-1; (messageIdx >= 0) && (nextMsgOffset == -1); --messageIdx) { var prevMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (((prevMsgHdr.attr & MSG_DELETE) == 0) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) + if (prevMsgHdr != null && ((prevMsgHdr.attr & MSG_DELETE) == 0) && (typeof(prevMsgHdr.thread_id) == "number") && (prevMsgHdr.thread_id == pMsgHdr.thread_id)) nextMsgOffset = prevMsgHdr.offset; } } @@ -13742,7 +13801,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = pMsgHdr.offset-1; (messageIdx >= 0) && (nextMsgOffset == -1); --messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (msgHdrMatch(nextMsgHdr)) + if (prevMsgHdr != null && msgHdrMatch(nextMsgHdr)) nextMsgOffset = nextMsgHdr.offset; } } @@ -13753,7 +13812,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu for (var messageIdx = this.GetMsgIdx(pMsgHdr.number)-1; (messageIdx >= 0) && (nextMsgOffset == -1); --messageIdx) { nextMsgHdr = this.GetMsgHdrByIdx(messageIdx); - if (msgHdrMatch(nextMsgHdr)) + if (nextMsgHdr != null && msgHdrMatch(nextMsgHdr)) { //nextMsgOffset = nextMsgHdr.offset; nextMsgOffset = this.GetMsgIdx(nextMsgHdr.number); @@ -13791,7 +13850,7 @@ function DigDistMsgReader_FindThreadPrevOffset(pMsgHdr, pThreadType, pPositionCu // pPageNum: A page number (1-based) function DigDistMsgReader_CalcTraditionalMsgListTopIdx(pPageNum) { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) this.tradListTopMsgIdx = this.NumMessages() - (this.tradMsgListNumLines * (pPageNum-1)) - 1; else this.tradListTopMsgIdx = (this.tradMsgListNumLines * (pPageNum-1)); @@ -13804,7 +13863,7 @@ function DigDistMsgReader_CalcTraditionalMsgListTopIdx(pPageNum) // pPageNum: A page number (1-based) function DigDistMsgReader_CalcLightbarMsgListTopIdx(pPageNum) { - if (this.reverseListOrder) + if (this.userSettings.listMessagesInReverse) this.lightbarListTopMsgIdx = this.NumMessages() - (this.lightbarMsgListNumLines * (pPageNum-1)) - 1; else { @@ -13829,7 +13888,7 @@ function DigDistMsgReader_CalcMsgListScreenIdxVarsFromMsgNum(pMsgNum) var numItemsPerPage = this.tradMsgListNumLines; if (this.msgListUseLightbarListInterface && canDoHighASCIIAndANSI()) numItemsPerPage = this.lightbarMsgListNumLines; - var newPageNum = findPageNumOfItemNum(pMsgNum, numItemsPerPage, this.NumMessages(), this.reverseListOrder); + var newPageNum = findPageNumOfItemNum(pMsgNum, numItemsPerPage, this.NumMessages(), this.userSettings.listMessagesInReverse); this.CalcTraditionalMsgListTopIdx(newPageNum); this.CalcLightbarMsgListTopIdx(newPageNum); this.lightbarListSelectedMsgIdx = pMsgNum - 1; @@ -14045,18 +14104,34 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn) // Add the options to the option box const checkIdx = 48; - const ENH_SCROLLBAR_OPT_INDEX = optionBox.addTextItem("Scrollbar in reader [ ]"); + const optionFormatStr = "%-" + (checkIdx-1) + "s[ ]"; + const ENH_SCROLLBAR_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Scrollbar in reader")); if (this.userSettings.useEnhReaderScrollbar) optionBox.chgCharInTextItem(ENH_SCROLLBAR_OPT_INDEX, checkIdx, CHECK_CHAR); - const INDEXED_MODE_NEWSCAN_OPT_INDEX = optionBox.addTextItem("Use indexed mode for newscan [ ]"); + const LIST_MESSAGES_IN_REVERSE_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "List messages in reverse")); + if (this.userSettings.listMessagesInReverse) + optionBox.chgCharInTextItem(LIST_MESSAGES_IN_REVERSE_OPT_INDEX, checkIdx, CHECK_CHAR); + + const INDEXED_MODE_NEWSCAN_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Use indexed mode for newscan")); if (this.userSettings.useIndexedModeForNewscan) optionBox.chgCharInTextItem(INDEXED_MODE_NEWSCAN_OPT_INDEX, checkIdx, CHECK_CHAR); + const INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Index menu: Enter shows message list")); + if (this.userSettings.enterFromIndexMenuShowsMsgList) + optionBox.chgCharInTextItem(INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_INDEX, checkIdx, CHECK_CHAR); + + const READER_QUIT_TO_MSG_LIST_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Quit from reader to message list")); + if (this.userSettings.quitFromReaderGoesToMsgList) + optionBox.chgCharInTextItem(READER_QUIT_TO_MSG_LIST_OPT_INDEX, checkIdx, CHECK_CHAR); + // Create an object containing toggle values (true/false) for each option index var optionToggles = {}; optionToggles[ENH_SCROLLBAR_OPT_INDEX] = this.userSettings.useEnhReaderScrollbar; + optionToggles[LIST_MESSAGES_IN_REVERSE_OPT_INDEX] = this.userSettings.listMessagesInReverse; optionToggles[INDEXED_MODE_NEWSCAN_OPT_INDEX] = this.userSettings.useIndexedModeForNewscan; + optionToggles[INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_INDEX] = this.userSettings.enterFromIndexMenuShowsMsgList; + optionToggles[READER_QUIT_TO_MSG_LIST_OPT_INDEX] = this.userSettings.quitFromReaderGoesToMsgList; // Other actions var USER_TWITLIST_OPT_INDEX = optionBox.addTextItem("Personal twit list"); @@ -14085,9 +14160,18 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn) case ENH_SCROLLBAR_OPT_INDEX: this.readerObj.userSettings.useEnhReaderScrollbar = !this.readerObj.userSettings.useEnhReaderScrollbar; break; + case LIST_MESSAGES_IN_REVERSE_OPT_INDEX: + this.readerObj.userSettings.listMessagesInReverse = !this.readerObj.userSettings.listMessagesInReverse; + break; case INDEXED_MODE_NEWSCAN_OPT_INDEX: this.readerObj.userSettings.useIndexedModeForNewscan = !this.readerObj.userSettings.useIndexedModeForNewscan; break; + case INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_INDEX: + this.readerObj.userSettings.enterFromIndexMenuShowsMsgList = !this.readerObj.userSettings.enterFromIndexMenuShowsMsgList; + break; + case READER_QUIT_TO_MSG_LIST_OPT_INDEX: + this.readerObj.userSettings.quitFromReaderGoesToMsgList = !this.readerObj.userSettings.quitFromReaderGoesToMsgList; + break; default: break; } @@ -14172,13 +14256,22 @@ function DigDistMsgReader_DoUserSettings_Traditional() userTwitListChanged: false }; - console.crlf(); - console.print("\x01n\x01c\x01hU\x01n\x01cser \x01hS\x01n\x01cettings\x01n\r\n"); - console.print("\x01c\x01h1\x01g: \x01n\x01c\x01hU\x01n\x01cse \x01hI\x01n\x01cndexed \x01hm\x01n\x01code \x01hf\x01n\x01cor \x01hn\x01n\x01cewscan\x01n\r\n"); - console.print("\x01c\x01h2\x01g: \x01n\x01c\x01hP\x01n\x01cersonal \x01ht\x01n\x01cwit list\x01n\r\n"); - var USER_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM = 1; - var USER_TWITLIST_OPT_NUM = 2; + var LIST_MESSAGES_IN_REVERSE_OPT_NUM = 1; + var USE_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM = 2; + var INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_NUM = 3; + var READER_QUIT_TO_MSG_LIST_OPT_NUM = 4; + var USER_TWITLIST_OPT_NUM = 5; var HIGHEST_CHOICE_NUM = USER_TWITLIST_OPT_NUM; + + console.crlf(); + var wordFirstCharAttrs = "\x01c\x01h"; + var wordRemainingAttrs = "\x01c"; + console.print(colorFirstCharAndRemainingCharsInWords("User Settings", wordFirstCharAttrs, wordRemainingAttrs) + "\r\n"); + printTradUserSettingOption(LIST_MESSAGES_IN_REVERSE_OPT_NUM, "List messages in reverse", wordFirstCharAttrs, wordRemainingAttrs); + printTradUserSettingOption(USE_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM, "Use Indexed mode for newscan", wordFirstCharAttrs, wordRemainingAttrs); + printTradUserSettingOption(INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_NUM, "Index: Selection shows message list", wordFirstCharAttrs, wordRemainingAttrs); + printTradUserSettingOption(READER_QUIT_TO_MSG_LIST_OPT_NUM, "Quitting From reader goes to message list", wordFirstCharAttrs, wordRemainingAttrs); + printTradUserSettingOption(USER_TWITLIST_OPT_NUM, "Personal twit list", wordFirstCharAttrs, wordRemainingAttrs); console.crlf(); console.print("\x01cYour choice (\x01hQ\x01n\x01c: Quit)\x01h: \x01g"); var userChoiceNum = console.getnum(HIGHEST_CHOICE_NUM); @@ -14190,11 +14283,26 @@ function DigDistMsgReader_DoUserSettings_Traditional() var userSettingsChanged = false; switch (userChoiceNum) { - case USER_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM: + case LIST_MESSAGES_IN_REVERSE_OPT_NUM: + var oldListMsgsInReverseSetting = this.userSettings.listMessagesInReverse; + this.userSettings.listMessagesInReverse = !console.noyes("List messages in reverse"); + userSettingsChanged = (this.userSettings.listMessagesInReverse != oldListMsgsInReverseSetting); + break; + case USE_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM: var oldIndexedModeNewscanSetting = this.userSettings.useIndexedModeForNewscan; this.userSettings.useIndexedModeForNewscan = !console.noyes("Use indexed mode for newscan-all"); userSettingsChanged = (this.userSettings.useIndexedModeForNewscan != oldIndexedModeNewscanSetting); break; + case INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_NUM: + var oldIndexedModeEnterShowsMsgListSetting = this.userSettings.enterFromIndexMenuShowsMsgList; + this.userSettings.enterFromIndexMenuShowsMsgList = !console.noyes("Index menu: Show message list with enter"); + userSettingsChanged = (this.userSettings.enterFromIndexMenuShowsMsgList != oldIndexedModeEnterShowsMsgListSetting); + break; + case READER_QUIT_TO_MSG_LIST_OPT_NUM: + var oldReaderQuitSetting = this.userSettings.quitFromReaderGoesToMsgList; + this.userSettings.quitFromReaderGoesToMsgList = !console.noyes("Quit key from reader: Go to the message list (rather than exit)"); + userSettingsChanged = (this.userSettings.quitFromReaderGoesToMsgList != oldReaderQuitSetting); + break; case USER_TWITLIST_OPT_NUM: console.editfile(gUserTwitListFilename); // Re-read the user's twitlist and see if the user's twitlist changed @@ -14219,23 +14327,48 @@ function DigDistMsgReader_DoUserSettings_Traditional() return retObj; } +// Helper for DigDistMsgReader_DoUserSettings_Traditional: Returns a string where for each word, +// the first letter will have one set of Synchronet attributes applied and the remainder of the word +// will have another set of Synchronet attributes applied +function colorFirstCharAndRemainingCharsInWords(pStr, pWordFirstCharAttrs, pWordRemainderAttrs) +{ + if (typeof(pStr) !== "string" || pStr.length == 0) + return ""; + if (typeof(pWordFirstCharAttrs) !== "string" || typeof(pWordRemainderAttrs) !== "string") + return pStr; + + var wordsArray = pStr.split(" "); + for (var i = 0; i < wordsArray.length; ++i) + { + if (wordsArray[i] != " ") + wordsArray[i] = "\x01n" + pWordFirstCharAttrs + wordsArray[i].substr(0, 1) + "\x01n" + pWordRemainderAttrs + wordsArray[i].substr(1); + } + return wordsArray.join(" "); +} + +// Helper for DigDistMsgReader_DoUserSettings_Traditional: Returns a string where for each word, +// the first letter will have one set of Synchronet attributes applied and the remainder of the word +// will have another set of Synchronet attributes applied +function printTradUserSettingOption(pOptNum, pStr, pWordFirstCharAttrs, pWordRemainderAttrs) +{ + printf("\x01c\x01h%d\x01g: %s\r\n", pOptNum, colorFirstCharAndRemainingCharsInWords(pStr, pWordFirstCharAttrs, pWordRemainderAttrs)); +} /////////////////////////////////////////////////////////////// // Stuff for indexed reader mode -// For the DigDistMsgReader class: Starts indexed mode +// For the DigDistMsgReader class: Starts indexed mode. For a new scan, displays +// a menu of sub-boards with the number of new messages etc. // // Parameters: // pScanScope: Numeric - Whether to scan the current sub-board, group, or all. // This would be SCAN_SCOPE_SUB_BOARD, SCAN_SCOPE_GROUP, or SCAN_SCOPE_ALL. function DigDistMsgReader_DoIndexedMode(pScanScope) { - // GitLab issue for DDMsgReader "Indexed" reader mode: - // https://gitlab.synchro.net/main/sbbs/-/issues/354 - var scanScope = (isValidScanScopeVal(pScanScope) ? pScanScope : SCAN_SCOPE_ALL); this.indexedMode = true; + this.doingMsgScan = true; // msgHdrsCache is used to prevent long loading again when loading a sub-board // a 2nd time or later. Only if this.enableIndexedModeMsgListCache is true. @@ -14250,8 +14383,12 @@ function DigDistMsgReader_DoIndexedMode(pScanScope) // Let the user choose a sub-board, and if their choice is valid, // let them read the sub-board. var indexRetObj = this.IndexedModeChooseSubBoard(clearScreenForMenu, drawMenu, writeBottomHelpLine, pScanScope); - if (typeof(indexRetObj.chosenSubCode) === "string" && msg_area.sub.hasOwnProperty(indexRetObj.chosenSubCode)) + var userChoseAValidSubBoard = (typeof(indexRetObj.chosenSubCode) === "string" && msg_area.sub.hasOwnProperty(indexRetObj.chosenSubCode)); + if (userChoseAValidSubBoard) { + // Ensure we draw the index menu & help line in the next iteration + drawMenu = true; + writeBottomHelpLine = true; this.subBoardCode = indexRetObj.chosenSubCode; if (!this.enableIndexedModeMsgListCache || !msgHdrsCache.hasOwnProperty(indexRetObj.chosenSubCode)) { @@ -14273,33 +14410,60 @@ function DigDistMsgReader_DoIndexedMode(pScanScope) var startIdx = numMessages - indexRetObj.numNewMsgs; if (startIdx < 0) startIdx = numMessages - 1; - // Let the user read the sub-board - // pSubBoardCode, pStartingMsgOffset, pReturnOnMessageList, pAllowChgArea, pReturnOnNextAreaNav, - // pPromptToGoToNextAreaIfNoSearchResults - var readRetObj = this.ReadMessages(indexRetObj.chosenSubCode, startIdx, false, false, true, false); - // Update the text for the current menu item to ensure the message numbers are up to date - var currentMenuItem = this.indexedModeMenu.GetItem(this.indexedModeMenu.selectedItemIdx); - var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(indexRetObj.chosenSubCode); - currentMenuItem.text = itemInfo.itemText; - currentMenuItem.retval.numNewMsgs = itemInfo.numNewMsgs; - // If enabled, store the message headers for this sub-board as a cache for performance - if (this.enableIndexedModeMsgListCache) + // If the user chose to view the message list, display the message list to let the user + // choose a message to read. Otherwise, start reader mode. + if (indexRetObj.viewMsgList) { - msgHdrsCache[indexRetObj.chosenSubCode] = { - hdrsForCurrentSubBoard: this.hdrsForCurrentSubBoard, - hdrsForCurrentSubBoardByMsgNum: this.hdrsForCurrentSubBoardByMsgNum - }; + var listRetObj = this.ListMessages(indexRetObj.chosenSubCode, false); + // If the user chose a message from the list, let the user read starting with that message + if (listRetObj.selectedMsgOffset > -1) + { + var readRetObj = this.ReadMessages(indexRetObj.chosenSubCode, listRetObj.selectedMsgOffset, false, false, true, false); + // Update the text for the current menu item to ensure the message numbers are up to date + var currentMenuItem = this.indexedModeMenu.GetItem(this.indexedModeMenu.selectedItemIdx); + var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(indexRetObj.chosenSubCode); + currentMenuItem.text = itemInfo.itemText; + currentMenuItem.retval.numNewMsgs = itemInfo.numNewMsgs; + // If enabled, store the message headers for this sub-board as a cache for performance + if (this.enableIndexedModeMsgListCache) + { + msgHdrsCache[indexRetObj.chosenSubCode] = { + hdrsForCurrentSubBoard: this.hdrsForCurrentSubBoard, + hdrsForCurrentSubBoardByMsgNum: this.hdrsForCurrentSubBoardByMsgNum + }; + } + } } - /* - switch (readRetObj.lastAction) + else { - case ACTION_QUIT: - //continueOn = false; - break; - default: - break; + // Let the user read the sub-board + // pSubBoardCode, pStartingMsgOffset, pReturnOnMessageList, pAllowChgArea, pReturnOnNextAreaNav, + // pPromptToGoToNextAreaIfNoSearchResults + var readRetObj = this.ReadMessages(indexRetObj.chosenSubCode, startIdx, false, false, true, false); + // Update the text for the current menu item to ensure the message numbers are up to date + var currentMenuItem = this.indexedModeMenu.GetItem(this.indexedModeMenu.selectedItemIdx); + var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(indexRetObj.chosenSubCode); + currentMenuItem.text = itemInfo.itemText; + currentMenuItem.retval.numNewMsgs = itemInfo.numNewMsgs; + // If enabled, store the message headers for this sub-board as a cache for performance + if (this.enableIndexedModeMsgListCache) + { + msgHdrsCache[indexRetObj.chosenSubCode] = { + hdrsForCurrentSubBoard: this.hdrsForCurrentSubBoard, + hdrsForCurrentSubBoardByMsgNum: this.hdrsForCurrentSubBoardByMsgNum + }; + } + /* + switch (readRetObj.lastAction) + { + case ACTION_QUIT: + //continueOn = false; + break; + default: + break; + } + */ } - */ } else { @@ -14355,6 +14519,7 @@ function DigDistMsgReader_DoIndexedMode(pScanScope) } this.indexedMode = false; + this.doingMsgScan = false; } // For indexed mode: Displays any sub-boards with new messages and lets the user choose one @@ -14369,11 +14534,14 @@ function DigDistMsgReader_DoIndexedMode(pScanScope) // Return value: An object containing the following values: // chosenSubCode: The user's chosen sub-board code; if none selected, this will be null // numNewMsgs: The number of new messages in the chosen sub-board +// viewMsgList: Whether or not to view the message list instead of going to reader mode +// lastUserInput: The last keypress entered by the user function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDisplayHelpLine, pScanScope) { var retObj = { chosenSubCode: null, numNewMsgs: 0, + viewMsgList: false, // To view the message list instead of reader mode lastUserInput: "" }; @@ -14383,19 +14551,31 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi var scanScope = (isValidScanScopeVal(pScanScope) ? pScanScope : SCAN_SCOPE_ALL); - // Note: DDlightbarMenu now supports non-ANSI terminals with a more traditional UI + // Note: DDlightbarMenu supports non-ANSI terminals with a more traditional UI // of listing the items and letting the user choose one by typing its number. // Set text widths for the menu items var newMsgWidthObj = findWidestNumMsgsAndNumNewMsgs(scanScope); var numMsgsWidth = newMsgWidthObj.widestNumMsgs; var numNewMsgsWidth = newMsgWidthObj.widestNumNewMsgs; + // Ensure the column widths for the last few columns (after description) are wide enough + // to fit the labels + if (numMsgsWidth < 5) // "Total" + numMsgsWidth = 5; + if (numNewMsgsWidth < 3) // "New" + numNewMsgsWidth = 3; var lastPostDateWidth = 10; this.indexedModeItemDescWidth = console.screen_columns - numMsgsWidth - numNewMsgsWidth - lastPostDateWidth - 4; + var usingTradInterface = !this.msgListUseLightbarListInterface || !console.term_supports(USER_ANSI); + if (usingTradInterface) + this.indexedModeItemDescWidth -= 3; this.indexedModeSubBoardMenuFormatStrNumbers = "%-" + this.indexedModeItemDescWidth + "s %" + numMsgsWidth + "d %" + numNewMsgsWidth + "d %" + lastPostDateWidth + "s"; if (typeof(this.indexedModeMenu) !== "object") this.indexedModeMenu = this.MakeLightbarIndexedModeMenu(numMsgsWidth, numNewMsgsWidth, lastPostDateWidth, this.indexedModeItemDescWidth, this.indexedModeSubBoardMenuFormatStrNumbers); - + else + DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx = this.indexedModeMenu.selectedItemIdx; + // Ensure the menu is clear, and (re-)populate the menu with sub-board information w/ # of new messages in each, etc. + this.indexedModeMenu.RemoveAllItems(); for (var grpIdx = 0; grpIdx < msg_area.grp_list.length; ++grpIdx) { // If scanning the user's current group or sub-board and this is the wrong group, then skip this group. @@ -14405,6 +14585,7 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi var grpNameItemAddedToMenu = false; for (var subIdx = 0; subIdx < msg_area.grp_list[grpIdx].sub_list.length; ++subIdx) { + // Skip sub-boards that the user can't read or doesn't have configured for newscans if (!msg_area.grp_list[grpIdx].sub_list[subIdx].can_read) continue; if ((msg_area.grp_list[grpIdx].sub_list[subIdx].scan_cfg & SCAN_CFG_NEW) == 0) @@ -14420,9 +14601,7 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi var grpDesc = msg_area.grp_list[grpIdx].name; if (msg_area.grp_list[grpIdx].name != msg_area.grp_list[grpIdx].description) grpDesc += " - " + msg_area.grp_list[grpIdx].description; - var menuItemText = "\x01n\x01b"; - for (var i = 0; i < 5; ++i) - menuItemText += HORIZONTAL_SINGLE; + var menuItemText = "\x01n\x01b" + charStr(HORIZONTAL_SINGLE, 5); menuItemText += "\x01y\x01h "; menuItemText += grpDesc; var menuItemLen = console.strlen(menuItemText); @@ -14432,6 +14611,7 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi var numChars = this.indexedModeMenu.size.width - menuItemLen - 1; menuItemText += charStr(HORIZONTAL_SINGLE, numChars); } + menuItemText = skipsp(truncsp(menuItemText)); // Trim leading & trailing whitespace this.indexedModeMenu.Add(menuItemText, null, null, false); // Not selectable grpNameItemAddedToMenu = true; } @@ -14443,6 +14623,36 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi }); } } + // If we've saved the index of the selected item in the menu, then set it back in the menu, if it's + // valid. This is done because the list of items is cleared each time this function is called. + if (typeof(DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx) === "number") + { + if (DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx >= 0 && + DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx < this.indexedModeMenu.NumItems()) + { + this.indexedModeMenu.SetSelectedItemIdx(DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx); + // If the indexed menu has more items than will fit on the screen & isn't on the last + // page, then set the top item index to one before the selected index (if >0) or the + // same as the selected item index. + var selectedItemIsFirst = this.indexedModeMenu.selectedItemIdx == this.indexedModeMenu.topItemIdx; + var selectedItemOnLastPage = DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx >= this.indexedModeMenu.GetTopItemIdxOfLastPage(); + var moreThanOneScreenfulOfItems = this.indexedModeMenu.NumItems() > console.screen_columns - 2; + // Checking if the selected item index is on the first page + var numItems = this.indexedModeMenu.NumItems(); + var numItemsPerPage = this.indexedModeMenu.GetNumItemsPerPage(); + var lastItemIdxForFirstPage = (numItems > numItemsPerPage ? numItemsPerPage - 1 : numItems - 1); + var selectedItemIsOnFirstPage = (this.indexedModeMenu.selectedItemIdx >= 0 && this.indexedModeMenu.selectedItemIdx <= lastItemIdxForFirstPage); + if (!selectedItemIsFirst && !selectedItemOnLastPage && moreThanOneScreenfulOfItems && !selectedItemIsOnFirstPage) + { + if (this.indexedModeMenu.selectedItemIdx > 0) + this.indexedModeMenu.topItemIdx = this.indexedModeMenu.selectedItemIdx - 1; + else + this.indexedModeMenu.topItemIdx = this.indexedModeMenu.selectedItemIdx; + } + } + else + DigDistMsgReader_IndexedModeChooseSubBoard.selectedItemIdx = 0; + } // Clear the screen, if desired if (clearScreen) @@ -14461,20 +14671,38 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi console.attributes = "N"; } - // Show the menu and let the user make a choice + // Output a header above the menu if (usingANSI) console.gotoxy(1, 1); - var dateWidth = (this.indexedModeMenu.CanShowAllItemsInWindow() ? lastPostDateWidth : lastPostDateWidth-1); - var formatStrStrs = "%-" + (this.indexedModeItemDescWidth-1) + "s %" + numMsgsWidth + "s %" + numNewMsgsWidth + "s %" + dateWidth + "s"; + var descWidthForHdr = this.indexedModeItemDescWidth; + var maxScreenWidth = console.screen_columns - 3; // 3 spaces + var currentTotalColWidth = descWidthForHdr + numMsgsWidth + numNewMsgsWidth + lastPostDateWidth; + if (currentTotalColWidth < maxScreenWidth) + descWidthForHdr += (maxScreenWidth - currentTotalColWidth - 1); + var formatStrStrs = "%-" + descWidthForHdr + "s %" + numMsgsWidth + "s %" + numNewMsgsWidth + "s %" + lastPostDateWidth + "s"; printf(formatStrStrs, "Description", "Total", "New", "Last Post"); console.attributes = "N"; - if (!usingANSI) console.crlf(); + if (!usingANSI) + console.crlf(); var menuRetval = this.indexedModeMenu.GetVal(drawMenu); + // Show the menu and get the user's choice retObj.lastUserInput = this.indexedModeMenu.lastUserInput; if (menuRetval != null) { retObj.chosenSubCode = menuRetval.subCode; retObj.numNewMsgs = menuRetval.numNewMsgs; + // If the user has the option enabled to view the message list when pressing enter here, + // then allow that. + if (this.userSettings.enterFromIndexMenuShowsMsgList) + retObj.viewMsgList = true; + } + else if (this.indexedModeMenu.lastUserInput == "m" || this.indexedModeMenu.lastUserInput == "M") + { + // Message list for the highlighted sub-board + var highlightedItem = this.indexedModeMenu.GetItem(this.indexedModeMenu.selectedItemIdx); + retObj.chosenSubCode = highlightedItem.retval.subCode; + retObj.numNewMsgs = highlightedItem.retval.numNewMsgs; + retObj.viewMsgList = true; } console.attributes = "N"; return retObj; @@ -14526,13 +14754,21 @@ function DigDistMsgReader_MakeIndexedModeHelpLine() + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`PgUp`" + "\x1b[V" + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + "/" - + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`Dn`" + KEY_PAGEDN + "@" + + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`P`" + "\x1b[V" + "@" + + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`PgDn`" + KEY_PAGEDN + "@" + + this.colors.lightbarIndexedModeHelpLineGeneralColor + "/" + + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`N`" + "\x1b[V" + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`ENTER`" + KEY_ENTER + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`HOME`" + KEY_HOME + "@" + + this.colors.lightbarIndexedModeHelpLineGeneralColor + "/" + + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`F`" + "\x1b[V" + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`END`" + KEY_END + "@" + + this.colors.lightbarIndexedModeHelpLineGeneralColor + "/" + + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`L`" + "\x1b[V" + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`Ctrl-U`" + CTRL_U + "@" + this.colors.lightbarIndexedModeHelpLineGeneralColor + ", " @@ -14541,7 +14777,7 @@ function DigDistMsgReader_MakeIndexedModeHelpLine() + this.colors.lightbarIndexedModeHelpLineGeneralColor + "uit, " + this.colors.lightbarIndexedModeHelpLineHotkeyColor + "@`?`?@"; // Add spaces so that the text is centered on the screen - var helpLineLen = 49; // 41 without Ctrl-U + var helpLineLen = 60; var leftSideNumChars = Math.floor(console.screen_columns / 2) - Math.floor(helpLineLen / 2); var rightSideNumChars = leftSideNumChars; var totalNumChars = leftSideNumChars + rightSideNumChars + helpLineLen; @@ -14570,12 +14806,14 @@ function DigDistMsgReader_ShowIndexedListHelp() console.crlf(); displayTextWithLineBelow("Summary of the keyboard commands:", false, this.colors.tradInterfaceHelpScreenColor, "\x01k\x01h"); console.print(this.colors.tradInterfaceHelpScreenColor); - console.print("\x01n\x01h\x01cDown arrow" + this.colors.tradInterfaceHelpScreenColor + ": Move the cursor down/select the next message\r\n"); - console.print("\x01n\x01h\x01cUp arrow" + this.colors.tradInterfaceHelpScreenColor + ": Move the cursor up/select the previous message\r\n"); - console.print("\x01n\x01h\x01cPageDown" + this.colors.tradInterfaceHelpScreenColor + ": Go to the next page\r\n"); - console.print("\x01n\x01h\x01cPageUp" + this.colors.tradInterfaceHelpScreenColor + ": Go to the previous page\r\n"); - console.print("\x01n\x01h\x01cHOME" + this.colors.tradInterfaceHelpScreenColor + ": Go to the first item\r\n"); - console.print("\x01n\x01h\x01cEND" + this.colors.tradInterfaceHelpScreenColor + ": Go to the last item\r\n"); + console.print("\x01n\x01h\x01cDown arrow" + this.colors.tradInterfaceHelpScreenColor + ": Move the cursor down/select the next sub-board\r\n"); + console.print("\x01n\x01h\x01cUp arrow" + this.colors.tradInterfaceHelpScreenColor + ": Move the cursor up/select the previous sub-board\r\n"); + console.print("\x01n\x01h\x01cPageDown \x01n\x01cor \x01hN" + this.colors.tradInterfaceHelpScreenColor + ": Go to the next page\r\n"); + console.print("\x01n\x01h\x01cPageUp \x01n\x01cor \x01hP" + this.colors.tradInterfaceHelpScreenColor + ": Go to the previous page\r\n"); + console.print("\x01n\x01h\x01cHOME \x01n\x01cor \x01hF" + this.colors.tradInterfaceHelpScreenColor + ": Go to the first item\r\n"); + console.print("\x01n\x01h\x01cEND \x01n\x01cor \x01hL" + this.colors.tradInterfaceHelpScreenColor + ": Go to the last item\r\n"); + console.print("\x01n\x01h\x01cENTER" + this.colors.tradInterfaceHelpScreenColor + ": Read the sub-board\r\n"); + console.print("\x01n\x01h\x01cM" + this.colors.tradInterfaceHelpScreenColor + ": Show message list for the sub-board\r\n"); console.print("\x01n\x01h\x01cCtrl-U" + this.colors.tradInterfaceHelpScreenColor + ": User settings\r\n"); console.print("\x01n\x01h\x01cQ" + this.colors.tradInterfaceHelpScreenColor + ": Quit\r\n"); //console.print("\x01n\x01h\x01c?" + this.colors.tradInterfaceHelpScreenColor + ": Show this help screen\r\n"); @@ -14658,38 +14896,39 @@ function getLatestPostTimestampAndNumNewMsgs(pSubCode) // scan_ptr: user's current new message scan pointer (highest-read message number) if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number") { - var lastNonDeletedMsgHdr = GetLastNonDeletedMsgHdr(pSubCode); - if (lastNonDeletedMsgHdr != null) + var lastReadableMsgHdr = getLastReadableMsgHdrInSubBoard(pSubCode); + if (lastReadableMsgHdr != null) { - // Check for 4294967295 (0xffffffff, or ~0): That is a special value - // for the user's scan_ptr meaning it should point to the latest message - // in the messagebase. - if (msg_area.sub[pSubCode].scan_ptr != 0xffffffff) - { - retObj.numNewMsgs = lastNonDeletedMsgHdr.number - msg_area.sub[pSubCode].scan_ptr; - /* - // Count the number of readable messages after scan_ptr up to lastNonDeletedMsgHdr. - var scan - // First, get the message number and go from there. - var scanPtrMsgHdr = msgbase.get_msg_header(false, msg_area.sub[pSubCode].scan_ptr, false); - if (scanPtrMsgHdr != null) + // If the user's scan_ptr for the sub-board isn't the 'last message' + // special value, then use it + if (!subBoardScanPtrIsLatestMsgSpecialVal(pSubCode)) + { + // Count the number of readable messages after scan_ptr up to the last read message. + // If both index objects are null, then calculate this via the last readable message + // number and the scan pointer. + var scanPtrMsgIndex = msgbase.get_msg_index(false, msg_area.sub[pSubCode].scan_ptr, false); + //var lastMsgIndex = msgbase.get_msg_index(false, msgbase.last_msg, false); + //if (scanPtrMsgIndex != null && lastMsgIndex != null) + if (scanPtrMsgIndex != null) { - for (var msgOffset = lastNonDeletedMsgHdr.offset + 1; msgOffset <= scanPtrMsgHdr.offset; ++msgOffset) + //for (var i = scanPtrMsgIndex.offset; i < lastMsgIndex.offset; ++i) + for (var i = scanPtrMsgIndex.offset; i < lastReadableMsgHdr.offset; ++i) { - var msgHdr = msgbase.get_msg_header(true, msgOffset, true); - if (isReadableMsgHdr(msgHdr, pSubCode)) + var msgIndex = msgbase.get_msg_index(true, i, false); + if (msgIndex != null && isReadableMsgHdr(msgIndex, pSubCode)) ++retObj.numNewMsgs; } } - */ + else + retObj.numNewMsgs = lastReadableMsgHdr.number - msg_area.sub[pSubCode].scan_ptr; } } } else if (typeof(msg_area.sub[pSubCode].last_read) === "number") { - var lastNonDeletedMsgHdr = GetLastNonDeletedMsgHdr(pSubCode); - if (lastNonDeletedMsgHdr != null) - retObj.numNewMsgs = lastNonDeletedMsgHdr.number - msg_area.sub[pSubCode].last_read; + var lastReadableMsgHdr = getLastReadableMsgHdrInSubBoard(pSubCode); + if (lastReadableMsgHdr != null) + retObj.numNewMsgs = lastReadableMsgHdr.number - msg_area.sub[pSubCode].last_read; } else retObj.numNewMsgs = msg_area.sub[pSubCode].posts; @@ -14744,11 +14983,18 @@ function DigDistMsgReader_MakeLightbarIndexedModeMenu(pNumMsgsWidth, pNumNewMsgs indexedModeMenu.multiSelect = false; indexedModeMenu.ampersandHotkeysInItems = false; indexedModeMenu.wrapNavigation = false; - indexedModeMenu.allowANSI = this.msgListUseLightbarListInterface; + indexedModeMenu.allowANSI = this.msgListUseLightbarListInterface && console.term_supports(USER_ANSI); // Add additional keypresses for quitting the menu's input loop so we can // respond to these keys - indexedModeMenu.AddAdditionalQuitKeys("Qq?" + CTRL_U); // Ctrl-U for user settings + // TODO: Include Mm to allow the user to view the message list instead of read it from the indexed menu + indexedModeMenu.AddAdditionalQuitKeys("QqMm?" + CTRL_U); // Ctrl-U for user settings + + // Add additional keypresses for PageUp, PageDown, HOME (first page), and END (last page) + indexedModeMenu.AddAdditionalPageUpKeys("Pp"); // Previous page + indexedModeMenu.AddAdditionalPageDownKeys("Nn"); // Next page + indexedModeMenu.AddAdditionalFirstPageKeys("Ff"); // First page + indexedModeMenu.AddAdditionalLastPageKeys("Ll"); // Last page return indexedModeMenu; } @@ -14903,8 +15149,8 @@ function DigDistMsgReader_SaveMsgToFile(pMsgHdr, pFilename, pPromptPos) function DigDistMsgReader_ToggleSelectedMessage(pSubCode, pMsgIdx, pSelected) { // Sanity checking - if (typeof(pSubCode) != "string") return; - if (typeof(pMsgIdx) != "number") return; + if (typeof(pSubCode) !== "string") return; + if (typeof(pMsgIdx) !== "number") return; // If the 'selected message' object doesn't have the sub code index, // then add it. @@ -16340,10 +16586,10 @@ function DigDistMsgReader_RefreshMsgAreaRectangle(pTxtLines, pTopLineIdx, pTopLe if (txtLineIdx < pTxtLines.length) { // Get the text attributes up to the current point and output them - console.print(getAllEditLineAttrsUntilLineIdx(pTxtLines, txtLineIdx, true, txtLineStartIdx)); + //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(pStr, pStartIdx, pLen) is defined in dd_lightbar_menu.js - var lineText = substrWithAttrCodes(pTxtLines[txtLineIdx], txtLineStartIdx, pWidth); + // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js + var lineText = substrWithAttrCodes(pTxtLines[txtLineIdx].replace(/[\r\n]+/g, ""), txtLineStartIdx, pWidth); var printableTxtLen = console.strlen(lineText); if (printableTxtLen < pWidth) lineText += format("\x01n%*s", pWidth - printableTxtLen, ""); @@ -16524,6 +16770,9 @@ function getDefaultColors() // Selected message marker color selectedMsgMarkColor: "\x01n\x01w\x01h", + // Unread personal email message marker color + unreadMsgMarkColor: "\x01n\x01w\x01h\x01i", + // Colors for the indexed mode sub-board menu: indexMenuDesc: "\x01n\x01w", indexMenuTotalMsgs: "\x01n\x01w", @@ -16653,11 +16902,11 @@ function trimSpaces(pString, pLeading, pMultiple, pTrailing) //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); if (leading) - pString = pString.replace(/(^\s*)/gi,""); + pString = skipsp(pString); //pString.replace(/(^\s*)/gi,""); if (multiple) pString = pString.replace(/[ ]{2,}/gi," "); if (trailing) - pString = pString.replace(/(\s*$)/gi,""); + pString = truncsp(pString); //pString.replace(/(\s*$)/gi,""); return pString; } @@ -17004,7 +17253,7 @@ function numMsgsInSubBoard(pSubBoardCode, pIncludeDeleted) // as deleted. for (var msgIdx = 0; msgIdx < msgbase.total_msgs; ++msgIdx) { - var msgHdr = msgbase.get_msg_header(true, msgIdx, false); + var msgHdr = msgbase.get_msg_index(true, msgIdx, false); if ((msgHdr != null) && (((msgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs())) ++numMessages; } @@ -17084,27 +17333,75 @@ function shortenStrWithAttrCodes(pStr, pNewLength, pFromLeft) return strCopy; } -// Returns whether a given name matches the logged-in user's handle, alias, or -// name. +// Returns whether a given name or CRC16 value matches the logged-in user's +// handle, alias, or name. // // Parameters: -// pName: A name to match against the logged-in user +// pNameOrCRC16: A name (string) to match against the logged-in user, or a CRC16 (number) +// to match against the logged-in user // // Return value: Boolean - Whether or not the given name matches the logged-in // user's handle, alias, or name -function userHandleAliasNameMatch(pName) +function userHandleAliasNameMatch(pNameOrCRC16) { - if (typeof(pName) != "string") + var checkByCRC16 = (typeof(pNameOrCRC16) === "number"); + if (!checkByCRC16 && typeof(pNameOrCRC16) !== "string") return false; var userMatch = false; - var nameUpper = pName.toUpperCase(); - if (user.handle.length > 0) - userMatch = (nameUpper.indexOf(user.handle.toUpperCase()) > -1); - if (!userMatch && (user.alias.length > 0)) - userMatch = (nameUpper.indexOf(user.alias.toUpperCase()) > -1); - if (!userMatch && (user.name.length > 0)) - userMatch = (nameUpper.indexOf(user.name.toUpperCase()) > -1); + if (checkByCRC16) + { + if (user.handle.length > 0) + { + if (userHandleAliasNameMatch.userHandleCRC16 === undefined) + userHandleAliasNameMatch.userHandleCRC16 = crc16_calc(user.handle.toLowerCase()); + userMatch = (userHandleAliasNameMatch.userHandleCRC16 == pNameOrCRC16); + } + if (!userMatch && (user.alias.length > 0)) + { + if (userHandleAliasNameMatch.userAliasCRC16 === undefined) + userHandleAliasNameMatch.userAliasCRC16 = crc16_calc(user.alias.toLowerCase()); + userMatch = (userHandleAliasNameMatch.userAliasCRC16 == pNameOrCRC16); + } + if (!userMatch && (user.name.length > 0)) + { + if (userHandleAliasNameMatch.userNameCRC16 === undefined) + userHandleAliasNameMatch.userNameCRC16 = crc16_calc(user.name.toLowerCase()); + userMatch = (userHandleAliasNameMatch.userNameCRC16 == pNameOrCRC16); + } + } + else + { + if (pNameOrCRC16 != "") + { + var nameUpper = pNameOrCRC16.toUpperCase(); + // If the name starts & ends with the same quote character, then remove the + // quote characters. + var firstChar = nameUpper.charAt(0); + var lastChar = nameUpper.charAt(nameUpper.length-1); + if ((firstChar == "\"" && lastChar == "\"") ||(firstChar == "'" && lastChar == "'")) + nameUpper = nameUpper.substring(1, nameUpper.length-1); + + if (user.handle.length > 0) + { + if (userHandleAliasNameMatch.userHandleUpper === undefined) + userHandleAliasNameMatch.userHandleUpper = user.handle.toUpperCase(); + userMatch = (nameUpper.indexOf(userHandleAliasNameMatch.userHandleUpper) > -1); + } + if (!userMatch && (user.alias.length > 0)) + { + if (userHandleAliasNameMatch.userAliasUpper === undefined) + userHandleAliasNameMatch.userAliasUpper = user.alias.toUpperCase(); + userMatch = (nameUpper.indexOf(userHandleAliasNameMatch.userAliasUpper) > -1); + } + if (!userMatch && (user.name.length > 0)) + { + if (userHandleAliasNameMatch.userNameUpper === undefined) + userHandleAliasNameMatch.userNameUpper = user.name.toUpperCase(); + userMatch = (nameUpper.indexOf(userHandleAliasNameMatch.userNameUpper) > -1); + } + } + } return userMatch; } @@ -17619,7 +17916,7 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma matchFn = function(pSearchStr, pMsgHdr, pMsgBase, pSubBoardCode) { // See if the message is not marked as deleted and the 'To' name // matches the user's handle, alias, and/or username. - return ((((pMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && userNameHandleAliasMatch(pMsgHdr.to)); + return ((((pMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && userHandleAliasNameMatch(pMsgHdr.to)); } break; case SEARCH_TO_USER_NEW_SCAN: @@ -17628,6 +17925,7 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma case SEARCH_TO_USER_NEW_SCAN_ALL: if (pSubCode != "mail") { + /* // If pStartIndex or pEndIndex aren't specified, then set // startMsgIndex to the scan pointer and endMsgIndex to one // past the index of the last message in the sub-board @@ -17640,8 +17938,8 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma subBoardGrpAndName(pSubCode) + " -- Scan pointer: " + msg_area.sub[pSubCode].scan_ptr); } - //startMsgIndex = absMsgNumToIdx(msgbase, msg_area.sub[pSubCode].last_read); - startMsgIndex = absMsgNumToIdx(msgbase, GetScanPtrOrLastMsgNum(pSubCode)); + //startMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, msg_area.sub[pSubCode].last_read); + startMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, GetScanPtrOrLastMsgNum(pSubCode)); if (startMsgIndex == -1) { msg_area.sub[pSubCode].scan_ptr = 0; @@ -17650,7 +17948,7 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma else { // If this message has been read, then start at the next message. - var startMsgHeader = msgbase.get_msg_header(true, startMsgIndex, false); + var startMsgHeader = msgbase.get_msg_index(true, startMsgIndex, false); if (startMsgHeader == null) ++startMsgIndex; else @@ -17662,29 +17960,64 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma } if (typeof(pEndIndex) != "number") endMsgIndex = (msgbase.total_msgs > 0 ? msgbase.total_msgs : 0); + */ + // For the new-to-you scan faster, check messages to the user from the user's new scan pointer to + // messagebase.last_msg + // scan_ptr (not last_read?) + if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number") + startMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, msg_area.sub[pSubCode].scan_ptr); + else if (typeof(pStartIndex) === "number") + startMsgIndex = pStartIndex; + else + startMsgIndex = 0; + endMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, msgbase.last_msg) + 1; + if (endMsgIndex == 0) // Not valid + endMsgIndex = msgbase.total_msgs; } matchFn = function(pSearchStr, pMsgHdr, pMsgBase, pSubBoardCode) { // Note: This assumes pSubBoardCode is not "mail" (personal mail). // See if the message 'To' name matches the user's handle, alias, // and/or username and is not marked as deleted and is unread. - return ((((pMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && ((pMsgHdr.attr & MSG_READ) == 0) && userNameHandleAliasMatch(pMsgHdr.to)); + return ((((pMsgHdr.attr & MSG_DELETE) == 0) || canViewDeletedMsgs()) && ((pMsgHdr.attr & MSG_READ) == 0) && userHandleAliasNameMatch(pMsgHdr.to)); } break; case SEARCH_MSG_NEWSCAN: case SEARCH_MSG_NEWSCAN_CUR_SUB: case SEARCH_MSG_NEWSCAN_CUR_GRP: case SEARCH_MSG_NEWSCAN_ALL: + /* + matchFn = function(pSearchStr, pMsgHdr, pMsgBase, pSubBoardCode) { + // Note: This assumes pSubBoardCode is not "mail" (personal mail). + // Get the offset of the last read message and compare it with the + // offset of the given message header + var lastReadMsgHdr = pMsgBase.get_msg_index(false, msg_area.sub[pSubBoardCode].last_read, false); + var lastReadMsgOffset = 0; + if (lastReadMsgHdr != null) + lastReadMsgOffset = absMsgNumToIdx(pSubBoardCode, lastReadMsgHdr.number); + if (lastReadMsgOffset < 0) + lastReadMsgOffset = 0; + return (absMsgNumToIdx(pSubBoardCode, pMsgHdr.number) > lastReadMsgOffset); + } + */ + if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number") + startMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, msg_area.sub[pSubCode].scan_ptr); + else if (typeof(pStartIndex) === "number") + startMsgIndex = pStartIndex; + else + startMsgIndex = 0; + endMsgIndex = absMsgNumToIdxWithMsgbaseObj(msgbase, msgbase.last_msg) + 1; + // TODO: Is this matchFn really needed? matchFn = function(pSearchStr, pMsgHdr, pMsgBase, pSubBoardCode) { // Note: This assumes pSubBoardCode is not "mail" (personal mail). // Get the offset of the last read message and compare it with the // offset of the given message header - var lastReadMsgHdr = pMsgBase.get_msg_header(false, msg_area.sub[pSubBoardCode].last_read, false); + var lastReadMsgHdr = pMsgBase.get_msg_index(false, msg_area.sub[pSubBoardCode].last_read, false); var lastReadMsgOffset = 0; if (lastReadMsgHdr != null) - lastReadMsgOffset = msgNumToIdxFromMsgbase(pSubBoardCode, lastReadMsgHdr.number); + lastReadMsgOffset = absMsgNumToIdx(pSubBoardCode, lastReadMsgHdr.number); if (lastReadMsgOffset < 0) lastReadMsgOffset = 0; - return (msgNumToIdxFromMsgbase(pSubBoardCode, pMsgHdr.number) > lastReadMsgOffset); + return (absMsgNumToIdx(pSubBoardCode, pMsgHdr.number) > lastReadMsgOffset); } break; } @@ -17736,8 +18069,6 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma for (var msgIdx = startMsgIndex; msgIdx < endMsgIndex; ++msgIdx) { var msgHeader = msgbase.get_msg_header(true, msgIdx, false); - // I've seen situations where the message header object is null for - // some reason, so check that before running the search function. if (msgHeader != null) { if (matchFn(pSearchString, msgHeader, msgbase, pSubCode)) @@ -17746,10 +18077,99 @@ function searchMsgbase(pSubCode, pSearchType, pSearchString, pListingPersonalEma } } } + else + { + // There is no match function - Get all message headers between the start & end indexes + } msgbase.close(); return msgHeaders; } +// Tries to look up a message index (offset) with a given message number. +// On error, returns -1. +// +// Parameters: +// pMsgbase: The messagebase object from which to retrieve the message header +// pMsgNum: The absolute message number +// +// Return value: The message's index, or -1 on error. +function absMsgNumToIdxWithMsgbaseObj(pMsgbase, pMsgNum) +{ + if ((pMsgbase == null) || (typeof(pMsgbase) != "object")) + return -1; + if (!pMsgbase.is_open) + return -1; + + if (typeof(pMsgNum) != "number") + return -1; + + var messageIdx = 0; + // If pMsgNum is the 'last number' special value, then use the last message + // number in the messagebase. + if (msgNumIsLatestMsgSpecialVal(pMsgNum)) + messageIdx = pMsgbase.total_msgs - 1; // Or this.NumMessages() - 1 but can't because this isn't a class member function + else + { + var msgHdr = pMsgbase.get_msg_index(false, pMsgNum, false); + if ((msgHdr == null) && gCmdLineArgVals.verboselogging) + { + writeToSysAndNodeLog("Message area " + pMsgbase.cfg.code + ": Tried to get message header for absolute message number " + + pMsgNum + " but got a null header object."); + } + messageIdx = (msgHdr != null ? msgHdr.offset : -1); + } + return messageIdx; +} +// Tries to look up a message index (offset) with a given message number. +// On error, returns -1. +// +// Parameters: +// pSubCodeOrMsgbase: The internal sub-board code of the messagebase to check, or +// an already-open messagebase object +// pMsgNum: The absolute message number +// +// Return value: The message's index, or -1 on error. +function absMsgNumToIdx(pSubCodeOrMsgbase, pMsgNum) +{ + var typename = typeof(pSubCodeOrMsgbase); + if (typename === "string") + { + var msgOffset = -1; + var msgbase = new MsgBase(pSubCodeOrMsgbase); + if (msgbase.open()) + { + msgOffset = absMsgNumToIdxWithMsgbaseObj(msgbase, pMsgNum); + msgbase.close(); + } + return msgOffset; + } + else if (typename === "object") + return absMsgNumToIdxWithMsgbaseObj(msgbase, pMsgNum); +} + +// Takes a message index and returns the message's absolute message number. +// +// Parameters: +// pMsgbase: The messagebase object from which to retrieve the message header +// pMsgIdx: The message index +// +// Return value: The absolue message number, or -1 on error. +function idxToAbsMsgNum(pMsgbase, pMsgIdx) +{ + if ((pMsgbase == null) || (typeof(pMsgbase) != "object")) + return -1; + if (!pMsgbase.is_open) + return -1; + + var msgHdr = pMsgbase.get_msg_index(true, pMsgIdx, false); + if ((msgHdr == null) && gCmdLineArgVals.verboselogging) + { + writeToSysAndNodeLog("Tried to get message header for message offset " + + pMsgIdx + " but got a null header object."); + } + return (msgHdr != null ? msgHdr.number : -1); +} + // Returns whether or not a message is to the current user (either the current // logged-in user or the user specified by the userNum command-line argument) // and is not deleted. @@ -17784,13 +18204,67 @@ function msgIsToUserByNum(pMsgHdr, pUserNum) } // Returns whether or not a message header is to the current logged-in user by name, alias, or handle -function msgIsToCurrentUserByName(pMsgHdr) +function msgIsToCurrentUserByName(pMsgHdrOrIdx) { - if (typeof(pMsgHdr) !== "object" || !pMsgHdr.hasOwnProperty("to")) + if (typeof(pMsgHdrOrIdx) !== "object" || !pMsgHdrOrIdx.hasOwnProperty("to")) + return false; + // Return false if the message is marked as deleted and the user can't read deleted messages + if (((pMsgHdrOrIdx.attr & MSG_DELETE) == MSG_DELETE) && !canViewDeletedMsgs()) return false; - var msgToUpper = pMsgHdr.to.toUpperCase(); - return (msgToUpper == user.name.toUpperCase() || msgToUpper == user.alias.toUpperCase() || msgToUpper == user.handle.toUpperCase()); + return userHandleAliasNameMatch(pMsgHdrOrIdx.to); +} + +// Checks to see if there are any unread messages to the current +// logged-in user in a sub-board +// +// Parameters: +// pMsgbase: An opened MessageBase object for the sub-board +// pSubCode: The internal code of the sub-board of the opened messagebase +// +// Return value: Boolean - Whether or not there are any unread messages to the current logged-in user +function anyUnreadMsgsToUserWithMsgbase(pMsgbase, pSubCode) +{ + if (typeof(pMsgbase) !== "object") + return false; + + var unreadMsgsToUserFound = false; + if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number" && !msgNumIsLatestMsgSpecialVal(msg_area.sub[pSubCode].scan_ptr)) + { + var startMsgIdx = absMsgNumToIdxWithMsgbaseObj(pMsgbase, msg_area.sub[pSubCode].scan_ptr); + var endMsgIdx = absMsgNumToIdxWithMsgbaseObj(pMsgbase, pMsgbase.last_msg) + 1; + if (endMsgIdx == 0) // Not valid + endMsgIdx = pMsgbase.total_msgs; + // Check for any messages to the user between the start & end indexes + for (var i = startMsgIdx; i < endMsgIdx; ++i) + { + var msgHeader = pMsgbase.get_msg_index(true, i, false); + if (msgHeader != null && (msgHeader.attr & MSG_READ) == 0 && msgIsToCurrentUserByName(msgHeader)) + { + unreadMsgsToUserFound = true; + break; + } + } + } + return unreadMsgsToUserFound; +} +// Checks to see if there are any unread messages to the current +// logged-in user in a sub-board +// +// Parameters: +// pSuBCode: The internal code of the sub-board to check +// +// Return value: Boolean - Whether or not there are any unread messages to the current logged-in user +function anyUnreadMsgsToUser(pSubCode) +{ + var unreadMsgsToUserFound = false; + var msgbase = new MsgBase(pSubCode); + if (msgbase.open()) + { + unreadMsgsToUserFound = anyUnreadMsgsToUserWithMsgbase(msgbase, pSubCode); + msgbase.close(); + } + return unreadMsgsToUserFound; } // Returns whether or not a message is from the current user (either the current @@ -18301,22 +18775,6 @@ function subBoardGrpAndName(pSubBoardCode) return subBoardGrpAndName; } -// Returns whether a given string matches the current user's name, handle, or alias. -// Does a case-insensitive match. -// -// Parameters: -// pStr: The string to match against the user's name/handle/alias -// -// Return value: Boolean - Whether or not the string matches the current user's name, -// handle, or alias -function userNameHandleAliasMatch(pStr) -{ - if (typeof(pStr) != "string") - return false; - var strUpper = pStr.toUpperCase(); - return ((strUpper == user.name.toUpperCase()) || (strUpper == user.handle.toUpperCase()) || (strUpper == user.alias.toUpperCase())); -} - // Writes a log message to the system log (using LOG_INFO log level) and to the // node log. This will prepend the text "Digital Distortion Message Reader (" // + user.alias + "): " to the log message. @@ -18904,24 +19362,25 @@ function getBogusMsgHdr(pSubject) } // Returns whether a message is readable to the user, based on its -// header and the sub-board code. +// header and the sub-board code. This also checks pMsgHdrOrIdx for +// null; if it's null, this function will return false. // // Parameters: -// pMsgHdr: The header object for the message +// pMsgHdrOrIdx: The header or index object for the message // pSubBoardCode: The internal code for the sub-board the message is in // // Return value: Boolean - Whether or not the message is readable for the user -function isReadableMsgHdr(pMsgHdr, pSubBoardCode) +function isReadableMsgHdr(pMsgHdrOrIdx, pSubBoardCode) { - if (pMsgHdr === null) + if (pMsgHdrOrIdx === null) return false; // Let the sysop see unvalidated messages and private messages but not other users. if (!user.is_sysop) { if (pSubBoardCode != "mail") { - if ((msg_area.sub[pSubBoardCode].is_moderated && ((pMsgHdr.attr & MSG_VALIDATED) == 0)) || - (((pMsgHdr.attr & MSG_PRIVATE) == MSG_PRIVATE) && !userHandleAliasNameMatch(pMsgHdr.to))) + if ((msg_area.sub[pSubBoardCode].is_moderated && ((pMsgHdrOrIdx.attr & MSG_VALIDATED) == 0)) || + (((pMsgHdrOrIdx.attr & MSG_PRIVATE) == MSG_PRIVATE) && !userHandleAliasNameMatch(pMsgHdrOrIdx.to))) { return false; } @@ -18929,18 +19388,18 @@ function isReadableMsgHdr(pMsgHdr, pSubBoardCode) } // If the message is deleted, determine whether it should be viewable, based // on the system settings. - if (((pMsgHdr.attr & MSG_DELETE) == MSG_DELETE) && !canViewDeletedMsgs()) + if (((pMsgHdrOrIdx.attr & MSG_DELETE) == MSG_DELETE) && !canViewDeletedMsgs()) return false; // The message voting and poll variables were added in sbbsdefs.js for // Synchronet 3.17. Make sure they're defined before referencing them. if (typeof(MSG_UPVOTE) != "undefined") { - if ((pMsgHdr.attr & MSG_UPVOTE) == MSG_UPVOTE) + if ((pMsgHdrOrIdx.attr & MSG_UPVOTE) == MSG_UPVOTE) return false; } if (typeof(MSG_DOWNVOTE) != "undefined") { - if ((pMsgHdr.attr & MSG_DOWNVOTE) == MSG_DOWNVOTE) + if ((pMsgHdrOrIdx.attr & MSG_DOWNVOTE) == MSG_DOWNVOTE) return false; } // Don't include polls as being unreadable messages - They just need to have @@ -18948,13 +19407,59 @@ function isReadableMsgHdr(pMsgHdr, pSubBoardCode) /* if (typeof(MSG_POLL) != "undefined") { - if ((pMsgHdr.attr & MSG_POLL) == MSG_POLL) + if ((pMsgHdrOrIdx.attr & MSG_POLL) == MSG_POLL) return false; } */ return true; } +// Returns the header of the last readable message in a messagbase. If none is found, +// this will return null. +// +// Parameters: +// pMsgbase: An open MessageBase object +// pSubBoardCode: The internal code of the messagebase sub-board +// +// Return value: The header of the last readable message in the messagebase. If none is +// found, this will be null. +function getLastReadableMsgHdrInMsgbase(pMsgbase, pSubBoardCode) +{ + var hdrOfLastReadableMsg = null; + if (pMsgbase.is_open) + { + var numMsgs = pMsgbase.total_msgs; + for (var i = numMsgs - 1; i >= 0; --i) + { + if (isReadableMsgHdr(pMsgbase.get_msg_index(true, i, false), pSubBoardCode)) + { + hdrOfLastReadableMsg = pMsgbase.get_msg_header(true, i, false, false); + break; + } + } + } + return hdrOfLastReadableMsg; +} +// Returns the header of the last readable message in a messagbase (temporarily opens +// the sub-board). If none is found, this will return null. +// +// Parameters: +// pSubBoardCode: The internal code of the messagebase sub-board +// +// Return value: The header of the last readable message in the messagebase. If none is +// found, this will be null. +function getLastReadableMsgHdrInSubBoard(pSubBoardCode) +{ + var msgHdr = null; + var msgbase = new MsgBase(pSubBoardCode); + if (msgbase.open()) + { + msgHdr = getLastReadableMsgHdrInMsgbase(msgbase, pSubBoardCode); + msgbase.close(); + } + return msgHdr; +} + // Returns the number of readable messages in a sub-board. // // Parameters: @@ -18981,13 +19486,9 @@ function numReadableMsgs(pMsgbase, pSubBoardCode) } else { - var msgHeader; for (var i = 0; i < pMsgbase.total_msgs; ++i) { - msgHeader = msgBase.get_msg_header(true, i, false); - if (msgHeader == null) - continue; - else if (isReadableMsgHdr(msgHeader, pSubBoardCode)) + if (isReadableMsgHdr(msgBase.get_msg_index(true, i, false), pSubBoardCode)) ++numMsgs; } } @@ -19035,8 +19536,7 @@ function toggleVoteMsgsDeleted(pMsgbase, pMsgNum, pMsgID, pDoDelete, pIsEmailSub continue; // If this header is a vote header and its thread_back or reply_id matches the given message, // then we can delete this message. - var isVoteMsg = (((msgHdrs[msgHdrsProp].attr & MSG_VOTE) == MSG_VOTE) || ((msgHdrs[msgHdrsProp].attr & MSG_UPVOTE) == MSG_UPVOTE) || ((msgHdrs[msgHdrsProp].attr & MSG_DOWNVOTE) == MSG_DOWNVOTE)); - if (isVoteMsg && (msgHdrs[msgHdrsProp].thread_back == pMsgNum) || (msgHdrs[msgHdrsProp].reply_id == pMsgID)) + if (isVoteHdr(msgHdrs[msgHdrsProp]) && (msgHdrs[msgHdrsProp].thread_back == pMsgNum) || (msgHdrs[msgHdrsProp].reply_id == pMsgID)) { ++retObj.numVoteMsgs; var msgWasAffected = false; @@ -19066,6 +19566,34 @@ function toggleVoteMsgsDeleted(pMsgbase, pMsgNum, pMsgID, pDoDelete, pIsEmailSub return retObj; } +// Returns whether the user's scan_ptr for a sub-board is 4294967295 +// (0xffffffff, or ~0). That is a special value for the user's scan_ptr +// meaning it should point to the latest message in the messagebase. +// +// Parameters: +// pSubCode: The internal code of a sub-board +// +// Return value: Whether or not the user's scan_ptr for the sub-board is that special value +function subBoardScanPtrIsLatestMsgSpecialVal(pSubCode) +{ + if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number") + return msgNumIsLatestMsgSpecialVal(msg_area.sub[pSubCode].scan_ptr); + else + return false; +} +// Returns whether a message number is 4294967295 (0xffffffff, or ~0). That is +// a special value for a message number meaning it should point to the latest +// message in the messagebase. +// +// Parameters: +// pMsgNum: A message number +// +// Return value: Whether or not the given message number is the special value +function msgNumIsLatestMsgSpecialVal(pMsgNum) +{ + return (pMsgNum == 0xffffffff); +} + ///////////////////////////////////////////////////////////////////////// // Debug helper & error output functions @@ -19966,8 +20494,8 @@ function strWithToUserColor(pStr, pToUserColor) } // Gets the value of the user's current scan_ptr in a sub-board, or if it's -// 0xffffffff, returns the message number of the last readable message in -// the sub-board (this is the message number, not the index). +// the 'last message' special value, returns the message number of the last +// readable message in the sub-board (this is the message number, not the index). // // Parameters: // pSubCode: A sub-board internal code @@ -19977,45 +20505,21 @@ function strWithToUserColor(pStr, pToUserColor) function GetScanPtrOrLastMsgNum(pSubCode) { var msgNumToReturn = 0; - // Check for 4294967295 (0xffffffff, or ~0): That is a special value - // for the user's scan_ptr meaning it should point to the latest message - // in the messagebase. - if (msg_area.sub[pSubCode].scan_ptr != 0xffffffff) + // If the user's scan_ptr for the sub-board isn't the 'last message' + // special value, then use it; otherwise, use the latest readable + // message number. + if (!subBoardScanPtrIsLatestMsgSpecialVal(pSubCode)) msgNumToReturn = msg_area.sub[pSubCode].scan_ptr; else { - var lastNonDeletedMsgHdr = GetLastNonDeletedMsgHdr(pSubCode); - if (lastNonDeletedMsgHdr != null) - msgNumToReturn = lastNonDeletedMsgHdr.number; + var lastReadableMsgHdr = getLastReadableMsgHdrInSubBoard(pSubCode); + if (lastReadableMsgHdr != null) + msgNumToReturn = lastReadableMsgHdr.number; } return msgNumToReturn; } -// Returns the last non-deleted message header in a sub-board, if available -// -// Parameters: -// pSubCode: A sub-board internal code -// -// Return value: The header of the last non-deleted message in the sub-board, or null if there is none -function GetLastNonDeletedMsgHdr(pSubCode) -{ - var lastReadableMsgHdr = null; - var msgbase = new MsgBase(pSubCode); - if (msgbase.open()) - { - var numMsgs = msgbase.total_msgs; - for (var msgIdx = numMsgs - 1; msgIdx >= 0 && lastReadableMsgHdr == null; --msgIdx) - { - var msgHdr = msgbase.get_msg_header(true, msgIdx); - if ((msgHdr != null) && ((msgHdr.attr & MSG_DELETE) == 0)) - lastReadableMsgHdr = msgHdr; - } - msgbase.close(); - } - return lastReadableMsgHdr; -} - // Returns whether a message header has one of the attachment flags // enabled (for Synchtonet 3.17 or newer). // @@ -20382,6 +20886,55 @@ function canViewDeletedMsgs() return (usersVDM || (user.is_sysop && sysopVDM)); } +// Returns whether or not a message header is a vote header +// +// Parameters: +// pMsgHdrOrIndex: A message header or index object +// +// Return value: Boolean - Whether or not the header is a vote header +function isVoteHdr(pMsgHdrOrIndex) +{ + if (typeof(pMsgHdrOrIndex) !== "object" || !pMsgHdrOrIndex.hasOwnProperty("attr")) + return false; + return (((pMsgHdrOrIndex.attr & MSG_VOTE) == MSG_VOTE) || ((pMsgHdrOrIndex.attr & MSG_UPVOTE) == MSG_UPVOTE) || ((pMsgHdrOrIndex.attr & MSG_DOWNVOTE) == MSG_DOWNVOTE)); +} + +// Updates scan_ptr and/or last_read for a sub-board +// +// Parameters: +// pSubCode: The internal code for the sub-board being read +// pMsgHdr: A message header or index object (the message number will be used) +// pDoingMsgScan: Boolean - Whether or not a scan is being done +function updateScanPtrAndOrLastRead(pSubCode, pMsgHdr, pDoingMsgScan) +{ + // If not reading personal email, then update the scan & last read message pointers. + if (pSubCode != "mail") + { + if (pDoingMsgScan) + { + if (typeof(msg_area.sub[pSubCode].scan_ptr) === "number") + { + if (!msgNumIsLatestMsgSpecialVal(msg_area.sub[pSubCode].scan_ptr) && msg_area.sub[pSubCode].scan_ptr < pMsgHdr.number) + msg_area.sub[pSubCode].scan_ptr = pMsgHdr.number; + } + else + msg_area.sub[pSubCode].scan_ptr = pMsgHdr.number; + //if (pMsgHdr.number > GetScanPtrOrLastMsgNum(pSubCode)) + // msg_area.sub[pSubCode].scan_ptr = pMsgHdr.number; + + if (pMsgHdr.number > msg_area.sub[pSubCode].last_read) + msg_area.sub[pSubCode].last_read = pMsgHdr.number; + } + else + { + msg_area.sub[pSubCode].last_read = pMsgHdr.number; + //if (pMsgHdr.number > msg_area.sub[pSubCode].last_read) + // msg_area.sub[pSubCode].last_read = pMsgHdr.number; + } + } +} + + /////////////////////////////////////////////////////////////////////////////////// // ChoiceScrollbox stuff (this was copied from SlyEdit_Misc.js; maybe there's a better way to do this) diff --git a/xtrn/DDMsgReader/DefaultTheme.cfg b/xtrn/DDMsgReader/DefaultTheme.cfg index b699158388..4874513b0b 100644 --- a/xtrn/DDMsgReader/DefaultTheme.cfg +++ b/xtrn/DDMsgReader/DefaultTheme.cfg @@ -277,3 +277,6 @@ hdrLineValueColor=nbh ; Selected message check mark color selectedMsgMarkColor=wh + +; Unread persomal email/messages message mark color +unreadMsgMarkColor=whi diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt index 210e643819..f129ab5933 100644 --- a/xtrn/DDMsgReader/readme.txt +++ b/xtrn/DDMsgReader/readme.txt @@ -1,6 +1,6 @@ Digital Distortion Message Reader - Version 1.79 - Release date: 2023-09-20 + Version 1.80 + Release date: 2023-10-10 by @@ -602,17 +602,17 @@ Main configuration file (DDMsgReader.cfg) ----------------------------------------- Setting Description ------- ----------- -interfaceStyle String: The user interface to use. Valid values - are Traditional (traditional user interface - with user prompted for input at the end of each - screenful) and Lightbar (use the lightbar user - interface). - -reverseListOrder true/false: Whether or not to display the list - in reverse order, or the string "ask" to ask the user - whether they want the list displayed in reverse. If - the list is displayed in reverse, that has the effect - of listing in descending order by date & time. +listInterfaceStyle String: The user interface to use for message + lists. Valid values are Traditional (non- + lightbar user interface with user prompted for + input at the end of each screenful) or Lightbar + (use the lightbar user interface). + +reverseListOrder Default for the user setting for whether or + not to display message lists in reverse order. + Valid values are true or false. When a message + list is displayed in reverse, it will be listed + in descending order by date & time. readerInterfaceStyle The user interface style to use for the message reader. Valid values are @@ -754,6 +754,16 @@ saveAllHdrsWhenSavingMsgToBBSPC For the sysop, whether to save all message PC. This could be a boolean (true/false) or the string "ask" to prompt every time +useIndexedModeForNewscan Default for the user setting for whether + or not to use indexed mode for doing a + newscan. For newscan only (not new + to-you). If enabled, a newscan will appear + as a menu listing the various sub-boards + and how many total messages and number of + new messages they have. This is the + default for a user setting; users can + toggle this for themselves as they like. + themeFilename The name of the configuration file to use for colors & string settings @@ -1168,6 +1178,10 @@ selectedMsgMarkColor The color to use for the checkmark for selected messages (used in the message list) +unreadMsgMarkColor The color to use for the 'unread' message + marker character in the message list + (appears as a U) + 7. Indexed reader mode ====================== "Indexed" reader mode is a new mode that was added to DDMsgReader v1.70. This @@ -1189,7 +1203,7 @@ This is an example of the sub-board menu that appears in indexed mode - And from here, the user can choose a sub-board to read: Description Total New Last Post -───── AgoraNet ──────────────────────────────────────────────────────────────── +��������������������������������������������� AgoraNet ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ AGN GEN - General Chat 1004 0 2023-04-02 AGN BBS - BBS Discussion 1000 0 2023-01-17 NEW AGN ART - Art/Demo Scene 603 1 2023-04-02 @@ -1198,7 +1212,7 @@ NEW AGN ART - Art/Demo Scene 603 1 2023-04-02 AGN L46 - League Scores & Recons 1000 0 2016-09-10 NEW AGN TST - Testing Setups 2086 10 2023-04-03 AGN SYS - Sysops Only 1000 0 2023-01-19 -───── FIDO - FidoNet ────────────────────────────────────────────────────────── +��������������������������������������������� FIDO - FidoNet ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ NEW BBS CARNIVAL - BBS Software Chatter 660 5 2023-04-04 BBS INTERNET - DOS/Win/OS2/Unix Internet BBS Applicatio 18 0 2023-03-04 CHWARE - Cheepware Support/Discussion 111 0 2023-03-16 @@ -1232,27 +1246,27 @@ added for a user will be preserved (DDMsgReader does a bitwise 'or'). A quick-validation set in CFG is a set that includes a security level, flag sets, exemptions, restrictions, and additional credits. For example: -░░░░╔══════════════════════════════════════════════════════════════════════╗░░░░ -░░░░║ System Conf╔══════════════════════════[ >]╗ ║░░░░ -░░░░╔════════════════════════════════════║ Quick-Validation Set 0 ║═╗░░░░ -░░░░║ ╔══════════════╠═══════════════════════════════╣ ║░░░░ -░░░░╠═════════════════════║ Quick-Validat║ │Level 5 ║═╣░░░░ -░░░░║ │System Password ╠══════════════║ │Flag Set #1 ║ ║░░░░ -░░░░║ │Prompt for System P║ │0 SL: 5 F║ │Flag Set #2 ║ ║░░░░ -░░░░║ │Allow Sysop Access ║ │1 SL: 10 F║ │Flag Set #3 ║ ║░░░░ -░░░░║ │Allow Login by Real║ │2 SL: 20 F║ │Flag Set #4 ║ ║░░░░ -░░░░║ │Allow Login by User║ │3 SL: 30 F║ │Exemptions ║ ║░░░░ -░░░░║ │Users Can Choose Pa║ │4 SL: 40 F║ │Restrictions ║ ║░░░░ -░░░░║ │Always Prompt for P║ │5 SL: 50 F║ │Extend Expiration 0 days ║ ║░░░░ -░░░░║ │Display/Log Passwor║ │6 SL: 60 F║ │Additional Credits 0 ║ ║░░░░ -░░░░║ │Days to Preserve De║ │7 SL: 70 F╚═══════════════════════════════╝ ║░░░░ -░░░░║ │Maximum Days of Use║ │8 SL: 80 F1: ║ ║░░░░ -░░░░║ │Open to New Users ║ │9 SL: 90 F1: ║ ║░░░░ -░░░░║ │User Expires When O╚═════════════════════════╝ ║░░░░ -░░░░║ │Security Level Values... ║░░░░ -░░░░║ │Expired Account Values... ║░░░░ -░░░░║ │Quick-Validation Values... ║░░░░ -░░░░╚══════════════════════════════════════════════════════════════════════╝ +������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ +��������������������������������������������� System Conf���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[ >]��������� ��������������������������������������������� +������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Quick-Validation Set 0 ��������������������������������������������������������������� +��������������������������������������������� ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ��������������������������������������������� +��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� Quick-Validat��������� ���������Level 5 ��������������������������������������������������������������� +��������������������������������������������� ���������System Password ������������������������������������������������������������������������������������������������������������������������������������������������ ���������Flag Set #1 ��������� ��������������������������������������������� +��������������������������������������������� ���������Prompt for System P��������� ���������0 SL: 5 F��������� ���������Flag Set #2 ��������� ��������������������������������������������� +��������������������������������������������� ���������Allow Sysop Access ��������� ���������1 SL: 10 F��������� ���������Flag Set #3 ��������� ��������������������������������������������� +��������������������������������������������� ���������Allow Login by Real��������� ���������2 SL: 20 F��������� ���������Flag Set #4 ��������� ��������������������������������������������� +��������������������������������������������� ���������Allow Login by User��������� ���������3 SL: 30 F��������� ���������Exemptions ��������� ��������������������������������������������� +��������������������������������������������� ���������Users Can Choose Pa��������� ���������4 SL: 40 F��������� ���������Restrictions ��������� ��������������������������������������������� +��������������������������������������������� ���������Always Prompt for P��������� ���������5 SL: 50 F��������� ���������Extend Expiration 0 days ��������� ��������������������������������������������� +��������������������������������������������� ���������Display/Log Passwor��������� ���������6 SL: 60 F��������� ���������Additional Credits 0 ��������� ��������������������������������������������� +��������������������������������������������� ���������Days to Preserve De��������� ���������7 SL: 70 F��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ��������������������������������������������� +��������������������������������������������� ���������Maximum Days of Use��������� ���������8 SL: 80 F1: ��������� ��������������������������������������������� +��������������������������������������������� ���������Open to New Users ��������� ���������9 SL: 90 F1: ��������� ��������������������������������������������� +��������������������������������������������� ���������User Expires When O��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ��������������������������������������������� +��������������������������������������������� ���������Security Level Values... ��������������������������������������������� +��������������������������������������������� ���������Expired Account Values... ��������������������������������������������� +��������������������������������������������� ���������Quick-Validation Values... ��������������������������������������������� +������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 9. Drop file for replying to messages with Synchronet message editors ===================================================================== diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt index 4d0c65fc72..2508e05d97 100644 --- a/xtrn/DDMsgReader/revision_history.txt +++ b/xtrn/DDMsgReader/revision_history.txt @@ -5,6 +5,25 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +1.80 2023-10-10 Improved speed of new-to-you scans, and to an extent + (hopefully) overall speed + Bug fix: Setting reverseListOrder to "ask" in the .cfg + file works properly again. + Bug fix: When listing messages in reverse order, the + selected menu index (for lightbar mode) is now correct. + Bug fix: If the user is allowed to read deleted messages, + then allow the left & right arrow keys to to the next or + previous message if it's deleted. + Small fixes for indexed scanning mode. + New: For personal email, unread emails will have an + 'unread' message indicator in the message list as a U + between the message number and the 'from' name. + New user setting: "Quit from reader to message list": + When enabled, quitting from reader mode goes to the + message list instead of exiting out of DDMsgReader fully. + New user setting: Enter/selection from indexed mode menu + shows message list (instead of going into reader mode) + New user setting: List messages in reverse order 1.79 2023-09-20 Fixed poll voting for single-answer polls 1.78 2023-08-30 Bug fix for going to a specific message in the message list (especially for lightbar mode) -- GitLab