diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg b/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg new file mode 100644 index 0000000000000000000000000000000000000000..206bf56598396128d2339969efeded52975b9836 --- /dev/null +++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.cfg @@ -0,0 +1,33 @@ +[BEHAVIOR] +useLightbarInterface=true + +[COLORS] +; Area number +areaNum=nwh +; Description +desc=nc +; Number of items +numItems=bh +; List header +header=nyh +; For the directory list header that has "Directories for" the group and +; has the page number +fileAreaHdr=ng +; Mark character for areas that are currently selected +areaMark=gh +; Highlighted colors (for lightbar mode) +bkgHighlight=4 +areaNumHighlight=wh +descHighlight=c +numItemsHighlight=wh + +; Colors for the lightbar help line text: +; Background +lightbarHelpLineBkg=7 +; The color for general text in the lightbar help line +lightbarHelpLineGeneral=b +; The color for the hotkeys in the lightbar help line +lightbarHelpLineHotkey=r +; The color for the ) separating the hotkeys from the general text in the +; lightbar help line +lightbarHelpLineParen=m \ No newline at end of file diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.js b/xtrn/DDAreaChoosers/DDFileAreaChooser.js new file mode 100644 index 0000000000000000000000000000000000000000..49560abba911f42fdd6d30d4437b3d8e2672ed7f --- /dev/null +++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.js @@ -0,0 +1,1782 @@ +/* This is a script that lets the user choose a file area, + * with either a lightbar or traditional user interface. + * + * Date User Version Description + * 2010-03-06 Eric Oulashin 0.90 Started (based on DDFileAreaChooser.js) + * 2010-03-08 Eric Oulashin Continued work + * 2010-03-09 Eric Oulashin Continued work + * 2010-03-11 Eric Oulashin Continued work + * 2010-03-12 Eric Oulashin Fixed a bug related to choosing a file area + * by typing a number, in lightbar mode. + * 2010-03-13 Eric Oulashin 1.00 Fixed a bug where it was incorrectly displaying + * the "currently selected" marker next to some + * directories in some file libraries. + * Updated to read settings from a configuration + * file. + * 2012-10-06 Eric Oulashin 1.02 For lightbar mode, updated to display the + * page number in the header at the top (in + * addition to the total number of pages, + * which it was already displaying). + * 2012-11-30 Eric Oulashin 1.03 Bug fix: After leaving the help screen + * from the directory list, the top line + * is now correctly written with the page + * information as "Page # of #". + * 2013-05-04 Eric Oulashin 1.04 Updated to dynamically adjust the length + * of the # files column based on the + * greatest number of files of all + * file dirs within a file library so + * that the formatting still looks good. + * 2013-05-10 Eric Oulashin 1.05 Bug fix: In + * DDFileAreaChooser_listDirsInFileLib_Traditional, + * updated a couple lines of code to use + * libIndex rather than pLibIndex, since + * pLibIndex can sometimes be invalid. + * Also updated that function to prepare + * the associative array of directory + * information for the file library. + * 2014-09-14 Eric Oulashin 1.06 Bug fix: Updated the highlight (lightbar) + * format string in the + * DDFileAreaChooser_buildFileDirPrintfInfoForLib() + * function to include a normal attribute at + * the end to avoid color issues when clearing + * the screen, etc. Bug reported by Psi-Jack. + * 2014-12-22 Eric Oulashin 1.07 Updated the verison number to match the + * message area chooser, which had a couple of + * bugs fixed. + * 2015-04-19 Eric Oulashin 1.08 Added color settings for the lightbar help text + * at the bottom of the screen. Also, added the + * ability to use the PageUp & PageDown keys instead + * of P and N in the lightbar lists. + */ + +/* Command-line arguments: + 1 (argv[0]): Boolean - Whether or not to run the area chooser (if false, + then this file will just provide the DDFileAreaChooser class). +*/ + +load("sbbsdefs.js"); + +// This script requires Synchronet version 3.14 or higher. +// Exit if the Synchronet version is below the minimum. +if (system.version_num < 31400) +{ + var message = "\1n\1h\1y\1i* Warning:\1n\1h\1w Digital Distortion Message Lister " + + "requires version \1g3.14\1w or\r\n" + + "higher of Synchronet. This BBS is using version \1g" + system.version + + "\1w. Please notify the sysop."; + console.crlf(); + console.print(message); + console.crlf(); + console.pause(); + exit(); +} + +// Version & date variables +var DD_FILE_AREA_CHOOSER_VERSION = "1.08"; +var DD_FILE_AREA_CHOOSER_VER_DATE = "2015-04-19"; + +// Keyboard input key codes +var CTRL_M = "\x0d"; +var KEY_ENTER = CTRL_M; +var KEY_ESC = ascii(27); + +// PageUp & PageDown keys - Not real key codes, but codes defined +// to be used & recognized in this script +var KEY_PAGE_UP = "\1PgUp"; +var KEY_PAGE_DOWN = "\1PgDn"; + +// Key codes for display +var UP_ARROW = ascii(24); +var DOWN_ARROW = ascii(25); + +// Determine whether or not to execute the message listing code, based +// on the first program argument (a boolean). +var executeThisScript = true; +if (typeof(argv[0]) != "undefined") + executeThisScript = argv[0]; + +// If executeThisScript is true, then create a DDFileAreaChooser object and use +// it to let the user choose a message area. +if (executeThisScript) +{ + var fileAreaChooser = new DDFileAreaChooser(); + fileAreaChooser.SelectFileArea(); +} + +// End of script execution + +/////////////////////////////////////////////////////////////////////////////////// +// DDFileAreaChooser class stuff + +function DDFileAreaChooser() +{ + // Colors + this.colors = new Object(); + this.colors.areaNum = "\1n\1w\1h"; + this.colors.desc = "\1n\1c"; + this.colors.numItems = "\1b\1h"; + this.colors.header = "\1n\1y\1h"; + this.colors.fileAreaHdr = "\1n\1g"; + this.colors.areaMark = "\1g\1h"; + // Highlighted colors (for lightbar mode) + this.colors.bkgHighlight = "\1" + "4"; // Blue background + this.colors.areaNumHighlight = "\1w\1h"; + this.colors.descHighlight = "\1c"; + this.colors.numItemsHighlight = "\1w\1h"; + // Lightbar help line colors + this.colors.lightbarHelpLineBkg = "\1" + "7"; + this.colors.lightbarHelpLineGeneral = "\1b"; + this.colors.lightbarHelpLineHotkey = "\1r"; + this.colors.lightbarHelpLineParen = "\1m"; + + // useLightbarInterface specifies whether or not to use the lightbar + // interface. The lightbar interface will still only be used if the + // user's terminal supports ANSI. + this.useLightbarInterface = true; + + // Store whether or not bbs.curlib and bbs.curdir are valid (they might not + // be for a new user). + this.curlibValid = ((bbs.curlib != null) && (typeof(bbs.curlib) == "number")); + this.curdirValid = ((bbs.curdir != null) && (typeof(bbs.curdir) == "number")); + + this.areaNumLen = 4; + this.descFieldLen = 67; // Description field length + + // Set the function pointers for the object + this.ReadConfigFile = DDFileAreaChooser_ReadConfigFile; + this.SelectFileArea = DDFileAreaChooser_selectFileArea; + this.SelectFileArea_Traditional = DDFileAreaChooser_selectFileArea_Traditional; + this.SelectDirWithinFileLib_Traditional = DDFileAreaChooser_selectDirWithinFileLib_Traditional; + this.ListFileLibs = DDFileAreaChooser_listFileLibs_Traditional; + this.ListDirsInFileLib = DDFileAreaChooser_listDirsInFileLib_Traditional; + this.WriteLibListHdrLine = DDFileAreaChooser_writeLibListTopHdrLine; + this.WriteDirListHdr1Line = DDFileAreaChooser_writeDirListHdr1Line; + // Lightbar-specific functions + this.SelectFileArea_Lightbar = DDFileAreaChooser_selectFileArea_Lightbar; + this.SelectDirWithinFileLib_Lightbar = DDFileAreaChooser_selectDirWithinFileLib_Lightbar; + this.WriteKeyHelpLine = DDFileAreaChooser_writeKeyHelpLine; + this.ListScreenfulOfFileLibs = DDFileAreaChooser_listScreenfulOfFileLibs; + this.WriteFileLibLine = DDFileAreaChooser_writeFileLibLine; + this.WriteFileLibDirLine = DDFileAreaChooser_writeFileLibDirLine; + this.updatePageNumInHeader = DDFileAreaChooser_updatePageNumInHeader; + this.ListScreenfulOfDirs = DDMsgAreaChooser_listScreenfulOfFileDirs; + // Help screen + this.ShowHelpScreen = DDFileAreaChooser_showHelpScreen; + // Misc. functions + this.NumFilesInDir = DDFileAreaChooser_NumFilesInDir; + + // Function to build the directory printf information for a file lib + this.BuildFileDirPrintfInfoForLib = DDFileAreaChooser_buildFileDirPrintfInfoForLib; + + // Read the settings from the config file. + this.ReadConfigFile(); + + // printf strings used for outputting the file libraries + this.fileLibPrintfStr = " " + this.colors.areaNum + "%4d " + + this.colors.desc + "%-" + this.descFieldLen + + "s " + this.colors.numItems + "%4d"; + this.fileLibHighlightPrintfStr = "\1n" + this.colors.bkgHighlight + " " + + this.colors.areaNumHighlight + "%4d " + + this.colors.descHighlight + "%-" + this.descFieldLen + + "s " + this.colors.numItemsHighlight + "%4d"; + this.fileLibListHdrPrintfStr = this.colors.header + " %5s %-" + + +(this.descFieldLen-2) + "s %6s"; + this.fileDirHdrPrintfStr = this.colors.header + " %5s %-" + + +(this.descFieldLen-3) + "s %-7s"; + // Lightbar mode key help line + this.lightbarKeyHelpText = "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + UP_ARROW + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + DOWN_ARROW + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "HOME" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "END" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "#" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "PgUp" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "/" + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "Dn" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "F" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "irst pg, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "L" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "ast pg, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "Q" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "uit, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "?"; + // Pad the lightbar key help text on either side to center it on the screen + // (but leave off the last character to avoid screen drawing issues) + var helpTextLen = console.strlen(this.lightbarKeyHelpText); + var helpTextStartCol = (console.screen_columns/2) - (helpTextLen/2); + this.lightbarKeyHelpText = "\1n" + this.colors.lightbarHelpLineBkg + + format("%" + +(helpTextStartCol) + "s", "") + + this.lightbarKeyHelpText + "\1n" + + this.colors.lightbarHelpLineBkg; + var numTrailingChars = console.screen_columns - (helpTextStartCol+helpTextLen) - 1; + this.lightbarKeyHelpText += format("%" + +(numTrailingChars) + "s", "") + "\1n"; + + // this.fileDirListPrintfInfo will be an array of printf strings + // for the file directories in the file libraries. The index is the + // file library index. The file directory printf information is + // created on the fly the first time the user lists directories for + // a file library. + this.fileDirListPrintfInfo = new Array(); +} + +// For the DDFileAreaChooser class: Lets the user choose a file area. +function DDFileAreaChooser_selectFileArea() +{ + if (this.useLightbarInterface && console.term_supports(USER_ANSI)) + this.SelectFileArea_Lightbar(); + else + this.SelectFileArea_Traditional(); +} + +// For the DDFileAreaChooser class: Traditional user interface for +// letting the user choose a file area +function DDFileAreaChooser_selectFileArea_Traditional() +{ + // If there are no file libraries, then don't let the user + // choose one. + if (file_area.lib_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no file libraries.\r\n\1p"); + return; + } + + // Show the file libraries & directories and let the user choose one. + var selectedLib = 0; // The user's selected file library + var selectedDir = 0; // The user's selected file directory + var continueChooseFileLib = true; + while (continueChooseFileLib) + { + // Clear the BBS command string to make sure there are no extra + // commands in there that could cause weird things to happen. + bbs.command_str = ""; + + console.clear("\1n"); + this.ListFileLibs(); + console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + +(bbs.curlib+1) + + "\1n\1c]:\1h "); + // Accept Q (quit) or a file library number + selectedLib = console.getkeys("Q", file_area.lib_list.length); + + // If the user just pressed enter (selectedLib would be blank), + // default to the current library. + if (selectedLib.toString() == "") + selectedLib = bbs.curlib + 1; + + // If the user chose to quit, then set continueChooseFileLib to + // false so we'll exit the loop. Otherwise, let the user chose + // a dir within the library. + if (selectedLib.toString() == "Q") + continueChooseFileLib = false; + else + continueChooseFileLib = !this.SelectDirWithinFileLib_Traditional(selectedLib, selectedDir); + } +} + +// For the DDFileAreaChooser class: Lets the user select a file area (directory) +// within a specified file library - Traditional user interface. +// +// Parameters: +// pLibNumber: The file library number +// pSelectedDir: The currently-selected file area +// +// Return value: Boolean - Whether or not the user chose a file area. +function DDFileAreaChooser_selectDirWithinFileLib_Traditional(pLibNumber, pSelectedDir) +{ + var userChoseAnArea = false; + + // If the file library number is valid, then + // set it and let the user choose a file directory + // within the library. + if (pLibNumber > 0) + { + // Ensure that the file directory printf information is created for + // this file library. + this.BuildFileDirPrintfInfoForLib(pLibNumber-1); + + // Set the default directory #: The current directory, or if the + // user chose a different file library, then this should be set + // to the first directory. + var defaultDir = bbs.curdir + 1; + if (pLibNumber-1 != bbs.curlib) + defaultDir = 1; + + console.clear("\1n"); + this.ListDirsInFileLib(pLibNumber - 1, defaultDir - 1); + console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + defaultDir + + "\1n\1c]: \1h"); + // Accept Q (quit) or a file directory number + var selectedDir = console.getkeys("Q", file_area.lib_list[pLibNumber - 1].dir_list.length); + + // If the user just pressed enter (selectedDir would be blank), + // default the selected directory. + if (selectedDir.toString() == "") + selectedDir = defaultDir; + + // If the user chose a directory, then set bbs.curlib & + // bbs.curdir and quit the file library loop. + if ((pLibNumber.toString() != "Q") && (selectedDir > 0)) + { + bbs.curlib = pLibNumber - 1; + bbs.curdir = selectedDir - 1; + userChoseAnArea = true; + } + } + + return userChoseAnArea; +} + +// For the DDFileAreaChooser class: Traditional user interface for listing +// the file groups +function DDFileAreaChooser_listFileLibs_Traditional() +{ + // See if bbs.curlib is valid (it might not be for brand-new users). + var curlibValid = ((bbs.curlib != null) && (typeof(bbs.curlib) != "undefined")); + + // See if bbs.curlib and bbs.curdir are valid, since we'll be testing + // with them. + var curDirLibValid = ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number")); + + // Print the list header + printf(this.fileLibListHdrPrintfStr, "Lib #", "Description", "# Dirs"); + console.crlf(); + console.print("\1n"); + // Print the information for each file library + var currentDir = false; + for (var i = 0; i < file_area.lib_list.length; ++i) + { + // Print the library information. + console.print(curDirLibValid && (i == bbs.curlib) ? this.colors.areaMark + "*" : " "); + printf(this.fileLibPrintfStr, +(i+1), + file_area.lib_list[i].description.substr(0, this.descFieldLen), + file_area.lib_list[i].dir_list.length); + console.crlf(); + } +} + +// For the DDFileAreaChooser class: Traditional user interface for listing +// the directories in a file group +// +// Parameters: +// pLibIndex: The index of the file library (0-based) +// pMarkIndex: An index of a file library to display the "current" mark +// next to. This is optional; if left off, this will default +// to the current directory. +function DDFileAreaChooser_listDirsInFileLib_Traditional(pLibIndex, pMarkIndex) +{ + // set libIndex, the library index + var libIndex = bbs.curlib; + if (typeof(pLibIndex) == "number") + libIndex = pLibIndex; + + // Set markIndex, the index of the item to highlight + var markIndex = bbs.curdir; + if (typeof(pMarkIndex) == "number") + markIndex = pMarkIndex; + + // Make sure markIndex is valid (it might not be for brand-new users). + if ((markIndex == null) || (typeof(markIndex) == "undefined")) + markIndex = 0; + + // See if bbs.curlib and bbs.curdir are valid, since we'll be testing with + // them. + var curDirLibValid = ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number")); + + // Ensure that the file directory printf information is created for + // this file library. + this.BuildFileDirPrintfInfoForLib(libIndex); + + // Print the header lines + console.print(this.colors.fileAreaHdr + "Directories of \1h" + + file_area.lib_list[libIndex].description); + console.crlf(); + printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files"); + console.crlf(); + console.print("\1n"); + // Print the file directories + var isSelectedDir = false; + for (var i = 0; i < file_area.lib_list[libIndex].dir_list.length; ++i) + { + // See if this is the currently-selected directory. + if (curDirLibValid) + isSelectedDir = ((libIndex == bbs.curlib) && (i == markIndex)); + console.print(isSelectedDir ? this.colors.areaMark + "*" : " "); + printf(this.fileDirListPrintfInfo[libIndex].printfStr, +(i+1), + file_area.lib_list[libIndex].dir_list[i].description.substr(0, this.descFieldLen), + this.fileDirListPrintfInfo[libIndex].fileCounts[i]); + console.crlf(); + } +} + +// For the DDFileAreaChooser class: Outputs the header line to appear above +// the list of file libraries. +// +// Parameters: +// pNumPages: The number of pages (a number). This is optional; if this is +// not passed, then it won't be used. +// pPageNum: The page number. This is optional; if this is not passed, +// then it won't be used. +function DDFileAreaChooser_writeLibListTopHdrLine(pNumPages, pPageNum) +{ + var descStr = "Description"; + if ((typeof(pPageNum) == "number") && (typeof(pNumPages) == "number")) + descStr += " (Page " + pPageNum + " of " + pNumPages + ")"; + else if ((typeof(pPageNum) == "number") && (typeof(pNumPages) != "number")) + descStr += " (Page " + pPageNum + ")"; + else if ((typeof(pPageNum) != "number") && (typeof(pNumPages) == "number")) + descStr += " (" + pNumPages + (pNumPages == 1 ? " page)" : " pages)"); + printf(this.fileLibListHdrPrintfStr, "Lib #", descStr, "# Dirs"); + console.cleartoeol("\1n"); +} + +// For the DDFileAreaChooser class: Outputs the first header line to appear +// above the directory list for a file library. +// +// Parameters: +// pLibIndex: The index of the file library (assumed to be valid) +// pNumPages: The number of pages (a number). This is optional; if this is +// not passed, then it won't be used. +// pPageNum: The page number. This is optional; if this is not passed, +// then it won't be used. +function DDFileAreaChooser_writeDirListHdr1Line(pLibIndex, pNumPages, pPageNum) +{ + var descLen = 40; + var descFormatStr = this.colors.fileAreaHdr + "Directories of \1h%-" + descLen + "s \1n" + + this.colors.fileAreaHdr; + if ((typeof(pPageNum) == "number") && (typeof(pNumPages) == "number")) + descFormatStr += "(Page " + pPageNum + " of " + pNumPages + ")"; + else if ((typeof(pPageNum) == "number") && (typeof(pNumPages) != "number")) + descFormatStr += "(Page " + pPageNum + ")"; + else if ((typeof(pPageNum) != "number") && (typeof(pNumPages) == "number")) + descFormatStr += "(" + pNumPages + (pNumPages == 1 ? " page)" : " pages)"); + printf(descFormatStr, file_area.lib_list[pLibIndex].description.substr(0, descLen)); + console.cleartoeol("\1n"); +} + +// Lightbar functions + +// For the DDFileAreaChooser class: Lightbar interface for letting the user +// choose a file library and group +function DDFileAreaChooser_selectFileArea_Lightbar() +{ + // If there are file libraries, then don't let the user + // choose one. + if (file_area.lib_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no file libraries.\r\n\1p"); + return; + } + + // Returns the index of the bottommost file library that can be displayed + // on the screen. + // + // Parameters: + // pTopLibIndex: The index of the topmost file library displayed on screen + // pNumItemsPerPage: The number of items per page + function getBottommostLibIndex(pTopLibIndex, pNumItemsPerPage) + { + var bottomLibIndex = pTopLibIndex + pNumItemsPerPage - 1; + // If bottomLibIndex is beyond the last index, then adjust it. + if (bottomLibIndex >= file_area.lib_list.length) + bottomLibIndex = file_area.lib_list.length - 1; + return bottomLibIndex; + } + + + // Figure out the index of the user's currently-selected file library + var selectedLibIndex = 0; + if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number")) + selectedLibIndex = bbs.curlib; + + var listStartRow = 2; // The row on the screen where the list will start + var listEndRow = console.screen_rows - 1; // Row on screen where list will end + var topFileLibIndex = 0; // The index of the message group at the top of the list + + // Figure out the index of the last message group to appear on the screen. + var numItemsPerPage = listEndRow - listStartRow + 1; + var bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + // Figure out how many pages are needed to list all the file areas. + var numPages = Math.ceil(file_area.lib_list.length / numItemsPerPage); + var pageNum = 1; + // Figure out the top index for the last page. + var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage; + + // If the highlighted row is beyond the current screen, then + // go to the appropriate page. + if (selectedLibIndex > bottomFileLibIndex) + { + var nextPageTopIndex = 0; + while (selectedLibIndex > bottomFileLibIndex) + { + nextPageTopIndex = topFileLibIndex + numItemsPerPage; + if (nextPageTopIndex < file_area.lib_list.length) + { + // Adjust topFileLibIndex and bottomFileLibIndex, and + // refresh the list on the screen. + topFileLibIndex = nextPageTopIndex; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + } + else + break; + } + + // If we didn't find the correct page for some reason, then set the + // variables to display page 1 and select the first message group. + var foundCorrectPage = ((topFileLibIndex < file_area.lib_list.length) && + (selectedLibIndex >= topFileLibIndex) && (selectedLibIndex <= bottomFileLibIndex)); + if (!foundCorrectPage) + { + topFileLibIndex = 0; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + selectedLibIndex = 0; + } + } + + // Clear the screen, write the help line and group list header, and output + // a screenful of message groups. + console.clear("\1n"); + this.WriteKeyHelpLine(); + + var curpos = new Object(); + curpos.x = 1; + curpos.y = 1; + console.gotoxy(curpos); + this.WriteLibListHdrLine(numPages, pageNum); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, false, + false); + // Start of the input loop. + var highlightScrenRow = 0; // The row on the screen for the highlighted group + var userInput = ""; // Will store a keypress from the user + var retObj = null; // To store the return value of choosing a file area + var continueChoosingFileArea = true; + while (continueChoosingFileArea) + { + // Highlight the currently-selected message group + highlightScrenRow = listStartRow + (selectedLibIndex - topFileLibIndex); + curpos.y = highlightScrenRow; + if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows)) + { + console.gotoxy(1, highlightScrenRow); + this.WriteFileLibLine(selectedLibIndex, true); + } + + // Get a key from the user (upper-case) and take action based upon it. + userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF); + switch (userInput) + { + case KEY_UP: // Move up one message group in the list + if (selectedLibIndex > 0) + { + // If the previous group index is on the previous page, then + // display the previous page. + var previousGrpIndex = selectedLibIndex - 1; + if (previousGrpIndex < topFileLibIndex) + { + // Adjust topFileLibIndex and bottomFileLibIndex, and + // refresh the list on the screen. + topFileLibIndex -= numItemsPerPage; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteFileLibLine(selectedLibIndex, false); + } + selectedLibIndex = previousGrpIndex; + } + break; + case KEY_DOWN: // Move down one message group in the list + if (selectedLibIndex < file_area.lib_list.length - 1) + { + // If the next group index is on the next page, then display + // the next page. + var nextGrpIndex = selectedLibIndex + 1; + if (nextGrpIndex > bottomFileLibIndex) + { + // Adjust topFileLibIndex and bottomFileLibIndex, and + // refresh the list on the screen. + topFileLibIndex += numItemsPerPage; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteFileLibLine(selectedLibIndex, false); + } + selectedLibIndex = nextGrpIndex; + } + break; + case KEY_HOME: // Go to the top message group on the screen + if (selectedLibIndex > topFileLibIndex) + { + // Display the current line un-highlighted, then adjust + // selectedLibIndex. + console.gotoxy(1, curpos.y); + this.WriteFileLibLine(selectedLibIndex, false); + selectedLibIndex = topFileLibIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_END: // Go to the bottom message group on the screen + if (selectedLibIndex < bottomFileLibIndex) + { + // Display the current line un-highlighted, then adjust + // selectedLibIndex. + console.gotoxy(1, curpos.y); + this.WriteFileLibLine(selectedLibIndex, false); + selectedLibIndex = bottomFileLibIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_ENTER: // Select the currently-highlighted message group + retObj = this.SelectDirWithinFileLib_Lightbar(selectedLibIndex); + // If the user chose an area, then set bbs.curlib and + // bbs.curdir, and don't continue the input loop anymore. + if (retObj.fileDirChosen) + { + bbs.curlib = selectedLibIndex; + bbs.curdir = retObj.fileLibIndex; + continueChoosingFileArea = false; + } + else + { + // An area was not chosen, so we'll have to re-draw + // the header and list of message groups. + console.gotoxy(1, 1); + this.WriteLibListHdrLine(numPages, pageNum); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + } + break; + case KEY_PAGE_DOWN: // Go to the next page + var nextPageTopIndex = topFileLibIndex + numItemsPerPage; + if (nextPageTopIndex < file_area.lib_list.length) + { + // Adjust topFileLibIndex and bottomFileLibIndex, and + // refresh the list on the screen. + topFileLibIndex = nextPageTopIndex; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, + listEndRow, false, true); + selectedLibIndex = topFileLibIndex; + } + break; + case KEY_PAGE_UP: // Go to the previous page + var prevPageTopIndex = topFileLibIndex - numItemsPerPage; + if (prevPageTopIndex >= 0) + { + // Adjust topFileLibIndex and bottomFileLibIndex, and + // refresh the list on the screen. + topFileLibIndex = prevPageTopIndex; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, + listEndRow, false, true); + selectedLibIndex = topFileLibIndex; + } + break; + case 'F': // Go to the first page + if (topFileLibIndex > 0) + { + topFileLibIndex = 0; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + selectedLibIndex = 0; + } + break; + case 'L': // Go to the last page + if (topFileLibIndex < topIndexForLastPage) + { + topFileLibIndex = topIndexForLastPage; + pageNum = calcPageNum(topFileLibIndex, numItemsPerPage); + bottomFileLibIndex = getBottommostLibIndex(topFileLibIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + selectedLibIndex = topIndexForLastPage; + } + break; + case 'Q': // Quit + continueChoosingFileArea = false; + break; + case '?': // Show help + this.ShowHelpScreen(true, true); + console.pause(); + // Refresh the screen + this.WriteKeyHelpLine(); + console.gotoxy(1, 1); + this.WriteLibListHdrLine(numPages, pageNum); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + break; + default: + // If the user entered a numeric digit, then treat it as + // the start of the message group number. + if (userInput.match(/[0-9]/)) + { + var originalCurpos = curpos; + + // Put the user's input back in the input buffer to + // be used for getting the rest of the message number. + console.ungetstr(userInput); + // Move the cursor to the bottom of the screen and + // prompt the user for the message number. + console.gotoxy(1, console.screen_rows); + console.clearline("\1n"); + console.print("\1cChoose library #: \1h"); + userInput = console.getnum(file_area.lib_list.length); + // If the user made a selection, then let them choose a + // directory from the file library. + if (userInput > 0) + { + var selectedLibIndex = userInput - 1; + var retObj = this.SelectDirWithinFileLib_Lightbar(selectedLibIndex); + // If the user chose a sub-board, then set bbs.curlib and + // bbs.curdir, and don't continue the input loop anymore. + if (retObj.fileDirChosen) + { + bbs.curlib = selectedLibIndex; + bbs.curdir = retObj.fileLibIndex; + continueChoosingFileArea = false; + } + else + { + // A sub-board was not chosen, so we'll have to re-draw + // the header and list of message groups. + console.gotoxy(1, 1); + this.WriteLibListHdrLine(numPages, pageNum); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + } + } + else + { + // The user didn't make a selection. So, we need to refresh + // the screen due to everything being moved up one line. + this.WriteKeyHelpLine(); + console.gotoxy(1, 1); + this.WriteLibListHdrLine(numPages, pageNum); + this.ListScreenfulOfFileLibs(topFileLibIndex, listStartRow, listEndRow, + false, true); + } + } + break; + } + } +} + +// For the DDFileAreaChooser class: Lightbar interface for letting the user +// choose a directory within a file library. +// +// Parameters: +// pLibIndex: The index of the message group to choose from. This is +// optional; if not specified, bbs.curlib will be used. +// pHighlightIndex: An index of a message group to highlight. This +// is optional; if left off, this will default to +// the current sub-board. +// +// Return value: An object containing the following values: +// fileDirChosen: Boolean - Whether or not a sub-board was chosen. +// fileLibIndex: Numeric - The sub-board that was chosen (if any). +// Will be -1 if none chosen. +function DDFileAreaChooser_selectDirWithinFileLib_Lightbar(pLibIndex, pHighlightIndex) +{ + // Create the return object. + var retObj = new Object(); + retObj.fileDirChosen = false; + retObj.fileLibIndex = -1; + + var libIndex = 0; + if (typeof(pLibIndex) == "number") + libIndex = pLibIndex; + else if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number")) + libIndex = bbs.curlib; + // Double-check libIndex + if (libIndex < 0) + libIndex = 0; + else if (libIndex >= file_area.lib_list.length) + libIndex = file_area.lib_list.length - 1; + + var highlightIndex = 0; + if ((pHighlightIndex != null) && (typeof(pHighlightIndex) == "number")) + highlightIndex = pHighlightIndex; + else if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number") && + (bbs.curlib == pLibIndex)) + { + highlightIndex = bbs.curdir; + } + // Double-check highlightIndex + if (highlightIndex < 0) + highlightIndex = 0; + else if (highlightIndex >= file_area.lib_list[libIndex].dir_list.length) + highlightIndex = file_area.lib_list[libIndex].dir_list.length - 1; + + // If there are no sub-boards in the given message group, then show + // an error and return. + if (file_area.lib_list[libIndex].dir_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no directories in the chosen library.\r\n\1p"); + return retObj; + } + + // Ensure that the file directory printf information is created for + // this file library. + this.BuildFileDirPrintfInfoForLib(libIndex); + + // Returns the index of the bottommost directory that can be displayed on + // the screen. + // + // Parameters: + // pTopDirIndex: The index of the topmost directory displayed on screen + // pNumItemsPerPage: The number of items per page + function getBottommostDirIndex(pTopDirIndex, pNumItemsPerPage) + { + var bottomDirIndex = pTopDirIndex + pNumItemsPerPage - 1; + // If bottomDirIndex is beyond the last index, then adjust it. + if (bottomDirIndex >= file_area.lib_list[libIndex].dir_list.length) + bottomDirIndex = file_area.lib_list[libIndex].dir_list.length - 1; + return bottomDirIndex; + } + + + // Figure out the index of the user's currently-selected sub-board. + var selectedDirIndex = 0; + if ((bbs.curdir != null) && (typeof(bbs.curdir) == "number")) + { + if ((bbs.curlib != null) && (typeof(bbs.curlib) == "number") && + (bbs.curlib == pLibIndex)) + { + selectedDirIndex = bbs.curdir; + } + } + + var listStartRow = 3; // The row on the screen where the list will start + var listEndRow = console.screen_rows - 1; // Row on screen where list will end + var topDirIndex = 0; // The index of the message group at the top of the list + // Figure out the index of the last message group to appear on the screen. + var numItemsPerPage = listEndRow - listStartRow + 1; + var bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + // Figure out how many pages are needed to list all the sub-boards. + var numPages = Math.ceil(file_area.lib_list[libIndex].dir_list.length / numItemsPerPage); + var pageNum = calcPageNum(topDirIndex, numItemsPerPage); + // Figure out the top index for the last page. + var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage; + + // If the highlighted row is beyond the current screen, then + // go to the appropriate page. + if (selectedDirIndex > bottomDirIndex) + { + var nextPageTopIndex = 0; + while (selectedDirIndex > bottomDirIndex) + { + nextPageTopIndex = topDirIndex + numItemsPerPage; + if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length) + { + // Adjust topDirIndex and bottomDirIndex, and + // refresh the list on the screen. + topDirIndex = nextPageTopIndex; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + } + else + break; + } + + // If we didn't find the correct page for some reason, then set the + // variables to display page 1 and select the first message group. + var foundCorrectPage = + ((topDirIndex < file_area.lib_list[libIndex].dir_list.length) && + (selectedDirIndex >= topDirIndex) && (selectedDirIndex <= bottomDirIndex)); + if (!foundCorrectPage) + { + topDirIndex = 0; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + selectedDirIndex = 0; + } + } + + // Clear the screen, write the help line and group list header, and output + // a screenful of message groups. + console.clear("\1n"); + this.WriteDirListHdr1Line(libIndex, numPages, pageNum); + this.WriteKeyHelpLine(); + + var curpos = new Object(); + curpos.x = 1; + curpos.y = 2; + console.gotoxy(curpos); + printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files"); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, listEndRow, + false, false); + // Start of the input loop. + var highlightScrenRow = 0; // The row on the screen for the highlighted group + var userInput = ""; // Will store a keypress from the user + var continueChoosingFileDir = true; + while (continueChoosingFileDir) + { + // Highlight the currently-selected message group + highlightScrenRow = listStartRow + (selectedDirIndex - topDirIndex); + curpos.y = highlightScrenRow; + if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows)) + { + console.gotoxy(1, highlightScrenRow); + this.WriteFileLibDirLine(libIndex, selectedDirIndex, true); + } + + // Get a key from the user (upper-case) and take action based upon it. + userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF); + switch (userInput) + { + case KEY_UP: // Move up one message group in the list + if (selectedDirIndex > 0) + { + // If the previous group index is on the previous page, then + // display the previous page. + var previousSubIndex = selectedDirIndex - 1; + if (previousSubIndex < topDirIndex) + { + // Adjust topDirIndex and bottomDirIndex, and + // refresh the list on the screen. + topDirIndex -= numItemsPerPage; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteFileLibDirLine(libIndex, selectedDirIndex, false); + } + selectedDirIndex = previousSubIndex; + } + break; + case KEY_DOWN: // Move down one message group in the list + if (selectedDirIndex < file_area.lib_list[libIndex].dir_list.length - 1) + { + // If the next group index is on the next page, then display + // the next page. + var nextGrpIndex = selectedDirIndex + 1; + if (nextGrpIndex > bottomDirIndex) + { + // Adjust topDirIndex and bottomDirIndex, and + // refresh the list on the screen. + topDirIndex += numItemsPerPage; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteFileLibDirLine(libIndex, selectedDirIndex, false); + } + selectedDirIndex = nextGrpIndex; + } + break; + case KEY_HOME: // Go to the top message group on the screen + if (selectedDirIndex > topDirIndex) + { + // Display the current line un-highlighted, then adjust + // selectedDirIndex. + console.gotoxy(1, curpos.y); + this.WriteFileLibDirLine(libIndex, selectedDirIndex, false); + selectedDirIndex = topDirIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_END: // Go to the bottom message group on the screen + if (selectedDirIndex < bottomDirIndex) + { + // Display the current line un-highlighted, then adjust + // selectedDirIndex. + console.gotoxy(1, curpos.y); + this.WriteFileLibDirLine(libIndex, selectedDirIndex, false); + selectedDirIndex = bottomDirIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_ENTER: // Select the currently-highlighted sub-board; and we're done. + continueChoosingFileDir = false; + retObj.fileDirChosen = true; + retObj.fileLibIndex = selectedDirIndex; + break; + case KEY_PAGE_DOWN: // Go to the next page + var nextPageTopIndex = topDirIndex + numItemsPerPage; + if (nextPageTopIndex < file_area.lib_list[libIndex].dir_list.length) + { + // Adjust topDirIndex and bottomDirIndex, and + // refresh the list on the screen. + topDirIndex = nextPageTopIndex; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + selectedDirIndex = topDirIndex; + } + break; + case KEY_PAGE_UP: // Go to the previous page + var prevPageTopIndex = topDirIndex - numItemsPerPage; + if (prevPageTopIndex >= 0) + { + // Adjust topDirIndex and bottomDirIndex, and + // refresh the list on the screen. + topDirIndex = prevPageTopIndex; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + selectedDirIndex = topDirIndex; + } + break; + case 'F': // Go to the first page + if (topDirIndex > 0) + { + topDirIndex = 0; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + selectedDirIndex = 0; + } + break; + case 'L': // Go to the last page + if (topDirIndex < topIndexForLastPage) + { + topDirIndex = topIndexForLastPage; + pageNum = calcPageNum(topDirIndex, numItemsPerPage); + bottomDirIndex = getBottommostDirIndex(topDirIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + selectedDirIndex = topIndexForLastPage; + } + break; + case 'Q': // Quit + continueChoosingFileDir = false; + break; + case '?': // Show help + this.ShowHelpScreen(true, true); + console.pause(); + // Refresh the screen + console.gotoxy(1, 1); + this.WriteDirListHdr1Line(libIndex, numPages, pageNum); + console.cleartoeol("\1n"); + this.WriteKeyHelpLine(); + console.gotoxy(1, 2); + printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files"); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + break; + default: + // If the user entered a numeric digit, then treat it as + // the start of the message group number. + if (userInput.match(/[0-9]/)) + { + var originalCurpos = curpos; + + // Put the user's input back in the input buffer to + // be used for getting the rest of the message number. + console.ungetstr(userInput); + // Move the cursor to the bottom of the screen and + // prompt the user for the message number. + console.gotoxy(1, console.screen_rows); + console.clearline("\1n"); + console.print("\1cDir #: \1h"); + userInput = console.getnum(file_area.lib_list[libIndex].dir_list.length); + // If the user made a selection, then set it in the + // return object and don't continue the input loop. + if (userInput > 0) + { + continueChoosingFileDir = false; + retObj.fileDirChosen = true; + retObj.fileLibIndex = userInput - 1; + } + else + { + // The user didn't enter a selection. Now we need to + // re-draw the screen due to everything being moved + // up one line. + console.gotoxy(1, 1); + this.WriteDirListHdr1Line(libIndex, numPages, pageNum); + console.cleartoeol("\1n"); + this.WriteKeyHelpLine(); + console.gotoxy(1, 2); + printf(this.fileDirHdrPrintfStr, "Dir #", "Description", "# Files"); + this.ListScreenfulOfDirs(libIndex, topDirIndex, listStartRow, + listEndRow, false, true); + } + } + break; + } + } + + return retObj; +} + +// Displays a screenful of file libraries (for the lightbar interface). +// +// Parameters: +// pStartIndex: The file library index to start at (0-based) +// pStartScreenRow: The row on the screen to start at (1-based) +// pEndScreenRow: The row on the screen to end at (1-based) +// pClearScreenFirst: Boolean - Whether or not to clear the screen first +// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end +// screen row if there aren't enough message groups to fill +// the screen. +function DDFileAreaChooser_listScreenfulOfFileLibs(pStartIndex, pStartScreenRow, + pEndScreenRow, pClearScreenFirst, + pBlankToEndRow) +{ + // Check the parameters; If they're bad, then just return. + if ((typeof(pStartIndex) != "number") || + (typeof(pStartScreenRow) != "number") || + (typeof(pEndScreenRow) != "number")) + { + return; + } + if ((pStartIndex < 0) || (pStartIndex >= file_area.lib_list.length)) + return; + if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows)) + return; + if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows)) + return; + + // If pStartScreenRow is greather than pEndScreenRow, then swap them. + if (pStartScreenRow > pEndScreenRow) + { + var temp = pStartScreenRow; + pStartScreenRow = pEndScreenRow; + pEndScreenRow = temp; + } + + // Calculate the ending index to use for the file libraries array. + var endIndex = pStartIndex + (pEndScreenRow-pStartScreenRow); + if (endIndex >= file_area.lib_list.length) + endIndex = file_area.lib_list.length - 1; + var onePastEndIndex = endIndex + 1; + + // Check to make sure bbs.curlib is valid (it might not be for brand-new users). + var curlibValid = ((bbs.curlib != null) && (typeof(bbs.curlib) != "undefined")); + + // Clear the screen, go to the specified screen row, and display the message + // group information. + if (pClearScreenFirst) + console.clear("\1n"); + console.gotoxy(1, pStartScreenRow); + var libIndex = pStartIndex; + for (; libIndex < onePastEndIndex; ++libIndex) + { + this.WriteFileLibLine(libIndex, false); + if (libIndex < endIndex) + console.crlf(); + } + + // If pBlankToEndRow is true and we're not at the end row yet, then + // write blank lines to the end row. + if (pBlankToEndRow) + { + var screenRow = pStartScreenRow + (endIndex - pStartIndex) + 1; + if (screenRow <= pEndScreenRow) + { + for (; screenRow <= pEndScreenRow; ++screenRow) + { + console.gotoxy(1, screenRow); + console.clearline("\1n"); + } + } + } +} + +// For the DDFileAreaChooser class - Writes a file library information line. +// +// Parameters: +// pLibIndex: The index of the file library to write (assumed to be valid) +// pHighlight: Boolean - Whether or not to write the line highlighted. +function DDFileAreaChooser_writeFileLibLine(pLibIndex, pHighlight) +{ + console.print("\1n"); + // Write the highlight background color if pHighlight is true. + if (pHighlight) + console.print(this.colors.bkgHighlight); + + var curlibValid = (typeof(bbs.curlib) == "number"); + + // Write the message group information line + console.print((curlibValid && (pLibIndex == bbs.curlib)) ? this.colors.areaMark + "*" : " "); + printf((pHighlight ? this.fileLibHighlightPrintfStr : this.fileLibPrintfStr), + +(pLibIndex+1), + file_area.lib_list[pLibIndex].description.substr(0, this.descFieldLen), + file_area.lib_list[pLibIndex].dir_list.length); + console.cleartoeol("\1n"); +} + +// For the DDFileAreaChooser class: Writes a file directory information line. +// +// Parameters: +// pLibIndex: The index of the file library (assumed to be valid) +// pDirIndex: The index of the directory within the file library to write (assumed to be valid) +// pHighlight: Boolean - Whether or not to write the line highlighted. +function DDFileAreaChooser_writeFileLibDirLine(pLibIndex, pDirIndex, pHighlight) +{ + console.print("\1n"); + // Write the highlight background color if pHighlight is true. + if (pHighlight) + console.print(this.colors.bkgHighlight); + + // Determine if pLibIndex and pDirIndex specify the user's + // currently-selected file library & directory. + var currentDir = false; + if ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number")) + currentDir = ((pLibIndex == bbs.curlib) && (pDirIndex == bbs.curdir)); + + // Print the directory information line + console.print(currentDir ? this.colors.areaMark + "*" : " "); + printf((pHighlight ? this.fileDirListPrintfInfo[pLibIndex].highlightPrintfStr : this.fileDirListPrintfInfo[pLibIndex].printfStr), + +(pDirIndex+1), + file_area.lib_list[pLibIndex].dir_list[pDirIndex].description.substr(0, this.descFieldLen), + this.fileDirListPrintfInfo[pLibIndex].fileCounts[pDirIndex]); +} + +// Updates the page number text in the file group/area list header line on the screen. +// +// Parameters: +// pPageNum: The page number +// pNumPages: The total number of pages +// pFileLib: Boolean - Whether or not this is for the file library header. If so, +// then this will go to the right location for the file group page text +// and use this.colors.header for the text. Otherwise, this will +// go to the right place for the file area page text and use the +// file area header color. +// pRestoreCurPos: Optional - Boolean - If true, then move the cursor back +// to the position where it was before this function was called +function DDFileAreaChooser_updatePageNumInHeader(pPageNum, pNumPages, pFileLib, pRestoreCurPos) +{ + var originalCurPos = null; + if (pRestoreCurPos) + originalCurPos = console.getxy(); + + if (pFileLib) + { + console.gotoxy(30, 1); + console.print("\1n" + this.colors.header + pPageNum + " of " + pNumPages + ") "); + } + else + { + console.gotoxy(67, 1); + console.print("\1n" + this.colors.fileAreaHdr + pPageNum + " of " + pNumPages + ") "); + } + + if (pRestoreCurPos) + console.gotoxy(originalCurPos); +} + +// Displays a screenful of file directories, for the lightbar interface. +// +// Parameters: +// pLibIndex: The index of the file library (0-based) +// pStartDirIndex: The file directory index to start at (0-based) +// pStartScreenRow: The row on the screen to start at (1-based) +// pEndScreenRow: The row on the screen to end at (1-based) +// pClearScreenFirst: Boolean - Whether or not to clear the screen first +// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end +// screen row if there aren't enough message groups to fill +// the screen. +function DDMsgAreaChooser_listScreenfulOfFileDirs(pLibIndex, pStartDirIndex, + pStartScreenRow, pEndScreenRow, + pClearScreenFirst, pBlankToEndRow) +{ + // Check the parameters; If they're bad, then just return. + if ((typeof(pLibIndex) != "number") || + (typeof(pStartDirIndex) != "number") || + (typeof(pStartScreenRow) != "number") || + (typeof(pEndScreenRow) != "number")) + { + return; + } + if ((pLibIndex < 0) || (pLibIndex >= file_area.lib_list.length)) + return; + if ((pStartDirIndex < 0) || + (pStartDirIndex >= file_area.lib_list[pLibIndex].dir_list.length)) + { + return; + } + if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows)) + return; + if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows)) + return; + // If pStartScreenRow is greather than pEndScreenRow, then swap them. + if (pStartScreenRow > pEndScreenRow) + { + var temp = pStartScreenRow; + pStartScreenRow = pEndScreenRow; + pEndScreenRow = temp; + } + + // Calculate the ending index to use for the sub-board array. + var endIndex = pStartDirIndex + (pEndScreenRow-pStartScreenRow); + if (endIndex >= file_area.lib_list[pLibIndex].dir_list.length) + endIndex = file_area.lib_list[pLibIndex].dir_list.length - 1; + var onePastEndIndex = endIndex + 1; + + // Clear the screen and go to the specified screen row. + if (pClearScreenFirst) + console.clear("\1n"); + console.gotoxy(1, pStartScreenRow); + + // Start listing the file directories. + + var dirIndex = pStartDirIndex; + for (; dirIndex < onePastEndIndex; ++dirIndex) + { + this.WriteFileLibDirLine(pLibIndex, dirIndex, false); + if (dirIndex < endIndex) + console.crlf(); + } + + // If pBlankToEndRow is true and we're not at the end row yet, then + // write blank lines to the end row. + if (pBlankToEndRow) + { + var screenRow = pStartScreenRow + (endIndex - pStartDirIndex) + 1; + if (screenRow <= pEndScreenRow) + { + for (; screenRow <= pEndScreenRow; ++screenRow) + { + console.gotoxy(1, screenRow); + console.clearline("\1n"); + } + } + } +} + +function DDFileAreaChooser_writeKeyHelpLine() +{ + console.gotoxy(1, console.screen_rows); + console.print(this.lightbarKeyHelpText); +} + +// For the DDFileAreaChooser class: Reads the configuration file. +function DDFileAreaChooser_ReadConfigFile() +{ + // Determine the script's startup directory. + // This code is a trick that was created by Deuce, suggested by Rob Swindell + // as a way to detect which directory the script was executed in. I've + // shortened the code a little. + var startup_path = '.'; + try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; } + startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,'')); + + // Open the configuration file + var cfgFile = new File(startup_path + "DDFileAreaChooser.cfg"); + if (cfgFile.open("r")) + { + var settingsMode = "behavior"; + var fileLine = null; // A line read from the file + var equalsPos = 0; // Position of a = in the line + var commentPos = 0; // Position of the start of a comment + var setting = null; // A setting name (string) + var settingUpper = null; // Upper-case setting name + var value = null; // A value for a setting (string) + while (!cfgFile.eof) + { + // Read the next line from the config file. + fileLine = cfgFile.readln(2048); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // If in the "behavior" section, then set the behavior-related variables. + if (fileLine.toUpperCase() == "[BEHAVIOR]") + { + settingsMode = "behavior"; + continue; + } + else if (fileLine.toUpperCase() == "[COLORS]") + { + settingsMode = "colors"; + continue; + } + + // If the line has a semicolon anywhere in it, then remove + // everything from the semicolon onward. + commentPos = fileLine.indexOf(";"); + if (commentPos > -1) + fileLine = fileLine.substr(0, commentPos); + + // Look for an equals sign, and if found, separate the line + // into the setting name (before the =) and the value (after the + // equals sign). + equalsPos = fileLine.indexOf("="); + if (equalsPos > 0) + { + // Read the setting & value, and trim leading & trailing spaces. + setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); + settingUpper = setting.toUpperCase(); + value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); + + if (settingsMode == "behavior") + { + // Set the appropriate value in the settings object. + if (settingUpper == "USELIGHTBARINTERFACE") + this.useLightbarInterface = (value.toUpperCase() == "TRUE"); + } + else if (settingsMode == "colors") + this.colors[setting] = value; + } + } + + cfgFile.close(); + } +} + +// Misc. functions + +// For the DDFileAreaChooser class: Shows the help screen +// +// Parameters: +// pLightbar: Boolean - Whether or not to show lightbar help. If +// false, then this function will show regular help. +// pClearScreen: Boolean - Whether or not to clear the screen first +function DDFileAreaChooser_showHelpScreen(pLightbar, pClearScreen) +{ + if (pClearScreen) + console.clear("\1n"); + else + console.print("\1n"); + console.center("\1c\1hDigital Distortion File Area Chooser"); + console.center("\1k������������������������������������"); + console.center("\1n\1cVersion \1g" + DD_FILE_AREA_CHOOSER_VERSION + + " \1w\1h(\1b" + DD_FILE_AREA_CHOOSER_VER_DATE + "\1w)"); + console.crlf(); + console.print("\1n\1cFirst, a listing of file libraries is displayed. One can be chosen by typing"); + console.crlf(); + console.print("its number. Then, a listing of directories within that library will be"); + console.crlf(); + console.print("shown, and one can be chosen by typing its number."); + console.crlf(); + + if (pLightbar) + { + console.crlf(); + console.print("\1n\1cThe lightbar interface also allows up & down navigation through the lists:"); + console.crlf(); + console.print("\1k\1h��������������������������������������������������������������������������"); + console.crlf(); + console.print("\1n\1c\1hUp arrow\1n\1c: Move the cursor up one line"); + console.crlf(); + console.print("\1hDown arrow\1n\1c: Move the cursor down one line"); + console.crlf(); + console.print("\1hENTER\1n\1c: Select the current library/dir"); + console.crlf(); + console.print("\1hHOME\1n\1c: Go to the first item on the screen"); + console.crlf(); + console.print("\1hEND\1n\1c: Go to the last item on the screen"); + console.crlf(); + console.print("\1hPageUp\1n\1c/\1hPageDown\1n\1c: Go to the previous/next page"); + console.crlf(); + console.print("\1hF\1n\1c/\1hL\1n\1c: Go to the first/last page"); + console.crlf(); + } + + console.crlf(); + console.print("Additional keyboard commands:"); + console.crlf(); + console.print("\1k\1h�����������������������������"); + console.crlf(); + console.print("\1n\1c\1h?\1n\1c: Show this help screen"); + console.crlf(); + console.print("\1hQ\1n\1c: Quit"); + console.crlf(); +} + +// Returns the number of files in a directory for one of the file libraries. +// Note that returns the number of files in the directory on the hard drive, +// which isn't necessarily the number of files in the database for the directory. +// +// Paramters: +// pLibNum: The file library number (0-based) +// pDirNum: The file directory number (0-based) +// +// Returns: The number of files in the directory +function DDFileAreaChooser_NumFilesInDir(pLibNum, pDirNum) +{ + var numFiles = 0; + + // Count the files in the directory. If it's not a directory, then + // increment numFiles. + var files = directory(file_area.lib_list[pLibNum].dir_list[pDirNum].path + "*.*"); + numFiles = files.length; + // Make sure directories aren't counted: Go through the files array, and + // for each directory, decrement numFiles. + for (var i in files) + { + if (file_isdir(files[i])) + --numFiles; + } + + return numFiles; +} + +// Builds file directory printf format information for a file library. +// The widths of the description & # files columns are calculated +// based on the greatest number of files in a directory for the +// file library. +// +// Parameters: +// pLibIndex: The index of the file library +function DDFileAreaChooser_buildFileDirPrintfInfoForLib(pLibIndex) +{ + if (typeof(this.fileDirListPrintfInfo[pLibIndex]) == "undefined") + { + // Create the file directory listing object and set some defaults + this.fileDirListPrintfInfo[pLibIndex] = new Object(); + this.fileDirListPrintfInfo[pLibIndex].numFilesLen = 4; + // Get information about the number of files in each directory + // and the greatest number of files and set up the according + // information in the file directory list object + var fileDirInfo = getGreatestNumFiles(pLibIndex); + if (fileDirInfo != null) + { + this.fileDirListPrintfInfo[pLibIndex].numFilesLen = fileDirInfo.greatestNumFiles.toString().length; + this.fileDirListPrintfInfo[pLibIndex].fileCounts = fileDirInfo.fileCounts.slice(0); + } + else + { + // fileDirInfo is null. We still want to create + // the fileCounts array in the file directory object + // so that it's valid. + this.fileDirListPrintfInfo[pLibIndex].fileCounts = new Array(file_area.lib_list[pLibIndex].length); + for (var i = 0; i < file_area.lib_list[pLibIndex].length; ++i) + this.fileDirListPrintfInfo[pLibIndex].fileCounts[i] == 0; + } + + // Set the description field length and printf strings for + // this file library + this.fileDirListPrintfInfo[pLibIndex].descFieldLen = + console.screen_columns - this.areaNumLen + - this.fileDirListPrintfInfo[pLibIndex].numFilesLen - 5; + this.fileDirListPrintfInfo[pLibIndex].printfStr = + this.colors.areaNum + " %" + this.areaNumLen + "d " + + this.colors.desc + "%-" + + this.fileDirListPrintfInfo[pLibIndex].descFieldLen + + "s " + this.colors.numItems + "%" + + this.fileDirListPrintfInfo[pLibIndex].numFilesLen + "d"; + this.fileDirListPrintfInfo[pLibIndex].highlightPrintfStr = + "\1n" + this.colors.bkgHighlight + + this.colors.areaNumHighlight + " %" + this.areaNumLen + + "d " + this.colors.descHighlight + "%-" + + this.fileDirListPrintfInfo[pLibIndex].descFieldLen + + "s " + this.colors.numItemsHighlight + "%" + + this.fileDirListPrintfInfo[pLibIndex].numFilesLen +"d\1n"; + } +} + +// Removes multiple, leading, and/or trailing spaces +// The search & replace regular expressions used in this +// function came from the following URL: +// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces +// +// Parameters: +// pString: The string to trim +// pLeading: Whether or not to trim leading spaces (optional, defaults to true) +// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true) +// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true) +function trimSpaces(pString, pLeading, pMultiple, pTrailing) +{ + var leading = true; + var multiple = true; + var trailing = true; + if(typeof(pLeading) != "undefined") + leading = pLeading; + if(typeof(pMultiple) != "undefined") + multiple = pMultiple; + if(typeof(pTrailing) != "undefined") + trailing = pTrailing; + + // To remove both leading & trailing spaces: + //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); + + if (leading) + pString = pString.replace(/(^\s*)/gi,""); + if (multiple) + pString = pString.replace(/[ ]{2,}/gi," "); + if (trailing) + pString = pString.replace(/(\s*$)/gi,""); + + return pString; +} + +// Calculates & returns a page number. +// +// Parameters: +// pTopIndex: The index (0-based) of the topmost item on the page +// pNumPerPage: The number of items per page +// +// Return value: The page number +function calcPageNum(pTopIndex, pNumPerPage) +{ + return ((pTopIndex / pNumPerPage) + 1); +} + +// For a given file library index, returns an object containing +// the greatest number of files of all directories within a file +// library and an array containing the number of files in each +// directory. If the given library index is invalid, this +// function will return null. +// +// Parameters: +// pLibIndex: The index of the file library +// +// Returns: An object containing the following properties: +// greatestNumFiles: The greatest number of files of all +// directories within the file library +// fileCounts: An array, indexed by directory index, +// containing the number of files in each +// directory within the file library +function getGreatestNumFiles(pLibIndex) +{ + // Sanity checking + if (typeof(pLibIndex) != "number") + return null; + if (typeof(file_area.lib_list[pLibIndex]) == "undefined") + return null; + + var retObj = new Object(); + retObj.greatestNumFiles = 0; + retObj.fileCounts = new Array(file_area.lib_list[pLibIndex].dir_list.length); + for (var dirIndex = 0; dirIndex < file_area.lib_list[pLibIndex].dir_list.length; ++dirIndex) + { + retObj.fileCounts[dirIndex] = DDFileAreaChooser_NumFilesInDir(pLibIndex, dirIndex); + if (retObj.fileCounts[dirIndex] > retObj.greatestNumFiles) + retObj.greatestNumFiles = retObj.fileCounts[dirIndex]; + } + return retObj; +} + +// Inputs a keypress from the user and handles some ESC-based +// characters such as PageUp, PageDown, and ESC. If PageUp +// or PageDown are pressed, this function will return the +// string "\1PgUp" (KEY_PAGE_UP) or "\1Pgdn" (KEY_PAGE_DOWN), +// respectively. Also, F1-F5 will be returned as "\1F1" +// through "\1F5", respectively. +// Thanks goes to Psi-Jack for the original impementation +// of this function. +// +// Parameters: +// pGetKeyMode: Optional - The mode bits for console.getkey(). +// If not specified, K_NONE will be used. +// +// Return value: The user's keypress +function getKeyWithESCChars(pGetKeyMode) +{ + var getKeyMode = K_NONE; + if (typeof(pGetKeyMode) == "number") + getKeyMode = pGetKeyMode; + + var userInput = console.getkey(getKeyMode); + if (userInput == KEY_ESC) { + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case '[': + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case 'V': + userInput = KEY_PAGE_UP; + break; + case 'U': + userInput = KEY_PAGE_DOWN; + break; + } + break; + case 'O': + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case 'P': + userInput = "\1F1"; + break; + case 'Q': + userInput = "\1F2"; + break; + case 'R': + userInput = "\1F3"; + break; + case 'S': + userInput = "\1F4"; + break; + case 't': + userInput = "\1F5"; + break; + } + default: + break; + } + } + + return userInput; +} \ No newline at end of file diff --git a/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg b/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg new file mode 100644 index 0000000000000000000000000000000000000000..7e5cd45fe00f5dfb2be5d5d6f78af43fd0b96a6d --- /dev/null +++ b/xtrn/DDAreaChoosers/DDMsgAreaChooser.cfg @@ -0,0 +1,43 @@ +[BEHAVIOR] +useLightbarInterface=true +; If showImportDates is true, then the date shown for the latest date +; will be the messaeg import date. If false, the date will represent +; the timestamp in the message. +showImportDates=true + +[COLORS] +; Area number +areaNum=nwh +; Description +desc=nc +; Number of items +numItems=bh +; List header +header=nyh +; For the sub-board list header that has "Sub-boards for" the group and +; has the page number +subBoardHeader=ng +; Mark character for areas that are currently selected +areaMark=gh +; Latest message date +latestDate=ng +; Latest message time +latestTime=nm +; Highlighted colors (for lightbar mode) +bkgHighlight=4 +areaNumHighlight=wh +descHighlight=c +dateHighlight=wh +timeHighlight=wh +numItemsHighlight=wh + +; Colors for the lightbar help line text: +; Background +lightbarHelpLineBkg=7 +; The color for general text in the lightbar help line +lightbarHelpLineGeneral=b +; The color for the hotkeys in the lightbar help line +lightbarHelpLineHotkey=r +; The color for the ) separating the hotkeys from the general text in the +; lightbar help line +lightbarHelpLineParen=m \ No newline at end of file diff --git a/xtrn/DDAreaChoosers/DDMsgAreaChooser.js b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js new file mode 100644 index 0000000000000000000000000000000000000000..62fd3ffe4b0869d36f1668df2a13a19265faf45c --- /dev/null +++ b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js @@ -0,0 +1,1900 @@ +/* This is a script that lets the user choose a message area, + * with either a lightbar or traditional user interface. + * + * Date User Version Description + * 2010-02-05 Eric Oulashin 0.90 Started + * 2010-02-18 to + * 2010-02-27 Eric Oulashin Continued work. + * Added the first & lasg page functionality + * to the lightbar interface. + * 2010-03-13 Eric Oulashin 1.00 Added the ability to load settings from a + * configuration file. + * 2011-04-22 Eric Oulashin 1.01 Fixed the wording when choosing a message + * group - It now says "group #" instead + * of "sub-board #". + * 2012-10-06 Eric Oulashin 1.02 For lightbar mode, updated to display the + * page number in the header at the top (in + * addition to the total number of pages, + * which it was already displaying). + * 2012-11-30 Eric Oulashin 1.03 Bug fix: After leaving the help screen + * from the sub-board list, the top line is + * now correctly written with the page + * information as "Page # of #". + * 2013-05-04 Eric Oulashin 1.04 Updated to dynamically adjust the length + * of the # messages column based on the + * greatest number of messages of all + * sub-boards within a message group so + * that the formatting still looks good. + * 2013-05-10 Eric Oulashin 1.05 Updated the version to match the + * version in DDFileAreaChooser (a bug + * was fixed there, but DDMsgAreaChooser + * didn't have the corresponding bug). + * 2014-09-14 Eric Oulashin 1.06 Bug fix: Updated the highlight (lightbar) + * format string in the + * DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp() + * function to include a normal attribute at + * the end to avoid color issues when clearing + * the screen, etc. Bug reported by Psi-Jack. + * 2014-12-22 Eric Oulashin 1.07 Bug fix: Made this.colors.subBoardHeader apply + * to the whole line rather than just the page + * number. + * Bug fix: The initial display of the page number + * is now correct (previously, it would start out + * saying page 1, even if on another page). + * 2015-04-19 Eric Oulashin 1.08 Added color settings for the lightbar help text + * at the bottom of the screen. Also, added the + * ability to use the PageUp & PageDown keys instead + * of P and N in the lightbar lists. +*/ + +/* Command-line arguments: + 1 (argv[0]): Boolean - Whether or not to run the area chooser (if false, + then this file will just provide the DDMsgAreaChooser class). +*/ + +load("sbbsdefs.js"); + +// This script requires Synchronet version 3.14 or higher. +// Exit if the Synchronet version is below the minimum. +if (system.version_num < 31400) +{ + var message = "\1n\1h\1y\1i* Warning:\1n\1h\1w Digital Distortion Message Lister " + + "requires version \1g3.14\1w or\r\n" + + "higher of Synchronet. This BBS is using version \1g" + system.version + + "\1w. Please notify the sysop."; + console.crlf(); + console.print(message); + console.crlf(); + console.pause(); + exit(); +} + +// Version & date variables +var DD_MSG_AREA_CHOOSER_VERSION = "1.08"; +var DD_MSG_AREA_CHOOSER_VER_DATE = "2015-04-19"; + +// Keyboard input key codes +var CTRL_M = "\x0d"; +var KEY_ENTER = CTRL_M; +var KEY_ESC = ascii(27); +// PageUp & PageDown keys - Not real key codes, but codes defined +// to be used & recognized in this script +var KEY_PAGE_UP = "\1PgUp"; +var KEY_PAGE_DOWN = "\1PgDn"; + +// Key codes for display +var UP_ARROW = ascii(24); +var DOWN_ARROW = ascii(25); + +// Determine whether or not to execute the message listing code, based +// on the first program argument (a boolean). +var executeThisScript = true; +if (typeof(argv[0]) != "undefined") + executeThisScript = argv[0]; + +// If executeThisScript is true, then create a DDMsgAreaChooser object and use +// it to let the user choose a message area. +if (executeThisScript) +{ + var msgAreaChooser = new DDMsgAreaChooser(); + msgAreaChooser.SelectMsgArea(); +} + +// End of script execution + +/////////////////////////////////////////////////////////////////////////////////// +// DDMsgAreaChooser class stuff + +function DDMsgAreaChooser() +{ + // this.colors will be an associative array of colors (indexed by their + // usage) used for the message group/sub-board lists. + // Colors for the file & message area lists + this.colors = new Object(); + this.colors.areaNum = "\1n\1w\1h"; + this.colors.desc = "\1n\1c"; + this.colors.numItems = "\1b\1h"; + this.colors.header = "\1n\1y\1h"; + this.colors.subBoardHeader = "\1n\1g"; + this.colors.areaMark = "\1g\1h"; + this.colors.latestDate = "\1n\1g"; + this.colors.latestTime = "\1n\1m"; + // Highlighted colors (for lightbar mode) + this.colors.bkgHighlight = "\1" + "4"; // Blue background + this.colors.areaNumHighlight = "\1w\1h"; + this.colors.descHighlight = "\1c"; + this.colors.dateHighlight = "\1w\1h"; + this.colors.timeHighlight = "\1w\1h"; + this.colors.numItemsHighlight = "\1w\1h"; + // Lightbar help line colors + this.colors.lightbarHelpLineBkg = "\1" + "7"; + this.colors.lightbarHelpLineGeneral = "\1b"; + this.colors.lightbarHelpLineHotkey = "\1r"; + this.colors.lightbarHelpLineParen = "\1m"; + + // showImportDates is a boolean to specify whether or not to display the + // message import dates. If false, the message written dates will be + // displayed instead. + this.showImportDates = true; + + // useLightbarInterface specifies whether or not to use the lightbar + // interface. The lightbar interface will still only be used if the + // user's terminal supports ANSI. + this.useLightbarInterface = true; + + // These variables store the lengths of the various columns displayed in + // the message group/sub-board lists. + // Sub-board info field lengths + this.areaNumLen = 4; + this.numItemsLen = 4; + this.dateLen = 10; // i.e., YYYY-MM-DD + this.timeLen = 8; // i.e., HH:MM:SS + // Sub-board name length - This should be 47 for an 80-column display. + this.subBoardNameLen = console.screen_columns - this.areaNumLen - + this.numItemsLen - this.dateLen - this.timeLen - 7; + // Message group description length (67 chars on an 80-column screen) + this.msgGrpDescLen = console.screen_columns - this.areaNumLen - + this.numItemsLen - 5; + + // Set the function pointers for the object + this.ReadConfigFile = DDMsgAreaChooser_ReadConfigFile; + this.WriteKeyHelpLine = DDMsgAreaChooser_writeKeyHelpLine; + this.WriteGrpListHdrLine = DDMsgAreaChooser_writeGrpListTopHdrLine; + this.WriteSubBrdListHdr1Line = DMsgAreaChooser_writeSubBrdListHdr1Line; + this.SelectMsgArea = DDMsgAreaChooser_selectMsgArea; + this.SelectMsgArea_Lightbar = DDMsgAreaChooser_selectMsgArea_Lightbar; + this.SelectSubBoard_Lightbar = DDMsgAreaChooser_selectSubBoard_Lightbar; + this.SelectMsgArea_Traditional = DDMsgAreaChooser_selectMsgArea_Traditional; + this.ListMsgGrps = DDMsgAreaChooser_listMsgGrps_Traditional; + this.ListSubBoardsInMsgGroup = DDMsgAreaChooser_listSubBoardsInMsgGroup_Traditional; + // Lightbar-specific functions + this.ListScreenfulOfMsgGrps = DDMsgAreaChooser_listScreenfulOfMsgGrps; + this.WriteMsgGroupLine = DDMsgAreaChooser_writeMsgGroupLine; + this.updatePageNumInHeader = DDMsgAreaChooser_updatePageNumInHeader; + this.ListScreenfulOfSubBrds = DDMsgAreaChooser_listScreenfulOfSubBrds; + this.WriteMsgSubBoardLine = DDMsgAreaChooser_writeMsgSubBrdLine; + // Help screen + this.ShowHelpScreen = DDMsgAreaChooser_showHelpScreen; + // Function to build the sub-board printf information for a message + // group + this.BuildSubBoardPrintfInfoForGrp = DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp; + + // Read the settings from the config file. + this.ReadConfigFile(); + + // printf strings for various things + // Message group information (printf strings) + this.msgGrpListPrintfStr = "\1n " + this.colors.areaNum + "%" + this.areaNumLen + + "d " + this.colors.desc + "%-" + + this.msgGrpDescLen + "s " + this.colors.numItems + + "%" + this.numItemsLen + "d"; + this.msgGrpListHilightPrintfStr = "\1n" + this.colors.bkgHighlight + " " + + "\1n" + this.colors.bkgHighlight + + this.colors.areaNumHighlight + "%" + this.areaNumLen + + "d \1n" + this.colors.bkgHighlight + + this.colors.descHighlight + "%-" + + this.msgGrpDescLen + "s \1n" + this.colors.bkgHighlight + + this.colors.numItemsHighlight + "%" + this.numItemsLen + + "d"; + // Message group list header (printf string) + this.msgGrpListHdrPrintfStr = this.colors.header + "%6s %-" + + +(this.msgGrpDescLen-8) + "s %-12s"; + // Sub-board information header (printf string) + this.subBoardListHdrPrintfStr = this.colors.header + " %5s %-" + + +(this.subBoardNameLen-3) + "s %-7s %-19s"; + // Lightbar mode key help line + this.lightbarKeyHelpText = "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + UP_ARROW + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + DOWN_ARROW + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "HOME" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "END" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "#" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "PgUp" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "/" + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "Dn" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + ", " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "F" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "irst pg, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "L" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "ast pg, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "Q" + + "\1n" + this.colors.lightbarHelpLineParen + + this.colors.lightbarHelpLineBkg + ")" + + "\1n" + this.colors.lightbarHelpLineGeneral + + this.colors.lightbarHelpLineBkg + "uit, " + + "\1n" + this.colors.lightbarHelpLineHotkey + + this.colors.lightbarHelpLineBkg + "?"; + // Pad the lightbar key help text on either side to center it on the screen + // (but leave off the last character to avoid screen drawing issues) + var helpTextLen = console.strlen(this.lightbarKeyHelpText); + var helpTextStartCol = (console.screen_columns/2) - (helpTextLen/2); + this.lightbarKeyHelpText = "\1n" + this.colors.lightbarHelpLineBkg + + format("%" + +(helpTextStartCol) + "s", "") + + this.lightbarKeyHelpText + "\1n" + + this.colors.lightbarHelpLineBkg; + var numTrailingChars = console.screen_columns - (helpTextStartCol+helpTextLen) - 1; + this.lightbarKeyHelpText += format("%" + +(numTrailingChars) + "s", "") + "\1n"; + // this.subBoardListPrintfInfo will be an array of printf strings + // for the sub-boards in the message groups. The index is the + // message group index. The sub-board printf information is created + // on the fly the first time the user lists sub-boards for a message + // group. + this.subBoardListPrintfInfo = new Array(); +} + +// For the DDMsgAreaChooser class: Writes the line of key help at the bottom +// row of the screen. +function DDMsgAreaChooser_writeKeyHelpLine() +{ + console.gotoxy(1, console.screen_rows); + console.print(this.lightbarKeyHelpText); +} + +// For the DDMsgAreaChooser class: Outputs the header line to appear above +// the list of message groups. +// +// Parameters: +// pNumPages: The number of pages. This is optional; if this is +// not passed, then it won't be used. +// pPageNum: The page number. This is optional; if this is not passed, +// then it won't be used. +function DDMsgAreaChooser_writeGrpListTopHdrLine(pNumPages, pPageNum) +{ + var descStr = "Description"; + if ((typeof(pPageNum) == "number") && (typeof(pNumPages) == "number")) + descStr += " (Page " + pPageNum + " of " + pNumPages + ")"; + else if ((typeof(pPageNum) == "number") && (typeof(pNumPages) != "number")) + descStr += " (Page " + pPageNum + ")"; + else if ((typeof(pPageNum) != "number") && (typeof(pNumPages) == "number")) + descStr += " (" + pNumPages + (pNumPages == 1 ? " page)" : " pages)"); + printf(this.msgGrpListHdrPrintfStr, "Group#", descStr, "# Sub-Boards"); + console.cleartoeol("\1n"); +} + +// For the DDMsgAreaChooser class: Outputs the first header line to appear +// above the sub-board list for a message group. +// +// Parameters: +// pGrpIndex: The index of the message group (assumed to be valid) +// pNumPages: The number of pages. This is optional; if this is +// not passed, then it won't be used. +// pPageNum: The page number. This is optional; if this is not passed, +// then it won't be used. +function DMsgAreaChooser_writeSubBrdListHdr1Line(pGrpIndex, pNumPages, pPageNum) +{ + var descFormatStr = "\1n" + this.colors.subBoardHeader + "Sub-boards of \1h%-25s \1n" + + this.colors.subBoardHeader; + if ((typeof(pPageNum) == "number") && (typeof(pNumPages) == "number")) + descFormatStr += "(Page " + pPageNum + " of " + pNumPages + ")"; + else if ((typeof(pPageNum) == "number") && (typeof(pNumPages) != "number")) + descFormatStr += "(Page " + pPageNum + ")"; + else if ((typeof(pPageNum) != "number") && (typeof(pNumPages) == "number")) + descFormatStr += "(" + pNumPages + (pNumPages == 1 ? " page)" : " pages)"); + printf(descFormatStr, msg_area.grp_list[pGrpIndex].description.substr(0, 25)); + console.cleartoeol("\1n"); +} + +// For the DDMsgAreaChooser class: Lets the user choose a message group and +// sub-board via numeric input, using a lightbar interface (if enabled and +// if the user's terminal uses ANSI) or a traditional user interface. +function DDMsgAreaChooser_selectMsgArea() +{ + if (this.useLightbarInterface && console.term_supports(USER_ANSI)) + this.SelectMsgArea_Lightbar(); + else + this.SelectMsgArea_Traditional(); +} + +// For the DDMsgAreaChooser class: Lets the user choose a message group and +// sub-board via numeric input, using a lightbar user interface. +function DDMsgAreaChooser_selectMsgArea_Lightbar() +{ + // If there are no message groups, then don't let the user + // choose one. + if (msg_area.grp_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no message groups.\r\n\1p"); + return; + } + + // Returns the index of the bottommost message group that can be displayed + // on the screen. + // + // Parameters: + // pTopGrpIndex: The index of the topmost message group displayed on screen + // pNumItemsPerPage: The number of items per page + function getBottommostGrpIndex(pTopGrpIndex, pNumItemsPerPage) + { + var bottomGrpIndex = pTopGrpIndex + pNumItemsPerPage - 1; + // If bottomGrpIndex is beyond the last index, then adjust it. + if (bottomGrpIndex >= msg_area.grp_list.length) + bottomGrpIndex = msg_area.grp_list.length - 1; + return bottomGrpIndex; + } + + + // Figure out the index of the user's currently-selected message group + var selectedGrpIndex = 0; + if ((bbs.curgrp != null) && (typeof(bbs.curgrp) == "number")) + selectedGrpIndex = bbs.curgrp; + + var listStartRow = 2; // The row on the screen where the list will start + var listEndRow = console.screen_rows - 1; // Row on screen where list will end + var topMsgGrpIndex = 0; // The index of the message group at the top of the list + + // Figure out the index of the last message group to appear on the screen. + var numItemsPerPage = listEndRow - listStartRow + 1; + var bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + // Figure out how many pages are needed to list all the sub-boards. + var numPages = Math.ceil(msg_area.grp_list.length / numItemsPerPage); + // Figure out the top index for the last page. + var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage; + + // If the highlighted row is beyond the current screen, then + // go to the appropriate page. + if (selectedGrpIndex > bottomMsgGrpIndex) + { + var nextPageTopIndex = 0; + while (selectedGrpIndex > bottomMsgGrpIndex) + { + nextPageTopIndex = topMsgGrpIndex + numItemsPerPage; + if (nextPageTopIndex < msg_area.grp_list.length) + { + // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and + // refresh the list on the screen. + topMsgGrpIndex = nextPageTopIndex; + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + } + else + break; + } + + // If we didn't find the correct page for some reason, then set the + // variables to display page 1 and select the first message group. + var foundCorrectPage = ((topMsgGrpIndex < msg_area.grp_list.length) && + (selectedGrpIndex >= topMsgGrpIndex) && (selectedGrpIndex <= bottomMsgGrpIndex)); + if (!foundCorrectPage) + { + topMsgGrpIndex = 0; + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + selectedGrpIndex = 0; + } + } + + // Clear the screen, write the help line and group list header, and output + // a screenful of message groups. + console.clear("\1n"); + this.WriteKeyHelpLine(); + + var curpos = new Object(); + curpos.x = 1; + curpos.y = 1; + console.gotoxy(curpos); + var pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); + this.WriteGrpListHdrLine(numPages, pageNum); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, false, false); + // Start of the input loop. + var highlightScrenRow = 0; // The row on the screen for the highlighted group + var userInput = ""; // Will store a keypress from the user + var retObj = null; // To store the return value of choosing a sub-board + var continueChoosingMsgArea = true; + while (continueChoosingMsgArea) + { + // Highlight the currently-selected message group + highlightScrenRow = listStartRow + (selectedGrpIndex - topMsgGrpIndex); + curpos.y = highlightScrenRow; + if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows)) + { + console.gotoxy(1, highlightScrenRow); + this.WriteMsgGroupLine(selectedGrpIndex, true); + } + + // Get a key from the user (upper-case) and take action based upon it. + userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF); + switch (userInput) + { + case KEY_UP: // Move up one message group in the list + if (selectedGrpIndex > 0) + { + // If the previous group index is on the previous page, then + // display the previous page. + var previousGrpIndex = selectedGrpIndex - 1; + if (previousGrpIndex < topMsgGrpIndex) + { + // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and + // refresh the list on the screen. + topMsgGrpIndex -= numItemsPerPage; + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteMsgGroupLine(selectedGrpIndex, false); + } + selectedGrpIndex = previousGrpIndex; + } + break; + case KEY_DOWN: // Move down one message group in the list + if (selectedGrpIndex < msg_area.grp_list.length - 1) + { + // If the next group index is on the next page, then display + // the next page. + var nextGrpIndex = selectedGrpIndex + 1; + if (nextGrpIndex > bottomMsgGrpIndex) + { + // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and + // refresh the list on the screen. + topMsgGrpIndex += numItemsPerPage; + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteMsgGroupLine(selectedGrpIndex, false); + } + selectedGrpIndex = nextGrpIndex; + } + break; + case KEY_HOME: // Go to the top message group on the screen + if (selectedGrpIndex > topMsgGrpIndex) + { + // Display the current line un-highlighted, then adjust + // selectedGrpIndex. + console.gotoxy(1, curpos.y); + this.WriteMsgGroupLine(selectedGrpIndex, false); + selectedGrpIndex = topMsgGrpIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_END: // Go to the bottom message group on the screen + if (selectedGrpIndex < bottomMsgGrpIndex) + { + // Display the current line un-highlighted, then adjust + // selectedGrpIndex. + console.gotoxy(1, curpos.y); + this.WriteMsgGroupLine(selectedGrpIndex, false); + selectedGrpIndex = bottomMsgGrpIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_ENTER: // Select the currently-highlighted message group + retObj = this.SelectSubBoard_Lightbar(selectedGrpIndex); + // If the user chose a sub-board, then set bbs.curgrp and + // bbs.cursub, and don't continue the input loop anymore. + if (retObj.subBoardChosen) + { + bbs.curgrp = selectedGrpIndex; + bbs.cursub = retObj.subBoardIndex; + continueChoosingMsgArea = false; + } + else + { + // A sub-board was not chosen, so we'll have to re-draw + // the header and list of message groups. + console.gotoxy(1, 1); + this.WriteGrpListHdrLine(numPages, pageNum); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + } + break; + case KEY_PAGE_DOWN: // Go to the next page + var nextPageTopIndex = topMsgGrpIndex + numItemsPerPage; + if (nextPageTopIndex < msg_area.grp_list.length) + { + // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and + // refresh the list on the screen. + topMsgGrpIndex = nextPageTopIndex; + pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, + listEndRow, false, true); + selectedGrpIndex = topMsgGrpIndex; + } + break; + case KEY_PAGE_UP: // Go to the previous page + var prevPageTopIndex = topMsgGrpIndex - numItemsPerPage; + if (prevPageTopIndex >= 0) + { + // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and + // refresh the list on the screen. + topMsgGrpIndex = prevPageTopIndex; + pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, + listEndRow, false, true); + selectedGrpIndex = topMsgGrpIndex; + } + break; + case 'F': // Go to the first page + if (topMsgGrpIndex > 0) + { + topMsgGrpIndex = 0; + pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + selectedGrpIndex = 0; + } + break; + case 'L': // Go to the last page + if (topMsgGrpIndex < topIndexForLastPage) + { + topMsgGrpIndex = topIndexForLastPage; + pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage); + bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, true, false); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + selectedGrpIndex = topIndexForLastPage; + } + break; + case 'Q': // Quit + continueChoosingMsgArea = false; + break; + case '?': // Show help + this.ShowHelpScreen(true, true); + console.pause(); + // Refresh the screen + this.WriteKeyHelpLine(); + console.gotoxy(1, 1); + this.WriteGrpListHdrLine(numPages, pageNum); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + break; + default: + // If the user entered a numeric digit, then treat it as + // the start of the message group number. + if (userInput.match(/[0-9]/)) + { + var originalCurpos = curpos; + + // Put the user's input back in the input buffer to + // be used for getting the rest of the message number. + console.ungetstr(userInput); + // Move the cursor to the bottom of the screen and + // prompt the user for the message number. + console.gotoxy(1, console.screen_rows); + console.clearline("\1n"); + console.print("\1cChoose group #: \1h"); + userInput = console.getnum(msg_area.grp_list.length); + // If the user made a selection, then let them choose a + // sub-board from the group. + if (userInput > 0) + { + var msgGroupIndex = userInput - 1; + retObj = this.SelectSubBoard_Lightbar(msgGroupIndex); + // If the user chose a sub-board, then set bbs.curgrp and + // bbs.cursub, and don't continue the input loop anymore. + if (retObj.subBoardChosen) + { + bbs.curgrp = msgGroupIndex; + bbs.cursub = retObj.subBoardIndex; + continueChoosingMsgArea = false; + } + else + { + // A sub-board was not chosen, so we'll have to re-draw + // the header and list of message groups. + console.gotoxy(1, 1); + this.WriteGrpListHdrLine(numPages, pageNum); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + } + } + else + { + // The user didn't make a selection. So, we need to refresh + // the screen due to everything being moved up one line. + this.WriteKeyHelpLine(); + console.gotoxy(1, 1); + this.WriteGrpListHdrLine(numPages, pageNum); + this.ListScreenfulOfMsgGrps(topMsgGrpIndex, listStartRow, listEndRow, + false, true); + } + } + break; + } + } +} + +// For the DDMsgAreaChooser class: Lets the user choose a sub-board within a +// message group, with a lightbar interface. Does not set bbs.cursub. +// +// Parameters: +// pGrpIndex: The index of the message group to choose from. This is +// optional; if not specified, bbs.curgrp will be used. +// pMarkIndex: An index of a message group to display the "current" mark +// next to. This is optional; if left off, this will default to +// the current sub-board. +// +// Return value: An object containing the following values: +// subBoardChosen: Boolean - Whether or not a sub-board was chosen. +// subBoardIndex: Numeric - The sub-board that was chosen (if any). +// Will be -1 if none chosen. +function DDMsgAreaChooser_selectSubBoard_Lightbar(pGrpIndex, pMarkIndex) +{ + // Create the return object. + var retObj = new Object(); + retObj.subBoardChosen = false; + retObj.subBoardIndex = -1; + + var grpIndex = 0; + if (typeof(pGrpIndex) == "number") + grpIndex = pGrpIndex; + else if ((bbs.curgrp != null) && (typeof(bbs.curgrp) == "number")) + grpIndex = bbs.curgrp; + // Double-check grpIndex + if (grpIndex < 0) + grpIndex = 0; + else if (grpIndex >= msg_area.grp_list.length) + grpIndex = msg_area.grp_list.length - 1; + + var markIndex = 0; + if ((pMarkIndex != null) && (typeof(pMarkIndex) == "number")) + markIndex = pMarkIndex; + else if ((bbs.cursub != null) && (typeof(bbs.cursub) == "number") && + (bbs.curgrp == pGrpIndex)) + { + markIndex = bbs.cursub; + } + // Double-check markIndex + if (markIndex < 0) + markIndex = 0; + else if (markIndex >= msg_area.grp_list[grpIndex].sub_list.length) + markIndex = msg_area.grp_list[grpIndex].sub_list.length - 1; + + + // Ensure that the sub-board printf information is created for + // this message group. + this.BuildSubBoardPrintfInfoForGrp(grpIndex); + + + // If there are no sub-boards in the given message group, then show + // an error and return. + if (msg_area.grp_list[grpIndex].sub_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no sub-boards in the chosen group.\r\n\1p"); + return retObj; + } + + // Returns the index of the bottommost sub-board that can be displayed on + // the screen. + // + // Parameters: + // pTopSubIndex: The index of the topmost sub-board displayed on screen + // pNumItemsPerPage: The number of items per page + function getBottommostSubIndex(pTopSubIndex, pNumItemsPerPage) + { + var bottomGrpIndex = topSubIndex + pNumItemsPerPage - 1; + // If bottomGrpIndex is beyond the last index, then adjust it. + if (bottomGrpIndex >= msg_area.grp_list[grpIndex].sub_list.length) + bottomGrpIndex = msg_area.grp_list[grpIndex].sub_list.length - 1; + return bottomGrpIndex; + } + + + // Figure out the index of the user's currently-selected sub-board. + var selectedSubIndex = 0; + if ((bbs.cursub != null) && (typeof(bbs.cursub) == "number")) + { + if ((bbs.curgrp != null) && (typeof(bbs.curgrp) == "number") && + (bbs.curgrp == pGrpIndex)) + { + selectedSubIndex = bbs.cursub; + } + } + + var listStartRow = 3; // The row on the screen where the list will start + var listEndRow = console.screen_rows - 1; // Row on screen where list will end + var topSubIndex = 0; // The index of the message group at the top of the list + // Figure out the index of the last message group to appear on the screen. + var numItemsPerPage = listEndRow - listStartRow + 1; + var bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + // Figure out how many pages are needed to list all the sub-boards. + var numPages = Math.ceil(msg_area.grp_list[grpIndex].sub_list.length / numItemsPerPage); + // Figure out the top index for the last page. + var topIndexForLastPage = (numItemsPerPage * numPages) - numItemsPerPage; + + // If the highlighted row is beyond the current screen, then + // go to the appropriate page. + if (selectedSubIndex > bottomSubIndex) + { + var nextPageTopIndex = 0; + while (selectedSubIndex > bottomSubIndex) + { + nextPageTopIndex = topSubIndex + numItemsPerPage; + if (nextPageTopIndex < msg_area.grp_list[grpIndex].sub_list.length) + { + // Adjust topSubIndex and bottomSubIndex, and + // refresh the list on the screen. + topSubIndex = nextPageTopIndex; + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + } + else + break; + } + + // If we didn't find the correct page for some reason, then set the + // variables to display page 1 and select the first message group. + var foundCorrectPage = + ((topSubIndex < msg_area.grp_list[grpIndex].sub_list.length) && + (selectedSubIndex >= topSubIndex) && (selectedSubIndex <= bottomSubIndex)); + if (!foundCorrectPage) + { + topSubIndex = 0; + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + selectedSubIndex = 0; + } + } + + // Clear the screen, write the help line and group list header, and output + // a screenful of message groups. + console.clear("\1n"); + var pageNum = calcPageNum(topSubIndex, numItemsPerPage); + this.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum); + this.WriteKeyHelpLine(); + + var curpos = new Object(); + curpos.x = 1; + curpos.y = 2; + console.gotoxy(curpos); + printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time"); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, listEndRow, + false, false); + // Start of the input loop. + var highlightScrenRow = 0; // The row on the screen for the highlighted group + var userInput = ""; // Will store a keypress from the user + var continueChoosingSubBrd = true; + while (continueChoosingSubBrd) + { + // Highlight the currently-selected message group + highlightScrenRow = listStartRow + (selectedSubIndex - topSubIndex); + curpos.y = highlightScrenRow; + if ((highlightScrenRow > 0) && (highlightScrenRow < console.screen_rows)) + { + console.gotoxy(1, highlightScrenRow); + this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, true); + } + + // Get a key from the user (upper-case) and take action based upon it. + userInput = getKeyWithESCChars(K_UPPER | K_NOCRLF); + switch (userInput) + { + case KEY_UP: // Move up one message group in the list + if (selectedSubIndex > 0) + { + // If the previous group index is on the previous page, then + // display the previous page. + var previousSubIndex = selectedSubIndex - 1; + if (previousSubIndex < topSubIndex) + { + // Adjust topSubIndex and bottomSubIndex, and + // refresh the list on the screen. + topSubIndex -= numItemsPerPage; + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false); + } + selectedSubIndex = previousSubIndex; + } + break; + case KEY_DOWN: // Move down one message group in the list + if (selectedSubIndex < msg_area.grp_list[grpIndex].sub_list.length - 1) + { + // If the next group index is on the next page, then display + // the next page. + var nextGrpIndex = selectedSubIndex + 1; + if (nextGrpIndex > bottomSubIndex) + { + // Adjust topSubIndex and bottomSubIndex, and + // refresh the list on the screen. + topSubIndex += numItemsPerPage; + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + } + else + { + // Display the current line un-highlighted. + console.gotoxy(1, curpos.y); + this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false); + } + selectedSubIndex = nextGrpIndex; + } + break; + case KEY_HOME: // Go to the top message group on the screen + if (selectedSubIndex > topSubIndex) + { + // Display the current line un-highlighted, then adjust + // selectedSubIndex. + console.gotoxy(1, curpos.y); + this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false); + selectedSubIndex = topSubIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_END: // Go to the bottom message group on the screen + if (selectedSubIndex < bottomSubIndex) + { + // Display the current line un-highlighted, then adjust + // selectedSubIndex. + console.gotoxy(1, curpos.y); + this.WriteMsgSubBoardLine(grpIndex, selectedSubIndex, false); + selectedSubIndex = bottomSubIndex; + // Note: curpos.y is set at the start of the while loop. + } + break; + case KEY_ENTER: // Select the currently-highlighted sub-board; and we're done. + continueChoosingSubBrd = false; + retObj.subBoardChosen = true; + retObj.subBoardIndex = selectedSubIndex; + break; + case KEY_PAGE_DOWN: // Go to the next page + var nextPageTopIndex = topSubIndex + numItemsPerPage; + if (nextPageTopIndex < msg_area.grp_list[grpIndex].sub_list.length) + { + // Adjust topSubIndex and bottomSubIndex, and + // refresh the list on the screen. + topSubIndex = nextPageTopIndex; + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + selectedSubIndex = topSubIndex; + } + break; + case KEY_PAGE_UP: // Go to the previous page + var prevPageTopIndex = topSubIndex - numItemsPerPage; + if (prevPageTopIndex >= 0) + { + // Adjust topSubIndex and bottomSubIndex, and + // refresh the list on the screen. + topSubIndex = prevPageTopIndex; + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + selectedSubIndex = topSubIndex; + } + break; + case 'F': // Go to the first page + if (topSubIndex > 0) + { + topSubIndex = 0; + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + selectedSubIndex = 0; + } + break; + case 'L': // Go to the last page + if (topSubIndex < topIndexForLastPage) + { + topSubIndex = topIndexForLastPage; + pageNum = calcPageNum(topSubIndex, numItemsPerPage); + bottomSubIndex = getBottommostSubIndex(topSubIndex, numItemsPerPage); + this.updatePageNumInHeader(pageNum, numPages, false, false); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + selectedSubIndex = topIndexForLastPage; + } + break; + case 'Q': // Quit + continueChoosingSubBrd = false; + break; + case '?': // Show help + this.ShowHelpScreen(true, true); + console.pause(); + // Refresh the screen + console.gotoxy(1, 1); + this.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum); + console.cleartoeol("\1n"); + this.WriteKeyHelpLine(); + console.gotoxy(1, 2); + printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", + "Latest date & time"); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + break; + default: + // If the user entered a numeric digit, then treat it as + // the start of the message group number. + if (userInput.match(/[0-9]/)) + { + var originalCurpos = curpos; + + // Put the user's input back in the input buffer to + // be used for getting the rest of the message number. + console.ungetstr(userInput); + // Move the cursor to the bottom of the screen and + // prompt the user for the message number. + console.gotoxy(1, console.screen_rows); + console.clearline("\1n"); + console.print("\1cSub-board #: \1h"); + userInput = console.getnum(msg_area.grp_list[grpIndex].sub_list.length); + // If the user made a selection, then set it in the + // return object and don't continue the input loop. + if (userInput > 0) + { + continueChoosingSubBrd = false; + retObj.subBoardChosen = true; + retObj.subBoardIndex = userInput - 1; + } + else + { + // The user didn't enter a selection. Now we need to + // re-draw the screen due to everything being moved + // up one line. + console.gotoxy(1, 1); + this.WriteSubBrdListHdr1Line(grpIndex, numPages, pageNum); + console.cleartoeol("\1n"); + this.WriteKeyHelpLine(); + console.gotoxy(1, 2); + printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", + "Latest date & time"); + this.ListScreenfulOfSubBrds(grpIndex, topSubIndex, listStartRow, + listEndRow, false, true); + } + } + break; + } + } + + return retObj; +} + +// For the DDMsgAreaChooser class: Lets the user choose a message group and +// sub-board via numeric input, using a traditional user interface. +function DDMsgAreaChooser_selectMsgArea_Traditional() +{ + // If there are no message groups, then don't let the user + // choose one. + if (msg_area.grp_list.length == 0) + { + console.clear("\1n"); + console.print("\1y\1hThere are no message groups.\r\n\1p"); + return; + } + + // Show the message groups & sub-boards and let the user choose one. + var selectedGrp = 0; // The user's selected message group + var selectedSubBoard = 0; // The user's selected sub-board + var continueChoosingMsgArea = true; + while (continueChoosingMsgArea) + { + // Clear the BBS command string to make sure there are no extra + // commands in there that could cause weird things to happen. + bbs.command_str = ""; + + console.clear("\1n"); + this.ListMsgGrps(); + console.crlf(); + console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + + +(bbs.curgrp+1) + "\1n\1c]: \1h"); + // Accept Q (quit) or a file library number + selectedGrp = console.getkeys("Q", msg_area.grp_list.length); + + // If the user just pressed enter (selectedGrp would be blank), + // default to the current group. + if (selectedGrp.toString() == "") + selectedGrp = bbs.curgrp + 1; + + if (selectedGrp.toString() == "Q") + continueChoosingMsgArea = false; + else + { + // If the user specified a message group number, then + // set it and let the user choose a sub-board within + // the group. + if (selectedGrp > 0) + { + // Set the default sub-board #: The current sub-board, or if the + // user chose a different group, then this should be set + // to the first sub-board. + var defaultSubBoard = bbs.cursub + 1; + if (selectedGrp-1 != bbs.curgrp) + defaultSubBoard = 1; + + console.clear("\1n"); + this.ListSubBoardsInMsgGroup(selectedGrp-1, defaultSubBoard-1); + console.crlf(); + console.print("\1n\1b\1h� \1n\1cWhich, \1hQ\1n\1cuit, or [\1h" + + defaultSubBoard + "\1n\1c]: \1h"); + // Accept Q (quit) or a sub-board number + selectedSubBoard = console.getkeys("Q", msg_area.grp_list[selectedGrp - 1].sub_list.length); + + // If the user just pressed enter (selectedSubBoard would be blank), + // default the selected directory. + if (selectedSubBoard.toString() == "") + selectedSubBoard = defaultSubBoard; + + // If the user chose a directory, then set bbs.curlib & + // bbs.curdir and quit the file library loop. + if ((selectedGrp.toString() != "Q") && (selectedSubBoard > 0)) + { + bbs.curgrp = selectedGrp - 1; + bbs.cursub = selectedSubBoard - 1; + continueChoosingMsgArea = false; + } + } + } + } +} + +// For the DDMsgAreaChooser class: Lists all message groups (for the traditional +// user interface). +function DDMsgAreaChooser_listMsgGrps_Traditional() +{ + // Print the header + this.WriteGrpListHdrLine(); + console.print("\1n"); + // List the message groups + for (var i = 0; i < msg_area.grp_list.length; ++i) + { + console.crlf(); + this.WriteMsgGroupLine(i, false); + } +} + +// For the DDMsgAreaChooser class: Lists the sub-boards in a message group, +// for the traditional user interface. +// +// Parameters: +// pGrpIndex: The index of the message group (0-based) +// pMarkIndex: An index of a message group to highlight. This +// is optional; if left off, this will default to +// the current sub-board. +// pSortType: Optional - A string describing how to sort the list (if desired): +// "none": Default behavior - Sort by sub-board # +// "dateAsc": Sort by date, ascending +// "dateDesc": Sort by date, descending +// "description": Sort by description +function DDMsgAreaChooser_listSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIndex, pSortType) +{ + // Default to the current message group & sub-board if pGrpIndex + // and pMarkIndex aren't specified. + var grpIndex = bbs.curgrp; + if ((pGrpIndex != null) && (typeof(pGrpIndex) == "number")) + grpIndex = pGrpIndex; + var highlightIndex = bbs.cursub; + if ((pMarkIndex != null) && (typeof(pMarkIndex) == "number")) + highlightIndex = pMarkIndex; + + // Make sure grpIndex and highlightIndex are valid (they might not be for + // brand-new users). + if ((grpIndex == null) || (typeof(grpIndex) == "undefined")) + grpIndex = 0; + if ((highlightIndex == null) || (typeof(highlightIndex) == "undefined")) + highlightIndex = 0; + + // Ensure that the sub-board printf information is created for + // this message group. + this.BuildSubBoardPrintfInfoForGrp(grpIndex); + + // Print the headers + this.WriteSubBrdListHdr1Line(grpIndex); + console.crlf(); + printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time"); + console.print("\1n"); + + // List each sub-board in the message group. + var subBoardArray = null; // For sorting, if desired + var newestDate = new Object(); // For storing the date of the newest post in a sub-board + var msgBase = null; // For opening the sub-boards with a MsgBase object + var msgHeader = null; // For getting the date & time of the newest post in a sub-board + var subBoardNum = 0; // 0-based sub-board number (because the array index is the number as a str) + // If a sort type is specified, then add the sub-board information to + // subBoardArray so that it can be sorted. + if ((typeof(pSortType) == "string") && (pSortType != "") && (pSortType != "none")) + { + subBoardArray = new Array(); + var subBoardInfo = null; + for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list) + { + // Open the current sub-board with the msgBase object. + msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code); + if (msgBase.open()) + { + subBoardInfo = new MsgSubBoardInfo(); + subBoardInfo.subBoardNum = +(arrSubBoardNum); + subBoardInfo.description = msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description; + subBoardInfo.numPosts = msgBase.total_msgs; + + // Get the date & time when the last message was imported. + if (msgBase.total_msgs > 0) + { + msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true); + if (this.showImportDates) + subBoardInfo.newestPostDate = msgHeader.when_imported_time + else + subBoardInfo.newestPostDate = msgHeader.when_written_time; + } + } + msgBase.close(); + subBoardArray.push(subBoardInfo); + } + // Free some memory? + delete msgBase; + + // Sort sub-board list. + if (pSortType == "dateAsc") + { + subBoardArray.sort(function(pA, pB) + { + // Return -1, 0, or 1, depending on whether pA's date comes + // before, is equal to, or comes after pB's date. + var returnValue = 0; + if (pA.newestPostDate < pB.newestPostDate) + returnValue = -1; + else if (pA.newestPostDate > pB.newestPostDate) + returnValue = 1; + return returnValue; + }); + } + else if (pSortType == "dateDesc") + { + subBoardArray.sort(function(pA, pB) + { + // Return -1, 0, or 1, depending on whether pA's date comes + // after, is equal to, or comes before pB's date. + var returnValue = 0; + if (pA.newestPostDate > pB.newestPostDate) + returnValue = -1; + else if (pA.newestPostDate < pB.newestPostDate) + returnValue = 1; + return returnValue; + }); + } + else if (pSortType == "description") + { + // Binary safe string comparison + // + // version: 909.322 + // discuss at: http://phpjs.org/functions/strcmp // + original by: Waldo Malqui Silva + // + input by: Steve Hilder + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + revised by: gorthaur + // * example 1: strcmp( 'waldo', 'owald' ); // * returns 1: 1 + // * example 2: strcmp( 'owald', 'waldo' ); + // * returns 2: -1 + subBoardArray.sort(function(pA, pB) + { + return ((pA.description == pB.description) ? 0 : ((pA.description > pB.description) ? 1 : -1)); + }); + } + + // Display the sub-board list. + for (var i = 0; i < subBoardArray.length; ++i) + { + console.crlf(); + console.print((subBoardArray[i].subBoardNum == highlightIndex) ? "\1n" + this.colors.areaMark + "*" : " "); + printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardArray[i].subBoardNum+1), + subBoardArray[i].description.substr(0, this.subBoardNameLen), + subBoardArray[i].numPosts, strftime("%Y-%m-%d", subBoardArray[i].newestPostDate), + strftime("%H:%M:%S", subBoardArray[i].newestPostDate)); + } + } + // If no sort type is specified, then output the sub-board information in + // order of sub-board number. + else + { + for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list) + { + // Open the current sub-board with the msgBase object. + msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code); + if (msgBase.open()) + { + // Get the date & time when the last message was imported. + if (msgBase.total_msgs > 0) + { + msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true); + // Construct the date & time strings of the latest post + if (this.showImportDates) + { + 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); + } + } + else + newestDate.date = newestDate.time = ""; + + // Print the sub-board information + subBoardNum = +(arrSubBoardNum); + console.crlf(); + console.print((subBoardNum == highlightIndex) ? "\1n" + this.colors.areaMark + "*" : " "); + printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardNum+1), + msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen), + msgBase.total_msgs, newestDate.date, newestDate.time); + + msgBase.close(); + } + + // Free some memory? + delete msgBase; + } + } +} + +////////////////////////////////////////////// +// Message group list stuff (lightbar mode) // +////////////////////////////////////////////// + +// Displays a screenful of message groups, for the lightbar interface. +// +// Parameters: +// pStartIndex: The message group index to start at (0-based) +// pStartScreenRow: The row on the screen to start at (1-based) +// pEndScreenRow: The row on the screen to end at (1-based) +// pClearScreenFirst: Boolean - Whether or not to clear the screen first +// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end +// screen row if there aren't enough message groups to fill +// the screen. +function DDMsgAreaChooser_listScreenfulOfMsgGrps(pStartIndex, pStartScreenRow, + pEndScreenRow, pClearScreenFirst, + pBlankToEndRow) +{ + // Check the parameters; If they're bad, then just return. + if ((typeof(pStartIndex) != "number") || + (typeof(pStartScreenRow) != "number") || + (typeof(pEndScreenRow) != "number")) + { + return; + } + if ((pStartIndex < 0) || (pStartIndex >= msg_area.grp_list.length)) + return; + if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows)) + return; + if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows)) + return; + + // If pStartScreenRow is greather than pEndScreenRow, then swap them. + if (pStartScreenRow > pEndScreenRow) + { + var temp = pStartScreenRow; + pStartScreenRow = pEndScreenRow; + pEndScreenRow = temp; + } + + // Calculate the ending index to use for the message groups array. + var endIndex = pStartIndex + (pEndScreenRow-pStartScreenRow); + if (endIndex >= msg_area.grp_list.length) + endIndex = msg_area.grp_list.length - 1; + var onePastEndIndex = endIndex + 1; + + // Check to make sure bbs.curgrp is valid (it might not be for brand-new users). + var curgrpValid = ((bbs.curgrp != null) && (typeof(bbs.curgrp) != "undefined")); + + // Clear the screen, go to the specified screen row, and display the message + // group information. + if (pClearScreenFirst) + console.clear("\1n"); + console.gotoxy(1, pStartScreenRow); + var grpIndex = pStartIndex; + for (; grpIndex < onePastEndIndex; ++grpIndex) + { + this.WriteMsgGroupLine(grpIndex, false); + if (grpIndex < endIndex) + console.crlf(); + } + + // If pBlankToEndRow is true and we're not at the end row yet, then + // write blank lines to the end row. + if (pBlankToEndRow) + { + var screenRow = pStartScreenRow + (endIndex - pStartIndex) + 1; + if (screenRow <= pEndScreenRow) + { + for (; screenRow <= pEndScreenRow; ++screenRow) + { + console.gotoxy(1, screenRow); + console.clearline("\1n"); + } + } + } +} + +// For the DDMsgAreaChooser class - Writes a message group information line. +// +// Parameters: +// pGrpIndex: The index of the message group to write (assumed to be valid) +// pHighlight: Boolean - Whether or not to write the line highlighted. +function DDMsgAreaChooser_writeMsgGroupLine(pGrpIndex, pHighlight) +{ + console.print("\1n"); + // Write the highlight background color if pHighlight is true. + if (pHighlight) + console.print(this.colors.bkgHighlight); + + // Write the message group information line + console.print(((typeof(bbs.curgrp) == "number") && (pGrpIndex == bbs.curgrp)) ? this.colors.areaMark + "*" : " "); + printf((pHighlight ? this.msgGrpListHilightPrintfStr : this.msgGrpListPrintfStr), + +(pGrpIndex+1), + msg_area.grp_list[pGrpIndex].description.substr(0, this.msgGrpDescLen), + msg_area.grp_list[pGrpIndex].sub_list.length); + console.cleartoeol("\1n"); +} + +////////////////////////////////////////////////// +// Message sub-board list stuff (lightbar mode) // +////////////////////////////////////////////////// + +// Updates the page number text in the group list header line on the screen. +// +// Parameters: +// pPageNum: The page number +// pNumPages: The total number of pages +// pGroup: Boolean - Whether or not this is for the group header. If so, +// then this will go to the right location for the group page text +// and use this.colors.header for the text. Otherwise, this will +// go to the right place for the sub-board page text and use the +// sub-board header color. +// pRestoreCurPos: Optional - Boolean - If true, then move the cursor back +// to the position where it was before this function was called +function DDMsgAreaChooser_updatePageNumInHeader(pPageNum, pNumPages, pGroup, pRestoreCurPos) +{ + var originalCurPos = null; + if (pRestoreCurPos) + originalCurPos = console.getxy(); + + if (pGroup) + { + console.gotoxy(29, 1); + console.print("\1n" + this.colors.header + pPageNum + " of " + pNumPages + ") "); + } + else + { + console.gotoxy(51, 1); + console.print("\1n" + this.colors.subBoardHeader + pPageNum + " of " + pNumPages + ") "); + } + + if (pRestoreCurPos) + console.gotoxy(originalCurPos); +} + +// Displays a screenful of message sub-boards, for the lightbar interface. +// +// Parameters: +// pGrpIndex: The index of the message group (0-based) +// pStartSubIndex: The message sub-board index to start at (0-based) +// pStartScreenRow: The row on the screen to start at (1-based) +// pEndScreenRow: The row on the screen to end at (1-based) +// pClearScreenFirst: Boolean - Whether or not to clear the screen first +// pBlankToEndRow: Boolean - Whether or not to write blank lines to the end +// screen row if there aren't enough message groups to fill +// the screen. +function DDMsgAreaChooser_listScreenfulOfSubBrds(pGrpIndex, pStartSubIndex, + pStartScreenRow, pEndScreenRow, + pClearScreenFirst, pBlankToEndRow) +{ + // Check the parameters; If they're bad, then just return. + if ((typeof(pGrpIndex) != "number") || + (typeof(pStartSubIndex) != "number") || + (typeof(pStartScreenRow) != "number") || + (typeof(pEndScreenRow) != "number")) + { + return; + } + if ((pGrpIndex < 0) || (pGrpIndex >= msg_area.grp_list.length)) + return; + if ((pStartSubIndex < 0) || + (pStartSubIndex >= msg_area.grp_list[pGrpIndex].sub_list.length)) + { + return; + } + if ((pStartScreenRow < 1) || (pStartScreenRow > console.screen_rows)) + return; + if ((pEndScreenRow < 1) || (pEndScreenRow > console.screen_rows)) + return; + // If pStartScreenRow is greather than pEndScreenRow, then swap them. + if (pStartScreenRow > pEndScreenRow) + { + var temp = pStartScreenRow; + pStartScreenRow = pEndScreenRow; + pEndScreenRow = temp; + } + + // Calculate the ending index to use for the sub-board array. + var endIndex = pStartSubIndex + (pEndScreenRow-pStartScreenRow); + if (endIndex >= msg_area.grp_list[pGrpIndex].sub_list.length) + endIndex = msg_area.grp_list[pGrpIndex].sub_list.length - 1; + var onePastEndIndex = endIndex + 1; + + // Clear the screen and go to the specified screen row. + if (pClearScreenFirst) + console.clear("\1n"); + console.gotoxy(1, pStartScreenRow); + + // Start listing the sub-boards. + + var subIndex = pStartSubIndex; + for (; subIndex < onePastEndIndex; ++subIndex) + { + this.WriteMsgSubBoardLine(pGrpIndex, subIndex, false); + if (subIndex < endIndex) + console.crlf(); + } + + // If pBlankToEndRow is true and we're not at the end row yet, then + // write blank lines to the end row. + if (pBlankToEndRow) + { + var screenRow = pStartScreenRow + (endIndex - pStartSubIndex) + 1; + if (screenRow <= pEndScreenRow) + { + for (; screenRow <= pEndScreenRow; ++screenRow) + { + console.gotoxy(1, screenRow); + console.clearline("\1n"); + } + } + } +} + +// For the DDMsgAreaChooser class: Writes a message sub-board information line. +// +// Parameters: +// pGrpIndex: The index of the message group (assumed to be valid) +// pSubIndex: The index of the sub-board within the message group to write (assumed to be valid) +// pHighlight: Boolean - Whether or not to write the line highlighted. +function DDMsgAreaChooser_writeMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight) +{ + console.print("\1n"); + // Write the highlight background color if pHighlight is true. + if (pHighlight) + console.print(this.colors.bkgHighlight); + + // Determine if pGrpIndex and pSubIndex specify the user's + // currently-selected group and sub-board. + var currentSub = false; + if ((typeof(bbs.curgrp) == "number") && (typeof(bbs.cursub) == "number")) + currentSub = ((pGrpIndex == bbs.curgrp) && (pSubIndex == bbs.cursub)); + + // Open the current sub-board with the msgBase object (so that we can get + // the date & time of the last imporeted message). + var msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code); + if (msgBase.open()) + { + var newestDate = new Object(); // For storing the date of the newest post + // Get the date & time when the last message was imported. + if (msgBase.total_msgs > 0) + { + msgHeader = msgBase.get_msg_header(true, msgBase.total_msgs-1, true); + // Construct the date & time strings of the latest post + if (this.showImportDates) + { + 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); + } + } + else + newestDate.date = newestDate.time = ""; + + // Print the sub-board information line. + console.print(currentSub ? this.colors.areaMark + "*" : " "); + printf((pHighlight ? this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr : this.subBoardListPrintfInfo[pGrpIndex].printfStr), + +(pSubIndex+1), + msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, this.subBoardListPrintfInfo[pGrpIndex].nameLen), + msgBase.total_msgs, newestDate.date, newestDate.time); + msgBase.close(); + + // Free some memory? + delete msgBase; + } +} + +/////////////////////////////////////////////// +// Other functions for the msg. area chooser // +/////////////////////////////////////////////// + +// For the DDMsgAreaChooser class: Reads the configuration file. +function DDMsgAreaChooser_ReadConfigFile() +{ + // Determine the script's startup directory. + // This code is a trick that was created by Deuce, suggested by Rob Swindell + // as a way to detect which directory the script was executed in. I've + // shortened the code a little. + var startup_path = '.'; + try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; } + startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,'')); + + // Open the configuration file + var cfgFile = new File(startup_path + "DDMsgAreaChooser.cfg"); + if (cfgFile.open("r")) + { + var settingsMode = "behavior"; + var fileLine = null; // A line read from the file + var equalsPos = 0; // Position of a = in the line + var commentPos = 0; // Position of the start of a comment + var setting = null; // A setting name (string) + var settingUpper = null; // Upper-case setting name + var value = null; // A value for a setting (string) + while (!cfgFile.eof) + { + // Read the next line from the config file. + fileLine = cfgFile.readln(2048); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // If in the "behavior" section, then set the behavior-related variables. + if (fileLine.toUpperCase() == "[BEHAVIOR]") + { + settingsMode = "behavior"; + continue; + } + else if (fileLine.toUpperCase() == "[COLORS]") + { + settingsMode = "colors"; + continue; + } + + // If the line has a semicolon anywhere in it, then remove + // everything from the semicolon onward. + commentPos = fileLine.indexOf(";"); + if (commentPos > -1) + fileLine = fileLine.substr(0, commentPos); + + // Look for an equals sign, and if found, separate the line + // into the setting name (before the =) and the value (after the + // equals sign). + equalsPos = fileLine.indexOf("="); + if (equalsPos > 0) + { + // Read the setting & value, and trim leading & trailing spaces. + setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); + settingUpper = setting.toUpperCase(); + value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); + + if (settingsMode == "behavior") + { + // Set the appropriate value in the settings object. + if (settingUpper == "USELIGHTBARINTERFACE") + this.useLightbarInterface = (value.toUpperCase() == "TRUE"); + else if (settingUpper == "SHOWIMPORTDATES") + this.showImportDates = (value.toUpperCase() == "TRUE"); + } + else if (settingsMode == "colors") + this.colors[setting] = value; + } + } + + cfgFile.close(); + } +} + +// For the DDMsgAreaChooser class: Shows the help screen +// +// Parameters: +// pLightbar: Boolean - Whether or not to show lightbar help. If +// false, then this function will show regular help. +// pClearScreen: Boolean - Whether or not to clear the screen first +function DDMsgAreaChooser_showHelpScreen(pLightbar, pClearScreen) +{ + if (pClearScreen) + console.clear("\1n"); + else + console.print("\1n"); + console.center("\1c\1hDigital Distortion Message Area Chooser"); + console.center("\1k���������������������������������������"); + console.center("\1n\1cVersion \1g" + DD_MSG_AREA_CHOOSER_VERSION + + " \1w\1h(\1b" + DD_MSG_AREA_CHOOSER_VER_DATE + "\1w)"); + console.crlf(); + console.print("\1n\1cFirst, a listing of message groups is displayed. One can be chosen by typing"); + console.crlf(); + console.print("its number. Then, a listing of sub-boards within that message group will be"); + console.crlf(); + console.print("shown, and one can be chosen by typing its number."); + console.crlf(); + + if (pLightbar) + { + console.crlf(); + console.print("\1n\1cThe lightbar interface also allows up & down navigation through the lists:"); + console.crlf(); + console.print("\1k\1h��������������������������������������������������������������������������"); + console.crlf(); + console.print("\1n\1c\1hUp arrow\1n\1c: Move the cursor up one line"); + console.crlf(); + console.print("\1hDown arrow\1n\1c: Move the cursor down one line"); + console.crlf(); + console.print("\1hENTER\1n\1c: Select the current group/sub-board"); + console.crlf(); + console.print("\1hHOME\1n\1c: Go to the first item on the screen"); + console.crlf(); + console.print("\1hEND\1n\1c: Go to the last item on the screen"); + console.crlf(); + console.print("\1hPageUp\1n\1c/\1hPageDown\1n\1c: Go to the previous/next page"); + console.crlf(); + console.print("\1hF\1n\1c/\1hL\1n\1c: Go to the first/last page"); + console.crlf(); + } + + console.crlf(); + console.print("Additional keyboard commands:"); + console.crlf(); + console.print("\1k\1h�����������������������������"); + console.crlf(); + console.print("\1n\1c\1h?\1n\1c: Show this help screen"); + console.crlf(); + console.print("\1hQ\1n\1c: Quit"); + console.crlf(); +} + +// Builds sub-board printf format information for a message group. +// The widths of the description & # messages columns are calculated +// based on the greatest number of messages in a sub-board for the +// message group. +// +// Parameters: +// pGrpIndex: The index of the message group +function DDMsgAreaChooser_buildSubBoardPrintfInfoForGrp(pGrpIndex) +{ + // If the array of sub-board printf strings doesn't contain the printf + // strings for this message group, then figure out the largest number + // of messages in the message group and add the printf strings. + if (typeof(this.subBoardListPrintfInfo[pGrpIndex]) == "undefined") + { + var greatestNumMsgs = getGreatestNumMsgs(pGrpIndex); + + this.subBoardListPrintfInfo[pGrpIndex] = new Object(); + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen = greatestNumMsgs.toString().length; + // Sub-board name length: With a # items length of 4, this should be + // 47 for an 80-column display. + this.subBoardListPrintfInfo[pGrpIndex].nameLen = console.screen_columns - + this.areaNumLen - + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen - + this.dateLen - this.timeLen - 7; + // Create the printf strings + this.subBoardListPrintfInfo[pGrpIndex].printfStr = + " " + this.colors.areaNum + + "%" + this.areaNumLen + "d " + + this.colors.desc + "%-" + + this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s " + + this.colors.numItems + "%" + + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d " + + this.colors.latestDate + "%" + this.dateLen + "s " + + this.colors.latestTime + "%" + this.timeLen + "s"; + this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr = + "\1n" + this.colors.bkgHighlight + " " + + "\1n" + this.colors.bkgHighlight + + this.colors.areaNumHighlight + + "%" + this.areaNumLen + "d \1n" + + this.colors.bkgHighlight + + this.colors.descHighlight + "%-" + + this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s \1n" + + this.colors.bkgHighlight + + this.colors.numItemsHighlight + "%" + + this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d \1n" + + this.colors.bkgHighlight + + this.colors.dateHighlight + "%" + this.dateLen + "s \1n" + + this.colors.bkgHighlight + + this.colors.timeHighlight + "%" + this.timeLen + "s\1n"; + } +} + +// Removes multiple, leading, and/or trailing spaces. +// The search & replace regular expressions used in this +// function came from the following URL: +// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces +// +// Parameters: +// pString: The string to trim +// pLeading: Whether or not to trim leading spaces (optional, defaults to true) +// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true) +// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true) +function trimSpaces(pString, pLeading, pMultiple, pTrailing) +{ + var leading = true; + var multiple = true; + var trailing = true; + if(typeof(pLeading) != "undefined") + leading = pLeading; + if(typeof(pMultiple) != "undefined") + multiple = pMultiple; + if(typeof(pTrailing) != "undefined") + trailing = pTrailing; + + // To remove both leading & trailing spaces: + //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); + + if (leading) + pString = pString.replace(/(^\s*)/gi,""); + if (multiple) + pString = pString.replace(/[ ]{2,}/gi," "); + if (trailing) + pString = pString.replace(/(\s*$)/gi,""); + + return pString; +} + +// Calculates & returns a page number. +// +// Parameters: +// pTopIndex: The index (0-based) of the topmost item on the page +// pNumPerPage: The number of items per page +// +// Return value: The page number +function calcPageNum(pTopIndex, pNumPerPage) +{ + return ((pTopIndex / pNumPerPage) + 1); +} + +// Returns the greatest number of messages of all sub-boards within +// a message group. +// +// Parameters: +// pGrpIndex: The index of the message group +// +// Returns: The greatest number of messages of all sub-boards within +// the message group +function getGreatestNumMsgs(pGrpIndex) +{ + // Sanity checking + if (typeof(pGrpIndex) != "number") + return 0; + if (typeof(msg_area.grp_list[pGrpIndex]) == "undefined") + return 0; + + var greatestNumMsgs = 0; + var msgBase = null; + for (var subIndex = 0; subIndex < msg_area.grp_list[pGrpIndex].sub_list.length; ++subIndex) + { + msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[subIndex].code); + if (msgBase == null) continue; + if (msgBase.open()) + { + if (msgBase.total_msgs > greatestNumMsgs) + greatestNumMsgs = msgBase.total_msgs; + msgBase.close(); + } + } + return greatestNumMsgs; +} + +// Inputs a keypress from the user and handles some ESC-based +// characters such as PageUp, PageDown, and ESC. If PageUp +// or PageDown are pressed, this function will return the +// string "\1PgUp" (KEY_PAGE_UP) or "\1Pgdn" (KEY_PAGE_DOWN), +// respectively. Also, F1-F5 will be returned as "\1F1" +// through "\1F5", respectively. +// Thanks goes to Psi-Jack for the original impementation +// of this function. +// +// Parameters: +// pGetKeyMode: Optional - The mode bits for console.getkey(). +// If not specified, K_NONE will be used. +// +// Return value: The user's keypress +function getKeyWithESCChars(pGetKeyMode) +{ + var getKeyMode = K_NONE; + if (typeof(pGetKeyMode) == "number") + getKeyMode = pGetKeyMode; + + var userInput = console.getkey(getKeyMode); + if (userInput == KEY_ESC) { + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case '[': + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case 'V': + userInput = KEY_PAGE_UP; + break; + case 'U': + userInput = KEY_PAGE_DOWN; + break; + } + break; + case 'O': + switch (console.inkey(K_NOECHO|K_NOSPIN, 2)) { + case 'P': + userInput = "\1F1"; + break; + case 'Q': + userInput = "\1F2"; + break; + case 'R': + userInput = "\1F3"; + break; + case 'S': + userInput = "\1F4"; + break; + case 't': + userInput = "\1F5"; + break; + } + default: + break; + } + } + + return userInput; +} \ No newline at end of file diff --git a/xtrn/DDAreaChoosers/FILE_ID.DIZ b/xtrn/DDAreaChoosers/FILE_ID.DIZ new file mode 100644 index 0000000000000000000000000000000000000000..2f671a872aaac1513b7967567819a8ed51e4e44d --- /dev/null +++ b/xtrn/DDAreaChoosers/FILE_ID.DIZ @@ -0,0 +1,10 @@ + Digital Distortion Area Choosers v1.08 +����������������������������������������� + For Synchronet 3.14+ +These are a couple of JavaScript scripts +that let the user choose their file & message +area, with a lightbar interface and +configurable colors. The message area chooser +shows latest post dates & times in the +sub-board lists. +Release date: 2015-04-19 \ No newline at end of file diff --git a/xtrn/DDAreaChoosers/Read Me.txt b/xtrn/DDAreaChoosers/Read Me.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6670d06a90ea5a397198c0b2377c3ce27c6aaa8 --- /dev/null +++ b/xtrn/DDAreaChoosers/Read Me.txt @@ -0,0 +1,438 @@ + Digital Distortion Area Choosers + Version 1.08 + Release date: 2015-04-19 + + by + + Eric Oulashin + Sysop of Digital Distortion BBS + BBS internet address: digdist.bbsindex.com + Email: eric.oulashin@gmail.com + + + +This file describes the Digital Distortion area chooser scripts. + +Contents +======== +1. Disclaimer +2. Introduction +3. Installation & Setup +4. Configuration file +5. DDMsgAreaChooser class: Properties & methods +6. DDFileAreaChooser class: Properties & methods +7. Revision History + + +1. Disclaimer +============= +The only guarantee that I can make about these scripts is that they will take +up space on your computer. I have tested these with the Windows verison of +Synchronet 3.15 and 3.16 in Windows 2000 and Windows XP. +I created these scripts to customize the message & file area selection on my +BBS and am providing them to the Synchronet BBS community in case other +Synchronet sysops might find them useful. + + +2. Introduction +=============== +The Digital Distortion message & file area chooser scripts provide a lightbar +or traditional user interface to let the user choose their message and file +area. Also, the colors are customizable. Additionally, the message area +chooser will show the date & time of the latest post for each of the +sub-boards. + +The file & message area chooser scripts can be run in several ways: +- Executed from a JavaScript/Baja script +- Loaded in a JavaScript script, then use the DDMsgAreaChooser or + DDFileAreaChooser object to let the user choose their message/file area +- Set up as a door + +These scripts require Synchronet version 3.14 or higher. + + +3. Installation & Setup +======================= +Step 1: Copy the following files to a directory of your choice (i.e., sbbs/exec +or sbbs/mods): +DDFileAreaChooser.js +DDMsgAreaChooser.js +DDMsgAreaChooser.cfg +DDFileAreaChooser.cfg + +Step 2: Set up the scripts to run on your BBS. To do so, you will need to +update your command shell to execute the included script instead of +Synchronet's built-in area chooser functionality. Synchronet does not use a +traditional menu system like some other BBS packages do; rather, Synchronet +uses a "command shell", which is a script that handles user input, performs +actions based on the user's input, displays menu files, etc. If you have not +modified your Synchronet installation much, you may be using the default +command shell, which (at the time of this writing) is default.src and its +compiled version, default.bin, which is written in Baja (Synchronet's own +scripting language). It's also possible to use a command shell written in +JavaScript - Synchronet includes a few examples, such as classic_shell.js. + +For these examples, I will use DDMsgAreaChooser.js. + +To use these scripts in a Baja script (such as the 'default' script mentioned +above), you can run the area chooser as follows: +exec "?DDMsgAreaChooser.js" + +If you're using a JavaScript command shell, you can run it this way: +bbs.exec("?DDMsgAreaChooser.js"); + +To use the message area chooser in the 'default' command shell, search for the +text "msg_select_area" (without the double-quotes). It should be underneath a +line that says "cmdkey J" (meaning the J key is used to jump to another message +area). Replace msg_select_area with this line: +exec "?DDMsgAreaChooser.js" + +The process is similar for the file area chooser - Search for file_select_area +and replace that with the following: +exec "?DDFileAreaChooser.js" + + +If you are using the JavaScript command shell classic_shell.js, do the +following for message area selection (some knowledge of the JavaScript language +will be helpful): +- Search for the text "case 'J':" (without the double-quotes) +- Comment out the text from the next line until (and not including) the next + case statement +- Underneath the "case 'J':" line, put the following two lines: +bbs.exec("?DDMsgAreaChooser.js"); +break; + +In classic_shell.js, the process will be similar for file area selection. + + + +Advanced installation notes (optional) +-------------------------------------- +The area chooser functionality is encapsulated into JavaScript objects so +that you can customize the settings & colors within your script if you like. +To do this, follow these steps: + 1. Include the following line in your JavaScript script (preferably near the + top): + load("DDMsgAreaChooser.js", false); + 2. Where you want to have area choosing functionality, instantiate the object + and call its SelectMsgArea() function (for the message area chooser) or its + SelectFileArea() function (for the file area chooser). An example: + var msgAreaChooser = new DDMsgAreaChooser(); + msgAreaChooser.SelectMsgArea(); +You can also list message groups (using the same colors as the chooser) as +follows: + msgAreaChooser.ListMsgGrps(); +You can also use the message lister object to list the sub-boards in the +current message group (using the same colors as the chooser) as follows: + msgAreaChooser.ListSubBoardsInMsgGroup(); +For listing file libraries and file directories inside of a library, you can +do the following: + var fileAreaChooser = new DDFileAreaChooser(); + fileAreaChooser.ListFileLibs(); // List file libraries + fileAreaChooser.ListDirsInFileLib(); // List directories inside the current library + + +If you would like to set up these scripts as doors, the following is an example +setup of the message area chooser (assuming it is placed in sbbs/exec or +sbbs/mods): ++[�][?]----------------------------------------------------+ +� Message Area Chooser � +�----------------------------------------------------------� +� �Name Message Area Chooser � +� �Internal Code MSGARCHO � +� �Start-up Directory � +� �Command Line ?DDMsgAreaChooser.js � +� �Clean-up Command Line � +� �Execution Cost None � +� �Access Requirements � +� �Execution Requirements � +� �Multiple Concurrent Users Yes � +� �Intercept Standard I/O No � +� �Native (32-bit) Executable No � +� �Use Shell to Execute No � +� �Modify User Data No � +� �Execute on Event No � +� �Pause After Execution No � +� �BBS Drop File Type None � +� �Place Drop File In Node Directory � ++----------------------------------------------------------+ +To run that from a JavaScript, include this line: +bbs.exec_xtrn("MSGARCHO"); +To run that from a Baja script, include this line: +exec_xtrn MSGARCHO + + +4. Configuration file +===================== +If you want to change the default beavior and colors for one of these scripts, +you can edit its configuration file, which is a plain text file. The +configuration files have two sections: A behavior section (denoted by +[BEHAVIOR]) and a colors section (denoted by [COLORS]). For each setting or +color, the syntax is as folows: + +setting=value + +where "setting" is the behavior setting or color, and "value" is the corresponding +value for the setting/color. The colors are Synchronet color codes. + +Also, comments are allowed in the configuration file. Comments begin with a +semicolon (;). + +Behavior section: Message area chooser +-------------------------------------- +Setting Description +------- ----------- +useLightbarInterface true/false: Whether or not to use a + lightbar user interface. + +showImportDates true/false: Whether or not to show the + import dates (rather than message dates) + in the latest date & time column in the + sub-board lists. + +Colors section: Message area chooser +------------------------------------ +Color setting Description +------------- ----------- +areaNum The color to use for area numbers + +desc The color to use for descriptions + +numItems The color to use for the item counts + +header The color to use for list headers + +subBoardHeader The color to use for the header in the + sub-board list showing "Sub-boards of" + with the group description and page number + (note that the group description will have + he bright attribute applied) + +areaMark The color to use for the marker character + used to show the area that is currently + selected + +latestDate The color to use for the latest post date + +latestTime The color to use for the latest post time + +bkgHighlight The background highlight color for + lightbar mode + +areaNumHighlight The color to use for an area number for + a selected item in lightbar mode + +descHighlight The color to use for a description for + a selected item in lightbar mode + +dateHighlight The color to use for the date for a + selected item in lightbar mode + +timeHighlight The color to use for the time for a + selected item in lightbar mode + +numItemsHighlight The color to use for the number of items + for a selected item in lightbar mode + +lightbarHelpLineBkg The background color to use for the help + text line displayed at the bottom of the + screen in lightbar mode + +lightbarHelpLineGeneral The color to use for general text in the + help text line displayed at the bottom of + the screen in lightbar mode + +lightbarHelpLineHotkey The color to use for hotkeys in the help + text line displayed at the bottom of the + screen in lightbar mode + +lightbarHelpLineParen The color to use for the ) characters in + the help text line displayed at the bottom + of the screen in lightbar mode + +Behavior section: File area chooser +----------------------------------- +Setting Description +------- ----------- +useLightbarInterface true/false: Whether or not to use a + lightbar user interface. + +Colors section: File area chooser +------------------------------------ +Color setting Description +------------- ----------- +areaNum The color to use for area numbers + +desc The color to use for descriptions + +numItems The color to use for the item counts + +header The color to use for list headers + +fileAreaHdr The color to use for the header in the + directory list showing "Directories of" + with the group description and page number + (note that the group description will have + the bright attribute applied) + +areaMark The color to use for the marker character + used to show the area that is currently + selected + +bkgHighlight The background highlight color for + lightbar mode + +areaNumHighlight The color to use for an area number for + a selected item in lightbar mode + +descHighlight The color to use for a description for + a selected item in lightbar mode + +numItemsHighlight The color to use for the number of items + for a selected item in lightbar mode + +lightbarHelpLineBkg The background color to use for the help + text line displayed at the bottom of the + screen in lightbar mode + +lightbarHelpLineGeneral The color to use for general text in the + help text line displayed at the bottom of + the screen in lightbar mode + +lightbarHelpLineHotkey The color to use for hotkeys in the help + text line displayed at the bottom of the + screen in lightbar mode + +lightbarHelpLineParen The color to use for the ) characters in + the help text line displayed at the bottom + of the screen in lightbar mode + + +5. DDMsgAreaChooser class: Properties & methods +=============================================== +The following are the properties and methods of the DDMsgAreaChooser class, which +is the class used for letting the user choose a message area: +Property name Description +------------- ----------- +showImportDates Boolean: Whether or not to show the + import dates (rather than message dates) + in the latest date & time column in the + sub-board lists. + +useLightbarInterface Boolean: Whether or not to use a + lightbar user interface. + +Methods +------- +Method name Description +----------- ----------- +DDMsgAreaChooser() Constructor + +SelectMsgArea() Lets the user choose a message sub-board. + If the useLightbarInterface property is + true and the user's terminal supports + ANSI, it will use the lightbar interface; + otherwise, it will use traditional + interface. + +SelectMsgArea_Lightbar() Lets the user choose a message sub-board, + with a lightbar user interface. + +SelectMsgArea_Traditional() Lets the user choose a message sub-board, + with a traditional user interface. + +ListMsgGrps() Lists the message groups + +ListSubBoardsInMsgGroup(pGrpIndex, Lists the sub-boards in the user's + pMarkIndex, currently-selected message group. + pSortType) The parameters are all optional. They + specify the index of the message group, + the index of the sub-board to mark with + the "chosen" character, and a sort type, + which can be "none" (default sorting), + "dateAsc" for date ascending, + "dateDesc" for date descending, or + "description" for description. + +6. DDFileAreaChooser class: Properties & methods +=============================================== +The following are the properties and methods of the DDMsgAreaChooser class, which +is the class used for letting the user choose a message area: +Property name Description +------------- ----------- +useLightbarInterface Boolean: Whether or not to use a + lightbar user interface. + +Methods +------- +Method name Description +----------- ----------- +DDFileAreaChooser() Constructor + +SelectFileArea() Lets the user choose a file directory. + If the useLightbarInterface property is + true and the user's terminal supports + ANSI, it will use the lightbar interface; + otherwise, it will use traditional + interface. + +SelectFileArea_Lightbar() Lets the user choose a file directory, + with a lightbar user interface. + +SelectFileArea_Traditional() Lets the user choose a file directory, + with a traditional user interface. + +ListFileLibs() Lists the file libraries + +ListDirsInFileLib(pLibIndex, Lists the directories in the user's + pMarkIndex) currently-selected file library. + The parameters are optional. They + specify the index of the file library + and the index of the directory to mark + with the "chosen" character. +7. Revision History +=================== +Version Date Description +------- ---- ----------- +1.08 2015-04-19 Added customizable color settings for the key help text + line displayed at the bottom of the screen in lightbar + mode. Also, updated to allow the PageUp and PageDown + keys to be used instead of the P and N keys to go to the + previous & next pages in lightbar mode. +1.07 2014-12-22 Message area chooser: + Bug fix: Made this.colors.subBoardHeader apply to the + whole line rather than just the page number. + Bug fix: The initial display of the page number is now + correct (previously, it would start out saying page 1, + even if on another page). + Documentation & example configuration files: + Added the color options subBoardHeader (for the message + area chooser) and fileAreaHdr (for the file area chooser) + to the documentation and example configuration files. +1.06 2014-09-14 Bug fix: Updated the lightbar highlight format string to + include a normal attribute at the end to avoid the + highlight color to be used when clearing the screen, + etc. Bug reported by Psi-Jack. +1.05 2013-05-10 Bug fix in the file area chooser: When listing + directories in a file group, it would sometimes + crash due to an incorrect array index used, and + the array was not set up. Those have been fixed. +1.04 2013-05-04 Updated to properly format message sub-boards and + file directories with more than 9999 entries. The + formatting is now dynamically adjusted depending + on the greatest number of entries in a sub-board + for a message group or file directory in a file + library (the descriptions will shrink as the + text length of the greatest number of entries + increases). +1.03 2012-11-30 Bug fix: After leaving the help screen from the + sub-board/directory list, the top line is now + correctly written with the page information as "Page + # of #". +1.02 2012-10-06 For the lightbar interface, the current page number is + now displayed at the top of the screen (along with the + total number of pages) and is updated when going to a + new page. +1.01 2011-04-22 Fixed the wording when choosing a message sub-board and + file library. +1.00 2010-03-13 First public release \ No newline at end of file