-
nightfox authored
Trying to use a Frame to display poll message text to ensure it's scrollable. Having an issue with it displaying Synchronet color attributes though..
nightfox authoredTrying to use a Frame to display poll message text to ensure it's scrollable. Having an issue with it displaying Synchronet color attributes though..
SlyVote.js 69.20 KiB
/* This is a voting door for Synchronet. It requires Synchronet 3.17 or higher, since
* it makes use of the new voting features added to the message bases in Synchronet
* 3.17. Also, this requires an ANSI client.
*
* Author: Eric Oulashin (AKA Nightfox)
* BBS: Digital Distortion
* BBS address: digitaldistortionbbs.com (or digdist.bbsindex.com)
*
* Date Author Description
* 2016-12-29 Eric Oulashin Version 0.01 Beta
* Started
*/
load("sbbsdefs.js");
// This script requires Synchronet version 3.17 or higher.
// Exit if the Synchronet version is below the minimum.
if (system.version_num < 31700)
{
var message = "\1n\1h\1y\1i* Warning:\1n\1h\1w SlyVote requires version "
+ "\1g3.17\1w or\r\nhigher 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();
}
// Exit if the user's client doesn't support ANSI
if (!console.term_supports(USER_ANSI))
{
console.crlf();
console.print("\1n\1hSlyVote requires an ANSI client.");
console.crlf();
console.pause();
exit();
}
// Exit if the user has the V restriction (not allowed to vote)
if ((user.security.restrictions & UFLAG_V) == UFLAG_V)
{
console.crlf();
// Use the line from text.dat that says the user is not allowed to vote
console.print("\1n" + bbs.text(R_Voting));
console.pause();
}
load("frame.js");
load("scrollbar.js");
load("DDLightbarMenu.js");
// Version information
var SLYVOTE_VERSION = "0.01 Beta";
var SLYVOTE_DATE = "2017-01-09";
// 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 gStartupPath = '.';
try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
// Characters for display
// Box-drawing/border characters: Single-line
var UPPER_LEFT_SINGLE = "";
var HORIZONTAL_SINGLE = "";
var UPPER_RIGHT_SINGLE = "";
var VERTICAL_SINGLE = "";
var LOWER_LEFT_SINGLE = "";
var LOWER_RIGHT_SINGLE = "";
var T_SINGLE = "";
var LEFT_T_SINGLE = "";
var RIGHT_T_SINGLE = "";
var BOTTOM_T_SINGLE = "";
var CROSS_SINGLE = "";
// Box-drawing/border characters: Double-line
var UPPER_LEFT_DOUBLE = "";
var HORIZONTAL_DOUBLE = "";
var UPPER_RIGHT_DOUBLE = "";
var VERTICAL_DOUBLE = "";
var LOWER_LEFT_DOUBLE = "";
var LOWER_RIGHT_DOUBLE = "";
var T_DOUBLE = "";
var LEFT_T_DOUBLE = "";
var RIGHT_T_DOUBLE = "";
var BOTTOM_T_DOUBLE = "";
var CROSS_DOUBLE = "";
// Box-drawing/border characters: Vertical single-line with horizontal double-line
var UPPER_LEFT_VSINGLE_HDOUBLE = "";
var UPPER_RIGHT_VSINGLE_HDOUBLE = "";
var LOWER_LEFT_VSINGLE_HDOUBLE = "";
var LOWER_RIGHT_VSINGLE_HDOUBLE = "";
// Other special characters
var DOT_CHAR = "";
var CHECK_CHAR = "";
var THIN_RECTANGLE_LEFT = "";
var THIN_RECTANGLE_RIGHT = "";
var BLOCK1 = ""; // Dimmest block
var BLOCK2 = "";
var BLOCK3 = "";
var BLOCK4 = ""; // Brightest block
// Strings for the various message attributes (used by makeAllAttrStr(),
// makeMainMsgAttrStr(), makeAuxMsgAttrStr(), and makeNetMsgAttrStr())
var gMainMsgAttrStrs = {
MSG_DELETE: "Del",
MSG_PRIVATE: "Priv",
MSG_READ: "Read",
MSG_PERMANENT: "Perm",
MSG_LOCKED: "Lock",
MSG_ANONYMOUS: "Anon",
MSG_KILLREAD: "Killread",
MSG_MODERATED: "Mod",
MSG_VALIDATED: "Valid",
MSG_REPLIED: "Repl",
MSG_NOREPLY: "NoRepl"
}
var gAuxMsgAttrStrs = {
MSG_FILEREQUEST: "Freq",
MSG_FILEATTACH: "Attach",
MSG_TRUNCFILE: "TruncFile",
MSG_KILLFILE: "KillFile",
MSG_RECEIPTREQ: "RctReq",
MSG_CONFIRMREQ: "ConfReq",
MSG_NODISP: "NoDisp"
}
var gNetMsgAttrStrs = {
MSG_LOCAL: "FromLocal",
MSG_INTRANSIT: "Transit",
MSG_SENT: "Sent",
MSG_KILLSENT: "KillSent",
MSG_ARCHIVESENT: "ArcSent",
MSG_HOLD: "Hold",
MSG_CRASH: "Crash",
MSG_IMMEDIATE: "Now",
MSG_DIRECT: "Direct",
MSG_GATE: "Gate",
MSG_ORPHAN: "Orphan",
MSG_FPU: "FPU",
MSG_TYPELOCAL: "ForLocal",
MSG_TYPEECHO: "ForEcho",
MSG_TYPENET: "ForNetmail"
}
// An amount of milliseconds to wait after displaying an error message, etc.
var ERROR_PAUSE_WAIT_MS = 1500;
var gBottomBorderRow = 23;
// An object containing keypresses for the vote results reader
var gReaderKeys = {
vote: "V"
}
// cfgReadError: A string which will contain a message if failed to read the configuration file
// subBoardCodes: An array containing sub-board codes read from the configuration file
var slyVoteCfg = ReadConfigFile();
if (slyVoteCfg.cfgReadError.length > 0)
{
log(LOG_ERR, "SlyVote: Error reading SlyVote.cfg");
bbs.log_str("SlyVote: Error reading SlyVote.cfg");
console.print("\1n");
console.crlf();
console.print("\1h\1y* Error reading SlyVote.cfg\1n");
console.crlf();
console.pause();
exit();
}
if (slyVoteCfg.subBoardCodes.length == 0)
{
console.print("\1n");
console.crlf();
console.print("\1cThere are no voting areas configured.\1n");
console.crlf();
console.pause();
exit();
}
// Determine which sub-board to use - If there is more than one, let the user choose.
var gSubBoardCode = "";
if (slyVoteCfg.subBoardCodes.length == 1)
gSubBoardCode = slyVoteCfg.subBoardCodes[0];
else
{
console.gotoxy(2, 1);
console.print("\1n\1cChoose a sub-board:");
var subBoardsLB = new DDLightbarMenu(2, 2, 45, 10);
for (var idx = 0; idx < slyVoteCfg.subBoardCodes.length; ++idx)
{
var name = msg_area.sub[slyVoteCfg.subBoardCodes[idx]].name;
var desc = msg_area.sub[slyVoteCfg.subBoardCodes[idx]].description;
subBoardsLB.Add(desc, slyVoteCfg.subBoardCodes[idx]);
}
gSubBoardCode = subBoardsLB.GetVal();
console.gotoxy(1, subBoardsLB.pos.y+subBoardsLB.size.height+1);
}
//bbs.exec("?postpoll.js");
// Program states
var MAIN_MENU = 0;
var VOTING_ON_A_TOPIC = 1;
var VIEWING_VOTE_RESULTS = 2;
var EXIT_SLYVOTE = 999;
var gProgramState = MAIN_MENU;
// Program action loop
var gContinueSlyVote = true;
while (gContinueSlyVote)
{
switch (gProgramState)
{
case MAIN_MENU:
gProgramState = DoMainMenu();
break;
case VOTING_ON_A_TOPIC:
gProgramState = ChooseVoteTopic();
break;
case VIEWING_VOTE_RESULTS:
gProgramState = ViewVoteResults(gSubBoardCode);
break;
case EXIT_SLYVOTE:
gContinueSlyVote = false;
break;
}
}
////////////////////////////////////////////////////////////
// Helper functions
////////////////////////////////////////////////////////////
var gMainMenu = null;
function DoMainMenu()
{
gProgramState = MAIN_MENU;
var nextProgramState = MAIN_MENU;
// Display the SlyVote screen and menu of choices
console.clear("\1n");
var mainScrRetObj = DisplaySlyVoteMainVoteScreen(false);
if (gMainMenu == null)
{
gMainMenu = new DDLightbarMenu(mainScrRetObj.optMenuX, mainScrRetObj.optMenuY, 17, 5);
gMainMenu.Add("Vote On A Topic", "vote", "1");
gMainMenu.Add("Answer All Topics", "answerAll", "2");
gMainMenu.Add("Create A Topic", "create", "3");
gMainMenu.Add("View Results", "viewResults", "4");
gMainMenu.Add("Quit To BBS", "quit", "5");
gMainMenu.colors.itemColor = "\1n\1w";
gMainMenu.colors.selectedItemColor = "\1n\1" + "4\1w\1h";
}
// Get the user's choice and take appropriate action
var userChoice = gMainMenu.GetVal(true);
if (userChoice == "vote")
nextProgramState = VOTING_ON_A_TOPIC;
else if (userChoice == "answerAll")
{
}
else if (userChoice == "create")
{
console.print("\1n");
console.gotoxy(1, console.screen_rows);
bbs.exec("?postpoll.js");
}
else if (userChoice == "viewResults")
nextProgramState = VIEWING_VOTE_RESULTS;
else if (userChoice == "quit")
nextProgramState = EXIT_SLYVOTE;
return nextProgramState;
}
function ChooseVoteTopic()
{
gProgramState = VOTING_ON_A_TOPIC;
var nextProgramState = VOTING_ON_A_TOPIC;
// Clear the screen between the top & bottom borders
var formatStr = "%" + console.screen_columns + "s";
console.print("\1n");
for (var posY = 3; posY < gBottomBorderRow; ++posY)
{
console.gotoxy(1, posY);
printf(formatStr, "");
}
// Get the list of polls for the selected sub-board
var voteTopicInfo = GetVoteTopicHdrs(gSubBoardCode, true);
if (voteTopicInfo.errorMsg.length > 0)
{
console.gotoxy(1, 3);
console.print("\1n\1y\1h" + voteTopicInfo.errorMsg + "\1n");
console.crlf();
console.pause();
return;
}
else if (voteTopicInfo.msgHdrs.length == 0)
{
console.gotoxy(1, 3);
console.print("\1n\1cThere are no polls to vote on in this section\1n");
console.crlf();
console.pause();
return;
}
// Display the list of voting topics
console.print("\1n");
var pleaseSelectTextRow = 6;
// Draw the columns to frame the voting topics
var columnX1 = 17;
var columnX2 = 63;
var textLen = columnX2 - columnX1 - 2;
var listTopRow = pleaseSelectTextRow + 1;
for (var posY = listTopRow; posY < gBottomBorderRow; ++posY)
{
console.gotoxy(columnX1, posY);
console.print("\1" + "7\1h\1w\1n\1k\1" + "7\1n\1" + "7\1k\1h\1n");
console.gotoxy(columnX2, posY);
console.print("\1" + "7\1h\1w\1n\1k\1" + "7\1n\1" + "7\1k\1h\1n");
}
// Create the menu containing voting topics and get the user's choice
var startCol = columnX1+2;
var menuHeight = gBottomBorderRow-listTopRow-1;
var topicsMenu = new DDLightbarMenu(startCol, listTopRow, textLen, menuHeight);
for (var i = 0; i < voteTopicInfo.msgHdrs.length; ++i)
topicsMenu.Add(voteTopicInfo.msgHdrs[i].subject, voteTopicInfo.msgHdrs[i].number);
var drawTopicsMenu = true;
while (nextProgramState == VOTING_ON_A_TOPIC)
{
var pleaseSectTopicText = "\1n\1c\1hP\1n\1clease select a topic to vote on (\1hESC\1n\1g=\1cReturn)\1n";
console.gotoxy(18, pleaseSelectTextRow);
console.print(pleaseSectTopicText);
console.print("\1n");
var userChoice = topicsMenu.GetVal(drawTopicsMenu);
if (userChoice != null)
{
//topicsMenu.Erase();
console.gotoxy(18, pleaseSelectTextRow);
printf("%" + strip_ctrl(pleaseSectTopicText).length + "s", "");
var voteRetObj = VoteOnTopic(gSubBoardCode, userChoice, startCol, listTopRow, textLen, menuHeight);
drawTopicsMenu = true;
if (voteRetObj.errorMsg.length > 0)
{
var errorMsgRow = 3;
console.gotoxy(1, errorMsgRow);
if (voteRetObj.mnemonicsRequiredForErrorMsg)
{
console.gotoxy(1, errorMsgRow);
console.mnemonics(voteRetObj.errorMsg);
mswait(ERROR_PAUSE_WAIT_MS);
console.gotoxy(1, errorMsgRow);
printf("\1n%" + console.screen_columns + "s", "");
}
else
{
console.print("\1n\1y\1h" + voteRetObj.errorMsg);
mswait(ERROR_PAUSE_WAIT_MS);
console.gotoxy(1, errorMsgRow);
printf("\1n%" + voteRetObj.errorMsg.length + "s", "");
}
}
}
else // The user chose to exit the topics menu
nextProgramState = MAIN_MENU;
}
return nextProgramState;
}
// Lets the user vote on a topic
//
// Parameters:
// pSubBoardCode: The internal code of the sub-board
// pMsgNum: The number of the message to vote on
// pStartCol: The starting column on the screen for the option list
// pStartRow: The starting row on the screen for the option list
// pMenuWidth: The width to use for the topic option menu
// pMenuHeight: The height to use for the topic option menu
//
// Return value: An object containing the following properties:
// errorMsg: A string containing a message on error, or an empty string on success
// mnemonicsRequiredForErrorMsg: Whether or not mnemonics is required to display the error message
function VoteOnTopic(pSubBoardCode, pMsgNum, pStartCol, pStartRow, pMenuWidth, pMenuHeight)
{
var retObj = {
errorMsg: "",
mnemonicsRequiredForErrorMsg: false,
nextProgramState: VOTING_ON_A_TOPIC
};
// Open the chosen sub-board
var msgbase = new MsgBase(pSubBoardCode);
if (msgbase.open())
{
if (!HasUserVotedOnMsg(pMsgNum, pSubBoardCode, msgbase, user))
{
var msgHdr = msgbase.get_msg_header(false, pMsgNum, true);
if (msgHdr != null)
{
var pollTextAndOpts = GetPollTextAndOpts(msgHdr);
// Print the poll question text
console.gotoxy(1, pStartRow-4);
//printf("\1n\1c%-" + console.screen_columns + "s", msgHdr.subject.substr(0, console.screen_columns));
var pollSubject = msgHdr.subject.substr(0, console.screen_columns);
console.print("\1n\1c" + pollSubject);
// Output up to the first 3 poll comment lines
//console.print("\1n");
var i = 0;
var commentStartRow = pStartRow - 3;
for (var row = commentStartRow; (row < commentStartRow+3) && (i < pollTextAndOpts.commentLines.length); ++row)
{
console.gotoxy(1, row);
console.print(pollTextAndOpts.commentLines[i++].substr(0, console.screen_columns));
}
// Create the poll options menu, and show it and let the user choose an option
var optionsMenu = new DDLightbarMenu(pStartCol, pStartRow, pMenuWidth, pMenuHeight);
for (i = 0; i < pollTextAndOpts.options.length; ++i)
optionsMenu.Add(pollTextAndOpts.options[i], i);
optionsMenu.colors.itemColor = "\1c";
optionsMenu.colors.selectedItemColor = "\1b\1" + "7";
// Get the user's choice of voting option and submit it for voting
var userChoice = optionsMenu.GetVal(true);
var voteRetObj = VoteOnMessage(pSubBoardCode, msgbase, msgHdr, user, userChoice, true);
// If there was an error, then show it. Otherwise, show a success message.
var firstLineEraseLength = pollSubject.length;
console.gotoxy(1, pStartRow-4);
printf("\1n%" + pollSubject.length + "s", "");
console.gotoxy(1, pStartRow-4);
if (voteRetObj.errorMsg.length > 0)
{
var voteErrMsg = voteRetObj.errorMsg.substr(0, console.screen_columns - 2);
firstLineEraseLength = voteErrMsg.length;
console.print("\1y\1h* " + voteErrMsg);
mswait(ERROR_PAUSE_WAIT_MS);
}
else
{
console.print("\1cYour vote was successfully saved.");
mswait(ERROR_PAUSE_WAIT_MS);
}
// Before returning, erase the poll question text and comment lines from the screen
console.print("\1n");
console.gotoxy(1, pStartRow-4);
printf("%" + firstLineEraseLength + "s", "");
i = 0;
for (var row = commentStartRow; (row < commentStartRow+3) && (i < pollTextAndOpts.commentLines.length); ++row)
{
console.gotoxy(1, row);
var pollCommentLine = pollTextAndOpts.commentLines[i++].substr(0, console.screen_columns);
printf("%" + pollCommentLine.length + "s", "");
}
}
else
retObj.errorMsg = "Unable to retrieve the topic options";
}
else
{
// The user has already voted
retObj.errorMsg = bbs.text(VotedAlready).replace("\r\n", "").replace("\n", "").replace("\N", "").replace("\r", "").replace("\R", "").replace("\R\n", "").replace("\r\N", "").replace("\R\N", "");
retObj.mnemonicsRequiredForErrorMsg = true;
}
msgbase.close();
}
else
retObj.errorMsg = "Failed to open the messagebase";
return retObj;
}
// 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)
//
// Return value: The string with whitespace trimmed
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;
}
// Reads the configuration file (SlyVote.cfg). Returns an object with the following
// properties:
// cfgReadError: A string which will contain a message if failed to read the configuration file
// subBoardCodes: An array containing sub-board codes read from the configuration file
function ReadConfigFile()
{
var retObj = {
cfgReadError: "",
subBoardCodes: []
};
// Open the main configuration file. First look for it in the sbbs/mods
// directory, then sbbs/ctrl, then in the same directory as this script.
var filename = "SlyVote.cfg";
var cfgFilename = system.mods_dir + filename;
if (!file_exists(cfgFilename))
cfgFilename = system.ctrl_dir + filename;
if (!file_exists(cfgFilename))
cfgFilename = gStartupPath + filename;
var cfgFile = new File(cfgFilename);
if (cfgFile.open("r"))
{
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; // To store 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 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);
// Set the appropriate value in the settings object.
if (settingUpper == "SUBBOARDCODES")
{
// Split the value on commas and add all sub-board codes to
// retObj.subBoardCodes, as long as they're valid sub-board codes.
var valueLower = value.toLowerCase();
var subCodeArray = valueLower.split(",");
for (var idx = 0; idx < subCodeArray.length; ++idx)
{
// If the sub-board code exists and voting is allowed in the sub-board, then add it.
if (msg_area.sub.hasOwnProperty(subCodeArray[idx]))
{
if ((msg_area.sub[subCodeArray[idx]].settings & SUB_NOVOTING) == 0)
retObj.subBoardCodes.push(subCodeArray[idx]);
}
}
}
}
}
cfgFile.close();
}
else // Unable to read the configuration file
retObj.cfgReadError = "Unable to open the configuration file: SlyVote.cfg";
return retObj;
}
// Checks to see whether a user has voted on a message.
//
// Parameters:
// pMsgNum: The message number
// pSubBoardCode: The internal code of the sub-board
// pMsgbase: The MessageBase object for the sub-board
// pUser: Optional - A user account to check. If omitted, the current logged-in
// user will be used.
function HasUserVotedOnMsg(pMsgNum, pSubBoardCode, pMsgbase, pUser)
{
// Don't do this for personal email
if (pSubBoardCode == "mail")
return false;
// Thanks to echicken for explaining how to check this. To check a user's
// vote, use MsgBase.how_user_voted().
/*
The return value will be:
0 - user hasn't voted
1 - upvoted
2 - downvoted
Or, if the message was a poll, it's a bitfield:
if (votes&(1<<2)) {
// User selected answer 2
}
*/
var userHasVotedOnMsg = false;
if ((pMsgbase !== null) && pMsgbase.is_open)
{
if (typeof(pMsgbase.how_user_voted) === "function")
{
var votes = 0;
if (typeof(pUser) == "object")
votes = pMsgbase.how_user_voted(pMsgNum, (pMsgbase.cfg.settings & SUB_NAME) == SUB_NAME ? pUser.name : pUser.alias);
else
votes = pMsgbase.how_user_voted(pMsgNum, (pMsgbase.cfg.settings & SUB_NAME) == SUB_NAME ? user.name : user.alias);
userHasVotedOnMsg = (votes > 0);
/*
if (votes > 0)
{
var userVotedMaxVotes = false;
if ((votes == 0) || (votes == 1))
userVotedMaxVotes = (votes == 3); // (1 << 0) | (1 << 1);
else
{
var msgHdr = pMsgbase.get_msg_header(false, pMsgNum);
if (msgHdr != null)
{
userVotedMaxVotes = true;
for (var voteIdx = 0; voteIdx <= numVotes; ++voteIdx)
{
if (votes && (1 << voteIdx) == 0)
{
userVotedMaxVotes = false;
break;
}
}
}
}
userHasVotedOnMsg = userVotedMaxVotes;
}
*/
}
}
return userHasVotedOnMsg;
}
// Gets the body (text) of a message. If it's
// a poll, this method will format the message body with poll results. Otherwise,
// this method will simply get the message body.
//
// Parameters:
// pMsgbase: The MessageBase object for the sub-board
// pMsgHeader: The message header
// pSubBoardCode: The internal code of the sub-board
// pUser: Optional - A user account to check. If omitted, the current logged-in
// user will be used.
//
// Return value: The poll results, colorized. If the message is not a
// poll message, then an empty string will be returned.
function GetMsgBody(pMsgbase, pMsgHdr, pSubBoardCode, pUser)
{
var msgBody = "";
if ((typeof(MSG_TYPE_POLL) != "undefined") && (pMsgHdr.type & MSG_TYPE_POLL) == MSG_TYPE_POLL)
{
// A poll is intended to be parsed (and displayed) using on the header data. The
// (optional) comments are stored in the hdr.field_list[] with type values of
// SMB_COMMENT (now defined in sbbsdefs.js) and the available answers are in the
// field_list[] with type values of SMB_POLL_ANSWER.
// The 'comments' and 'answers' are also a part of the message header, so you can
// grab them separately, then format and display them however you want. You can
// find them in the header.field_list array; each element in that array should be
// an object with a 'type' and a 'data' property. Relevant types here are
// SMB_COMMENT and SMB_POLL_ANSWER. (This is what I'm doing on the web, and I
// just ignore the message body for poll messages.)
if (pMsgHdr.hasOwnProperty("field_list"))
{
//var voteOptDescLen = 27;
// Figure out the longest length of the poll choices, with
// a maximum of 22 characters less than the terminal width.
// Use a minimum of 27 characters.
// That length will be used for the formatting strings for
// the poll results.
var voteOptDescLen = 0;
for (var fieldI = 0; fieldI < pMsgHdr.field_list.length; ++fieldI)
{
if (pMsgHdr.field_list[fieldI].type == SMB_POLL_ANSWER)
{
if (pMsgHdr.field_list[fieldI].data.length > voteOptDescLen)
voteOptDescLen = pMsgHdr.field_list[fieldI].data.length;
}
}
if (voteOptDescLen > console.screen_columns - 22)
voteOptDescLen = console.screen_columns - 22;
else if (voteOptDescLen < 27)
voteOptDescLen = 27;
// Format strings for outputting the voting option lines
var unvotedOptionFormatStr = "\1n\1c\1h%2d\1n\1c: \1w\1h%-" + voteOptDescLen + "s [%-4d %6.2f%]\1n";
var votedOptionFormatStr = "\1n\1c\1h%2d\1n\1c: \1" + "5\1w\1h%-" + voteOptDescLen + "s [%-4d %6.2f%]\1n";
// Add up the total number of votes so that we can
// calculate vote percentages.
var totalNumVotes = 0;
if (pMsgHdr.hasOwnProperty("tally"))
{
for (var tallyI = 0; tallyI < pMsgHdr.tally.length; ++tallyI)
totalNumVotes += pMsgHdr.tally[tallyI];
}
// Go through field_list and append the voting options and stats to
// msgBody
var pollComment = "";
var optionNum = 1;
var numVotes = 0;
var votePercentage = 0;
var tallyIdx = 0;
for (var fieldI = 0; fieldI < pMsgHdr.field_list.length; ++fieldI)
{
if (pMsgHdr.field_list[fieldI].type == SMB_COMMENT)
pollComment += pMsgHdr.field_list[fieldI].data + "\r\n";
else if (pMsgHdr.field_list[fieldI].type == SMB_POLL_ANSWER)
{
// Figure out the number of votes on this option and the
// vote percentage
if (pMsgHdr.hasOwnProperty("tally"))
{
if (tallyIdx < pMsgHdr.tally.length)
{
numVotes = pMsgHdr.tally[tallyIdx];
votePercentage = (numVotes / totalNumVotes) * 100;
}
}
// Append to the message text
msgBody += format(numVotes == 0 ? unvotedOptionFormatStr : votedOptionFormatStr,
optionNum++, pMsgHdr.field_list[fieldI].data.substr(0, voteOptDescLen),
numVotes, votePercentage);
if (numVotes > 0)
msgBody += " " + CHECK_CHAR;
msgBody += "\r\n";
++tallyIdx;
}
}
if (pollComment.length > 0)
msgBody = pollComment + "\r\n" + msgBody;
// If voting is allowed in this sub-board and the current logged-in
// user has not voted on this message, then append some text saying
// how to vote.
var votingAllowed = ((pSubBoardCode != "mail") && (((msg_area.sub[pSubBoardCode].settings & SUB_NOVOTING) == 0)));
if (votingAllowed && !HasUserVotedOnMsg(pMsgHdr.number, pSubBoardCode, pMsgbase, pUser))
msgBody += "\1n\r\n\1gTo vote in this poll, press \1w\1h" + gReaderKeys.vote + "\1n\1g now.";
// If the current logged-in user created this poll, then show the
// users who have voted on it so far.
var msgFromUpper = pMsgHdr.from.toUpperCase();
if ((msgFromUpper == user.name.toUpperCase()) || (msgFromUpper == user.handle.toUpperCase()))
{
// Check all the messages in the messagebase after the current one
// to find poll response messages
if (typeof(pMsgbase.get_all_msg_headers) === "function")
{
// Get the line from text.dat for writing who voted & when. It
// is a format string and should look something like this:
//"\r\n\1n\1hOn %s, in \1c%s \1n\1c%s\r\n\1h\1m%s voted in your poll: \1n\1h%s\r\n" 787 PollVoteNotice
var userVotedInYourPollText = bbs.text(typeof(PollVoteNotice) != "undefined" ? PollVoteNotice : 787);
// Pass true to get_all_msg_headers() to tell it to return vote messages
// (the parameter was introduced in Synchronet 3.17+)
var tmpHdrs = pMsgbase.get_all_msg_headers(true);
for (var tmpProp in tmpHdrs)
{
if (tmpHdrs[tmpProp] == null)
continue;
// If this header's thread_back or reply_id matches the poll message
// number, then append the 'user voted' string to the message body.
if ((tmpHdrs[tmpProp].thread_back == pMsgHdr.number) || (tmpHdrs[tmpProp].reply_id == pMsgHdr.number))
{
var msgWrittenLocalTime = MsgWrittenTimeToLocalBBSTime(tmpHdrs[tmpProp]);
var voteDate = strftime("%a %b %d %Y %H:%M:%S", msgWrittenLocalTime);
msgBody += format(userVotedInYourPollText, voteDate, pMsgbase.cfg.grp_name, pMsgbase.cfg.name,
tmpHdrs[tmpProp].from, pMsgHdr.subject);
}
}
}
}
}
}
else
msgBody = pMsgbase.get_msg_body(false, pMsgHdr.number);
return msgBody;
}
// Gets a poll message's text and options
//
// Parameters:
// pMsgHdr: The header object of the message to get the text & options from
//
// Return value: An object with the following properties:
// commentLines: An array containing the comment lines
// options: An array containing the poll options (text)
function GetPollTextAndOpts(pMsgHdr)
{
var retObj = {
commentLines: [],
options: []
};
if ((typeof(MSG_TYPE_POLL) != "undefined") && (pMsgHdr.type & MSG_TYPE_POLL) == MSG_TYPE_POLL)
{
// A poll is intended to be parsed (and displayed) using on the header data. The
// (optional) comments are stored in the hdr.field_list[] with type values of
// SMB_COMMENT (now defined in sbbsdefs.js) and the available answers are in the
// field_list[] with type values of SMB_POLL_ANSWER.
// The 'comments' and 'answers' are also a part of the message header, so you can
// grab them separately, then format and display them however you want. You can
// find them in the header.field_list array; each element in that array should be
// an object with a 'type' and a 'data' property. Relevant types here are
// SMB_COMMENT and SMB_POLL_ANSWER. (This is what I'm doing on the web, and I
// just ignore the message body for poll messages.)
if (pMsgHdr.hasOwnProperty("field_list"))
{
// Go through field_list and append the voting options and stats to
// msgBody
var pollComment = "";
var optionNum = 1;
var numVotes = 0;
var votePercentage = 0;
var tallyIdx = 0;
for (var fieldI = 0; fieldI < pMsgHdr.field_list.length; ++fieldI)
{
if (pMsgHdr.field_list[fieldI].type == SMB_COMMENT)
retObj.commentLines.push(pMsgHdr.field_list[fieldI].data);
else if (pMsgHdr.field_list[fieldI].type == SMB_POLL_ANSWER)
retObj.options.push(pMsgHdr.field_list[fieldI].data);
}
}
}
return retObj;
}
// Lets the user vote on a message
//
// Parameters:
// pSubBoardCode: The internal code of the sub-board being used for voting
// pMsgbase: The MessageBase object for the sub-board being used for voting
// pMsgHdr: The header of the mesasge being voted on
// pUser: The user object representing the user voting on the poll
// pUserVoteNumber: Optional - A number inputted by the user representing their vote.
// If this is not passed in or is null, etc., then this function will
// prompt the user for their vote.
// pRemoveNLsFromVoteText: Optional boolean - Whether or not to remove newlines
// (and carriage returns) from the voting text from
// text.dat. Defaults to false.
//
// Return value: An object with the following properties:
// BBSHasVoteFunction: Boolean - Whether or not the system has
// the vote_msg function
// savedVote: Boolean - Whether or not the vote was saved
// userQuit: Boolean - Whether or not the user quit and didn't vote
// errorMsg: String - An error message, if something went wrong
// mnemonicsRequiredForErrorMsg: Boolean - Whether or not mnemonics is required to print the error message
// updatedHdr: The updated message header containing vote information.
// If something went wrong, this will be null.
function VoteOnMessage(pSubBoardCode, pMsgbase, pMsgHdr, pUser, pUserVoteNumber, pRemoveNLsFromVoteText)
{
var retObj = new Object();
retObj.BBSHasVoteFunction = (typeof(pMsgbase.vote_msg) === "function");
retObj.savedVote = false;
retObj.userQuit = false;
retObj.errorMsg = "";
retObj.mnemonicsRequiredForErrorMsg = false;
retObj.updatedHdr = null;
// Don't allow voting for personal email
if (pSubBoardCode == "mail")
{
retObj.errorMsg = "Can not vote on personal email";
return retObj;
}
// If the message vote function is not defined in the running verison of Synchronet,
// then just return.
if (!retObj.BBSHasVoteFunction)
return retObj;
var removeNLsFromVoteText = (typeof(pRemoveNLsFromVoteText) === "boolean" ? pRemoveNLsFromVoteText : false)
// See if voting is allowed in the current sub-board
if ((msg_area.sub[pSubBoardCode].settings & SUB_NOVOTING) == SUB_NOVOTING)
{
retObj.errorMsg = bbs.text(VotingNotAllowed);
if (removeNLsFromVoteText)
retObj.errorMsg = retObj.errorMsg.replace("\r\n", "").replace("\n", "").replace("\N", "").replace("\r", "").replace("\R", "").replace("\R\n", "").replace("\r\N", "").replace("\R\N", "");
retObj.mnemonicsRequiredForErrorMsg = true;
return retObj;
}
// If the message is a poll question and has the maximum number of votes
// already or is closed for voting, then don't let the user vote on it.
if ((pMsgHdr.attr & MSG_POLL) == MSG_POLL)
{
var userVotedMaxVotes = false;
var numVotes = (pMsgHdr.hasOwnProperty("votes") ? pMsgHdr.votes : 0);
if (typeof(pMsgbase.how_user_voted) === "function")
{
var votes = pMsgbase.how_user_voted(pMsgHdr.number, (pMsgbase.cfg.settings & SUB_NAME) == SUB_NAME ? user.name : user.alias);
if (votes >= 0)
{
if ((votes == 0) || (votes == 1))
userVotedMaxVotes = (votes == 3); // (1 << 0) | (1 << 1);
else
{
userVotedMaxVotes = true;
for (var voteIdx = 0; voteIdx <= numVotes; ++voteIdx)
{
if (votes && (1 << voteIdx) == 0)
{
userVotedMaxVotes = false;
break;
}
}
}
}
}
var pollIsClosed = ((pMsgHdr.auxattr & POLL_CLOSED) == POLL_CLOSED);
if (pollIsClosed)
{
retObj.errorMsg = "This poll is closed";
return retObj;
}
else if (userVotedMaxVotes)
{
retObj.errorMsg = bbs.text(VotedAlready);
if (removeNLsFromVoteText)
retObj.errorMsg = retObj.errorMsg.replace("\r\n", "").replace("\n", "").replace("\N", "").replace("\r", "").replace("\R", "").replace("\R\n", "").replace("\r\N", "").replace("\R\N", "");
retObj.mnemonicsRequiredForErrorMsg = true;
return retObj;
}
}
// If the user has voted on this message already, then set an error message and return.
if (HasUserVotedOnMsg(pMsgHdr.number, pSubBoardCode, pMsgbase, pUser))
{
retObj.errorMsg = bbs.text(VotedAlready);
if (removeNLsFromVoteText)
retObj.errorMsg = retObj.errorMsg.replace("\r\n", "").replace("\n", "").replace("\N", "").replace("\r", "").replace("\R", "").replace("\R\n", "").replace("\r\N", "").replace("\R\N", "");
retObj.mnemonicsRequiredForErrorMsg = true;
return retObj;
}
// New MsgBase method: vote_msg(). it takes a message header object
// (like save_msg), except you only need a few properties, in order of
// importarnce:
// attr: you need to have this set to MSG_UPVOTE, MSG_DOWNVOTE, or MSG_VOTE
// thread_back or reply_id: either of these must be set to indicate msg to vote on
// from: name of voter
// from_net_type and from_net_addr: if applicable
// Do some initial setup of the header for the vote message to be
// saved to the messagebase
var voteMsgHdr = new Object();
voteMsgHdr.thread_back = pMsgHdr.number;
voteMsgHdr.reply_id = pMsgHdr.number;
voteMsgHdr.from = (pMsgbase.cfg.settings & SUB_NAME) == SUB_NAME ? user.name : user.alias;
if (pMsgHdr.from.hasOwnProperty("from_net_type"))
{
voteMsgHdr.from_net_type = pMsgHdr.from_net_type;
if (pMsgHdr.from_net_type != NET_NONE)
voteMsgHdr.from_net_addr = user.email;
}
// Input vote options from the user differently depending on whether
// the message is a poll or not
if ((pMsgHdr.attr & MSG_POLL) == MSG_POLL)
{
if (pMsgHdr.hasOwnProperty("field_list"))
{
var userInputNum = 0;
if (typeof(pUserVoteNumber) != "number")
{
console.clear("\1n");
var selectHdr = bbs.text(SelectItemHdr);
printf("\1n" + selectHdr + "\1n", pMsgHdr.subject);
var optionFormatStr = "\1n\1c\1h%2d\1n\1c: \1h%s\1n";
var optionNum = 1;
for (var fieldI = 0; fieldI < pMsgHdr.field_list.length; ++fieldI)
{
if (pMsgHdr.field_list[fieldI].type == SMB_POLL_ANSWER)
{
printf(optionFormatStr, optionNum++, pMsgHdr.field_list[fieldI].data);
console.crlf();
}
}
console.crlf();
// Get the selection prompt text from text.dat and replace the %u or %d with
// the number 1 (default option)
var selectPromptText = bbs.text(SelectItemWhich);
selectPromptText = selectPromptText.replace(/%[uU]/, 1).replace(/%[dD]/, 1);
console.mnemonics(selectPromptText);
// Get & process the selection from the user
var maxNum = optionNum - 1;
// TODO: Update to support multiple answers from the user
userInputNum = console.getnum(maxNum);
console.print("\1n");
}
else
userInputNum = pUserVoteNumber;
//if (userInputNum == 0) // The user just pressed enter to choose the default
// userInputNum = 1;
if (userInputNum == -1) // The user chose Q to quit
retObj.userQuit = true;
else
{
// The user's answer is 0-based, so if userInputNum is positive,
// subtract 1 from it (if it's already 0, that means the user
// chose to keep the default first answer).
if (userInputNum > 0)
--userInputNum;
var votes = (1 << userInputNum);
voteMsgHdr.attr = MSG_VOTE;
voteMsgHdr.votes = votes;
}
}
}
// If the user hasn't quit and there is no error message, then save the vote
// message header
if (!retObj.userQuit && (retObj.errorMsg.length == 0))
{
retObj.savedVote = pMsgbase.vote_msg(voteMsgHdr);
if (!retObj.savedVote)
retObj.errorMsg = "Failed to save your vote";
}
return retObj;
}
// Displays the SlyVote screen.
//
// Parameters:
// pClearScr - Optional - Whether or not to clear the screen first. Defaults to true.
//
// Return value: An object with the following parameters:
// optMenuX: The column of the upper-left corner to use for the option menu
// optMenuY: The row of the upper-left corner to use for the option menu
function DisplaySlyVoteMainVoteScreen(pClearScr)
{
var clearScr = (typeof(pClearScr) == "boolean" ? pClearScr : true);
if (clearScr)
console.clear("\1n");
else
console.print("\1n");
//bbs.menu("../" + gStartupPath + "slyvote");
DisplayTopScreenBorder();
DisplaySlyVoteText();
DisplayVerAndRegBorders();
console.gotoxy(1, gBottomBorderRow);
DisplayBottomScreenBorder();
console.print("\1n");
// Write the SlyVote version centered
var fieldWidth = 28;
console.gotoxy(41, 14);
console.print(CenterText("\1n\1hSlyVote v\1c" + SLYVOTE_VERSION.replace(".", "\1b.\1c") + "\1n", fieldWidth));
// Write the "Registered to" text centered
console.gotoxy(41, 17);
console.print(CenterText("\1n\1h" + system.operator + "\1n", fieldWidth));
console.gotoxy(41, 18);
console.print(CenterText("\1n\1h" + system.name + "\1n", fieldWidth));
// Write the option numbers
var curPos = { x: 7, y: 12 };
var retObj = { optMenuX: curPos.x+4, optMenuY: curPos.y }; // For the option menu to be used later
for (var optNum = 1; optNum <= 5; ++optNum)
{
console.gotoxy(curPos.x, curPos.y++);
console.print("\1" + "7\1h\1w\1n\1k\1" + "7" + optNum + "\1n\1" + "7\1k\1h\1n");
}
return retObj;
}
function DisplayTopScreenBorder()
{
console.print("0");
console.crlf();
console.print("nh nbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnb");
console.crlf();
}
function DisplaySlyVoteText()
{
console.print("");
console.crlf();
console.print("hc nc hb nc h nc h");
console.crlf();
console.print(" nc h nc ");
console.crlf();
console.print(" h nc h nc ");
console.crlf();
console.print("hb c nc h nc h nbc h nc hb");
console.crlf();
console.print("nb");
console.crlf();
}
function DisplayVerAndRegBorders()
{
var curPos = { x: 40, y: 13 };
console.gotoxy(curPos.x, curPos.y++);
console.print("hkbnbhkbnbhnbhkbnbhknbhkbnbhknbh");
console.gotoxy(curPos.x, curPos.y++);
console.print("nbhw c b c k");
console.gotoxy(curPos.x, curPos.y++);
console.print("bknbhkbkbnbhknbhknbhkbkbkbnbhnb");
console.gotoxy(curPos.x, curPos.y++);
console.print("hknbhkb nbRhenbgisterehkd To: bknbhkĿ");
console.gotoxy(curPos.x, curPos.y++);
console.print("nbh");
console.gotoxy(curPos.x, curPos.y++);
console.print("knb");
console.gotoxy(curPos.x, curPos.y++);
console.print("hknbhknbhkbnbhknbhnbhnbhnbhnbhnbhkb");
}
function DisplayBottomScreenBorder()
{
console.print(" nbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbhcbnbn");
}
// Centers some text within a specified field length.
//
// Parameters:
// pText: The text to center
// pFieldLen: The field length
function CenterText(pText, pFieldLen)
{
var centeredText = "";
var textLen = strip_ctrl(pText).length;
// If pFieldLen is less than the text length, then truncate the string.
if (pFieldLen < textLen)
centeredText = pText.substring(0, pFieldLen);
else
{
// pFieldLen is at least equal to the text length, so we can
// center the text.
// Calculate the number of spaces needed to center the text.
var numSpaces = pFieldLen - textLen;
if (numSpaces > 0)
{
var rightSpaces = (numSpaces/2).toFixed(0);
var leftSpaces = numSpaces - rightSpaces;
// Build centeredText
for (var i = 0; i < leftSpaces; ++i)
centeredText += " ";
centeredText += pText;
for (var i = 0; i < rightSpaces; ++i)
centeredText += " ";
}
else // pFieldLength is the same length as the text.
centeredText = pText;
}
return centeredText;
}
// Returns an array of message headers for voting topics
//
// Parameters:
// pSubBoardCode: The internal code of the sub-board to open
// pCheckIfUserVoted: Boolean - Whether or not to check whether the user has voted on the topics
//
// Return value: An object containing the following properties:
// errorMsg: A string containing an error message on failure or a blank string on success
// msgHdrs: An array containing message headers for voting topics
function GetVoteTopicHdrs(pSubBoardCode, pCheckIfUserVoted)
{
var retObj = {
errorMsg: "",
msgHdrs: []
};
// Open the chosen sub-board
var msgbase = new MsgBase(pSubBoardCode);
if (msgbase.open())
{
var msgHdrs = msgbase.get_all_msg_headers(true);
for (var prop in msgHdrs)
{
if ((msgHdrs[prop].type & MSG_TYPE_POLL) == MSG_TYPE_POLL)
{
if (pCheckIfUserVoted)
{
if (!HasUserVotedOnMsg(msgHdrs[prop].number, pSubBoardCode, msgbase, user))
retObj.msgHdrs.push(msgHdrs[prop]);
}
else
retObj.msgHdrs.push(msgHdrs[prop]);
}
}
msgbase.close();
}
else
retObj.errorMsg = "Failed to open the messagebase";
return retObj;
}
function ViewVoteResults(pSubBoardCode)
{
var nextProgramState = MAIN_MENU;
var msgbase = new MsgBase(pSubBoardCode);
if (msgbase.open())
{
// Fill an array of message headers for the poll messages
var pollMsgHdrs = [];
var msgHdrs = msgbase.get_all_msg_headers(true);
for (var prop in msgHdrs)
{
if ((msgHdrs[prop].type & MSG_TYPE_POLL) == MSG_TYPE_POLL)
pollMsgHdrs.push(msgHdrs[prop]);
}
delete msgHdrs; // Free some memory
// Get the unmodified default header lines to be displayed
var displayMsgHdrUnmodified = GetDefaultMsgDisplayHdr();
// User input loop
console.clear("\1n");
var currentMsgIdx = 0;
var continueOn = true;
while (continueOn)
{
// Get the message header lines to be displayed
var dateTimeStr = pollMsgHdrs[currentMsgIdx]["date"].replace(/ [-+][0-9]+$/, "");
var displayMsgHdr = GetDisplayMsgHdrForMsg(pollMsgHdrs[currentMsgIdx], displayMsgHdrUnmodified, pSubBoardCode, pollMsgHdrs.length, currentMsgIdx+1, dateTimeStr, false, false);
// Display the message header on the screen
var curPos = { x: 1, y: 1 };
for (var i = 0; i < displayMsgHdr.length; ++i)
{
console.gotoxy(curPos.x, curPos.y++);
console.print(displayMsgHdr[i]);
}
console.print("\1n");
var msgBodyText = GetMsgBody(msgbase, pollMsgHdrs[currentMsgIdx], pSubBoardCode, user);
if (msgBodyText == null)
msgBodyText = "Unable to load poll";
// TODO: Finish this
console.print(msgBodyText); // Temporary
console.pause(); // Temporary
continueOn = false; // Temporary
/*
// Create a frame object to display
var displayFrame = new Frame(1, // x: Horizontal coordinate of top left
displayMsgHdr.length + 1, // y: Vertical coordinate of top left
console.screen_columns, // Width
console.screen_rows - displayMsgHdr.length - 1, // Height
BG_BLACK);
displayFrame.v_scroll = true;
displayFrame.h_scroll = false;
displayFrame.scrollbars = true;
var displayFrameScrollbar = new ScrollBar(displayFrame, {bg: BG_BLACK, fg: LIGHTGRAY, orientation: "vertical", autohide: false});
// Load the poll text into the Frame object and draw the frame
displayFrame.putmsg(msgBodyText);
displayFrame.draw();
// TODO: Finish this
console.pause(); // Temporary
continueOn = false; // Temporary
*/
}
msgbase.close();
}
return nextProgramState;
}
// Returns an array with lines to display on the screen for a default message header
function GetDefaultMsgDisplayHdr()
{
var hdrDisplayLines = [];
// Group name: 20% of console width
// Sub-board name: 34% of console width
var msgGrpNameLen = Math.floor(console.screen_columns * 0.2);
var subBoardNameLen = Math.floor(console.screen_columns * 0.34);
var hdrLine1 = "\1n\1h\1c" + UPPER_LEFT_SINGLE + HORIZONTAL_SINGLE + "\1n\1c"
+ HORIZONTAL_SINGLE + " \1h@GRP-L";
var numChars = msgGrpNameLen - 7;
for (var i = 0; i < numChars; ++i)
hdrLine1 += "#";
hdrLine1 += "@ @SUB-L";
numChars = subBoardNameLen - 7;
for (var i = 0; i < numChars; ++i)
hdrLine1 += "#";
hdrLine1 += "@\1k";
numChars = console.screen_columns - console.strlen(hdrLine1) - 4;
for (var i = 0; i < numChars; ++i)
hdrLine1 += HORIZONTAL_SINGLE;
hdrLine1 += "\1n\1c" + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h"
+ HORIZONTAL_SINGLE + UPPER_RIGHT_SINGLE;
hdrDisplayLines.push(hdrLine1);
var hdrLine2 = "\1n\1c" + VERTICAL_SINGLE + "\1h\1k" + BLOCK1 + BLOCK2
+ BLOCK3 + "\1gM\1n\1gsg#\1h\1c: \1b@MSG_NUM_AND_TOTAL-L";
numChars = console.screen_columns - 32;
for (var i = 0; i < numChars; ++i)
hdrLine2 += "#";
hdrLine2 += "@\1n\1c" + VERTICAL_SINGLE;
hdrDisplayLines.push(hdrLine2);
var hdrLine3 = "\1n\1h\1k" + VERTICAL_SINGLE + BLOCK1 + BLOCK2 + BLOCK3
+ "\1gF\1n\1grom\1h\1c: \1b@MSG_FROM-L";
numChars = console.screen_columns - 23;
for (var i = 0; i < numChars; ++i)
hdrLine3 += "#";
hdrLine3 += "@\1k" + VERTICAL_SINGLE;
hdrDisplayLines.push(hdrLine3);
var hdrLine4 = "\1n\1h\1k" + VERTICAL_SINGLE + BLOCK1 + BLOCK2 + BLOCK3
+ "\1gS\1n\1gubj\1h\1c: \1b@MSG_SUBJECT-L";
numChars = console.screen_columns - 26;
for (var i = 0; i < numChars; ++i)
hdrLine4 += "#";
hdrLine4 += "@\1k" + VERTICAL_SINGLE;
hdrDisplayLines.push(hdrLine4);
var hdrLine5 = "\1n\1c" + VERTICAL_SINGLE + "\1h\1k" + BLOCK1 + BLOCK2 + BLOCK3
+ "\1gD\1n\1gate\1h\1c: \1b@MSG_DATE-L";
//numChars = console.screen_columns - 23;
numChars = console.screen_columns - 67;
for (var i = 0; i < numChars; ++i)
hdrLine5 += "#";
//hdrLine5 += "@\1n\1c" + VERTICAL_SINGLE;
hdrLine5 += "@ @MSG_TIMEZONE@\1n";
for (var i = 0; i < 40; ++i)
hdrLine5 += " ";
hdrLine5 += "\1n\1c" + VERTICAL_SINGLE;
hdrDisplayLines.push(hdrLine5);
var hdrLine6 = "\1n\1h\1c" + BOTTOM_T_SINGLE + HORIZONTAL_SINGLE + "\1n\1c"
+ HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h\1k";
numChars = console.screen_columns - 8;
for (var i = 0; i < numChars; ++i)
hdrLine6 += HORIZONTAL_SINGLE;
hdrLine6 += "\1n\1c" + HORIZONTAL_SINGLE + HORIZONTAL_SINGLE + "\1h"
+ HORIZONTAL_SINGLE + BOTTOM_T_SINGLE;
hdrDisplayLines.push(hdrLine6);
return hdrDisplayLines;
}
function GetDisplayMsgHdrForMsg(pMsgHdr, pOriginalDisplayHdrLines, pSubBoardCode, pNumMsgs, pDisplayMsgNum, pDateTimeStr, pBBSLocalTimeZone, pAllowCLS)
{
var displayHdrForMsg = [];
for (var i = 0; i < pOriginalDisplayHdrLines.length; ++i)
{
var hdrLine = ParseMsgAtCodes(pOriginalDisplayHdrLines[i], pMsgHdr, pSubBoardCode, pNumMsgs, pDisplayMsgNum, pDateTimeStr, pBBSLocalTimeZone, pAllowCLS);
displayHdrForMsg.push(hdrLine);
}
return displayHdrForMsg;
}
// Replaces a given @-code format string in a text line with the appropriate
// message header info or BBS system info.
//
// Parameters:
// pMsgHdr: The object containing the message header information
// pSubBoardCode: The internal code of the sub-board that the message header is from
// pNumMsgs: The total number of messages available
// pDisplayMsgNum: The message number, if different from the number in the header
// object (i.e., if doing a message search). This parameter can
// be null, in which case the number in the header object will be
// used.
// pTextLine: The text line in which to perform the replacement
// pSpecifiedLen: The length extracted from the @-code format string
// pAtCodeStr: The @-code format string, which will be replaced with the actual message info
// pDateTimeStr: Formatted string containing the date & time
// pUseBBSLocalTimeZone: Boolean - Whether or not pDateTimeStr is in the BBS's local time zone.
// pMsgMainAttrStr: A string describing the main message attributes ('attr' property of header)
// pMsgAuxAttrStr: A string describing the auxiliary message attributes ('auxattr' property of header)
// pMsgNetAttrStr: A string describing the network message attributes ('netattr' property of header)
// pMsgAllAttrStr: A string describing all message attributes
// pDashJustifyIdx: Optional - The index of the -L or -R in the @-code string
function ReplaceMsgAtCodeFormatStr(pMsgHdr, pSubBoardCode, pNumMsgs, pDisplayMsgNum, pTextLine, pSpecifiedLen,
pAtCodeStr, pDateTimeStr, pUseBBSLocalTimeZone, pMsgMainAttrStr, pMsgAuxAttrStr,
pMsgNetAttrStr, pMsgAllAttrStr, pDashJustifyIdx)
{
var readingPersonalEmail = (pSubBoardCode == "mail");
if (typeof(pDashJustifyIdx) != "number")
pDashJustifyIdx = findDashJustifyIndex(pAtCodeStr);
// Specify the format string with left or right justification based on the justification
// character (either L or R).
var formatStr = ((/L/i).test(pAtCodeStr.charAt(pDashJustifyIdx+1)) ? "%-" : "%") + pSpecifiedLen + "s";
// Specify the replacement text depending on the @-code string
var replacementTxt = "";
if (pAtCodeStr.indexOf("@MSG_FROM") > -1)
replacementTxt = pMsgHdr["from"].substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_FROM_EXT") > -1)
replacementTxt = (typeof pMsgHdr["from_ext"] === "undefined" ? "" : pMsgHdr["from_ext"].substr(0, pSpecifiedLen));
else if ((pAtCodeStr.indexOf("@MSG_TO") > -1) || (pAtCodeStr.indexOf("@MSG_TO_NAME") > -1))
replacementTxt = pMsgHdr["to"].substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_TO_EXT") > -1)
replacementTxt = (typeof pMsgHdr["to_ext"] === "undefined" ? "" : pMsgHdr["to_ext"].substr(0, pSpecifiedLen));
else if (pAtCodeStr.indexOf("@MSG_SUBJECT") > -1)
replacementTxt = pMsgHdr["subject"].substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_DATE") > -1)
replacementTxt = pDateTimeStr.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_ATTR") > -1)
replacementTxt = pMsgMainAttrStr.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_AUXATTR") > -1)
replacementTxt = pMsgAuxAttrStr.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_NETATTR") > -1)
replacementTxt = pMsgNetAttrStr.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_ALLATTR") > -1)
replacementTxt = pMsgAllAttrStr.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@MSG_NUM_AND_TOTAL") > -1)
{
var messageNum = (typeof(pDisplayMsgNum) == "number" ? pDisplayMsgNum : pMsgHdr["offset"]+1);
replacementTxt = (messageNum.toString() + "/" + pNumMsgs).substr(0, pSpecifiedLen); // "number" is also absolute number
}
else if (pAtCodeStr.indexOf("@MSG_NUM") > -1)
{
var messageNum = (typeof(pDisplayMsgNum) == "number" ? pDisplayMsgNum : pMsgHdr["offset"]+1);
replacementTxt = messageNum.toString().substr(0, pSpecifiedLen); // "number" is also absolute number
}
else if (pAtCodeStr.indexOf("@MSG_ID") > -1)
replacementTxt = (typeof pMsgHdr["id"] === "undefined" ? "" : pMsgHdr["id"].substr(0, pSpecifiedLen));
else if (pAtCodeStr.indexOf("@MSG_REPLY_ID") > -1)
replacementTxt = (typeof pMsgHdr["reply_id"] === "undefined" ? "" : pMsgHdr["reply_id"].substr(0, pSpecifiedLen));
else if (pAtCodeStr.indexOf("@MSG_TIMEZONE") > -1)
{
if (pUseBBSLocalTimeZone)
replacementTxt = system.zonestr(system.timezone).substr(0, pSpecifiedLen);
else
replacementTxt = system.zonestr(pMsgHdr["when_written_zone"]).substr(0, pSpecifiedLen);
}
else if (pAtCodeStr.indexOf("@GRP") > -1)
{
if (readingPersonalEmail)
replacementTxt = "Personal mail".substr(0, pSpecifiedLen);
else
replacementTxt = msg_area.sub[pSubBoardCode].grp_name.substr(0, pSpecifiedLen);
}
else if (pAtCodeStr.indexOf("@GRPL") > -1)
{
if (readingPersonalEmail)
replacementTxt = "Personal mail".substr(0, pSpecifiedLen);
else
{
var grpIdx = msg_area.sub[pSubBoardCode].grp_index;
replacementTxt = msg_area.grp_list[grpIdx].description.substr(0, pSpecifiedLen);
}
}
else if (pAtCodeStr.indexOf("@SUB") > -1)
{
if (readingPersonalEmail)
replacementTxt = "Personal mail".substr(0, pSpecifiedLen);
else
replacementTxt = msg_area.sub[pSubBoardCode].name.substr(0, pSpecifiedLen);
}
else if (pAtCodeStr.indexOf("@SUBL") > -1)
{
if (readingPersonalEmail)
replacementTxt = "Personal mail".substr(0, pSpecifiedLen);
else
replacementTxt = msg_area.sub[pSubBoardCode].description.substr(0, pSpecifiedLen);
}
else if ((pAtCodeStr.indexOf("@BBS") > -1) || (pAtCodeStr.indexOf("@BOARDNAME") > -1))
replacementTxt = system.name.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@SYSOP") > -1)
replacementTxt = system.operator.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@CONF") > -1)
{
var msgConfDesc = msg_area.grp_list[msg_area.sub[pSubBoardCode].grp_index].name + " "
+ msg_area.sub[pSubBoardCode].name;
replacementTxt = msgConfDesc.substr(0, pSpecifiedLen);
}
else if (pAtCodeStr.indexOf("@DATE") > -1)
replacementTxt = system.datestr().substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@DIR") > -1)
{
if ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number"))
replacementTxt = file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].name.substr(0, pSpecifiedLen);
else
replacementTxt = "";
}
else if (pAtCodeStr.indexOf("@DIRL") > -1)
{
if ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number"))
replacementTxt = file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].description.substr(0, pSpecifiedLen);
else
replacementTxt = "";
}
else if ((pAtCodeStr.indexOf("@ALIAS") > -1) || (pAtCodeStr.indexOf("@USER") > -1))
replacementTxt = user.alias.substr(0, pSpecifiedLen);
else if (pAtCodeStr.indexOf("@NAME") > -1) // User name or alias
{
var userNameOrAlias = (user.name.length > 0 ? user.name : user.alias);
replacementTxt = userNameOrAlias.substr(0, pSpecifiedLen);
}
// Do the text replacement (escape special characters in the @-code string so we can do a literal search)
var searchRegex = new RegExp(pAtCodeStr.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), "gi");
return pTextLine.replace(searchRegex, format(formatStr, replacementTxt));
}
// Returns the index of the -L or -R in one of the @-code strings.
//
// Parameters:
// pAtCodeStr: The @-code string to search
//
// Return value: The index of the -L or -R, or -1 if not found
function findDashJustifyIndex(pAtCodeStr)
{
var strIndex = pAtCodeStr.indexOf("-");
if (strIndex > -1)
{
// If this part of the string is not -L or -R, then set strIndex to -1
// to signify that it was not found.
var checkStr = pAtCodeStr.substr(strIndex, 2).toUpperCase();
if ((checkStr != "-L") && (checkStr != "-R"))
strIndex = -1;
}
return strIndex;
}
// Looks for complex @-code strings in a text line and parses & replaces them
// appropriately with the appropriate info from the message header object and/or
// message base object. This is more complex than simple text substitution
// because message subject @-codes could include something like @MSG_SUBJECT-L######@
// or @MSG_SUBJECT-R######@ or @MSG_SUBJECT-L20@ or @MSG_SUBJECT-R20@.
//
// Parameters:
// pTextLine: The line of text to search
// pMsgHdr: The message header object
// pSubBoardCode: The internal code of the sub-board
// pNumMsgs: The total number of messages available
// pDisplayMsgNum: The message number, if different from the number in the header
// object (i.e., if doing a message search). This parameter can
// be null, in which case the number in the header object will be
// used.
// pDateTimeStr: Optional formatted string containing the date & time. If this is
// not provided, the current date & time will be used.
// pBBSLocalTimeZone: Optional boolean - Whether or not pDateTimeStr is in the BBS's
// local time zone. Defaults to false.
// pAllowCLS: Optional boolean - Whether or not to allow the @CLS@ code.
// Defaults to false.
//
// Return value: A string with the complex @-codes substituted in the line with the
// appropriate message header information.
function ParseMsgAtCodes(pTextLine, pMsgHdr, pSubBoardCode, pNumMsgs, pDisplayMsgNum, pDateTimeStr, pBBSLocalTimeZone, pAllowCLS)
{
var textLine = pTextLine;
var dateTimeStr = "";
var useBBSLocalTimeZone = false;
if (typeof(pDateTimeStr) == "string")
{
dateTimeStr = pDateTimeStr;
if (typeof(pBBSLocalTimeZone) == "boolean")
useBBSLocalTimeZone = pBBSLocalTimeZone;
}
else
dateTimeStr = strftime("%Y-%m-%d %H:%M:%S", pMsgHdr.when_written_date);
// Message attribute strings
var allMsgAttrStr = makeAllMsgAttrStr(pMsgHdr);
var mainMsgAttrStr = makeMainMsgAttrStr(pMsgHdr.attr);
var auxMsgAttrStr = makeAuxMsgAttrStr(pMsgHdr.auxattr);
var netMsgAttrStr = makeNetMsgAttrStr(pMsgHdr.netattr);
// An array of @-code strings without the trailing @, to be used for constructing
// regular expressions to look for versions with justification & length specifiers.
// The order of the strings in this array matters. For instance, @MSG_NUM_AND_TOTAL
// needs to come before @MSG_NUM so that it gets processed properly, since they
// both start out with the same text.
var atCodeStrBases = ["@MSG_FROM", "@MSG_FROM_EXT", "@MSG_TO", "@MSG_TO_NAME", "@MSG_TO_EXT",
"@MSG_SUBJECT", "@MSG_DATE", "@MSG_ATTR", "@MSG_AUXATTR", "@MSG_NETATTR",
"@MSG_ALLATTR", "@MSG_NUM_AND_TOTAL", "@MSG_NUM", "@MSG_ID",
"@MSG_REPLY_ID", "@MSG_TIMEZONE", "@GRP", "@GRPL", "@SUB", "@SUBL",
"@BBS", "@BOARDNAME", "@ALIAS", "@SYSOP", "@CONF", "@DATE", "@DIR", "@DIRL",
"@USER", "@NAME"];
// For each @-code, look for a version with justification & length specified and
// replace accordingly.
for (var atCodeStrBaseIdx in atCodeStrBases)
{
var atCodeStrBase = atCodeStrBases[atCodeStrBaseIdx];
// Synchronet @-codes can specify justification with -L or -R and width using a series
// of non-numeric non-space characters (i.e., @MSG_SUBJECT-L#####@ or @MSG_SUBJECT-R######@).
// So look for these types of format specifiers for the message subject and if found,
// parse and replace appropriately.
var multipleCharLenRegex = new RegExp(atCodeStrBase + "-[LR][^0-9 ]+@", "gi");
var atCodeMatchArray = textLine.match(multipleCharLenRegex);
if ((atCodeMatchArray != null) && (atCodeMatchArray.length > 0))
{
for (var idx in atCodeMatchArray)
{
// In this case, the subject length is the length of the whole format specifier.
var substLen = atCodeMatchArray[idx].length;
textLine = ReplaceMsgAtCodeFormatStr(pMsgHdr, pSubBoardCode, pNumMsgs, pDisplayMsgNum, textLine, substLen,
atCodeMatchArray[idx], pDateTimeStr, useBBSLocalTimeZone,
mainMsgAttrStr, auxMsgAttrStr, netMsgAttrStr, allMsgAttrStr);
}
}
// Now, look for subject formatters with the length specified (i.e., @MSG_SUBJECT-L20@ or @MSG_SUBJECT-R20@)
var numericLenSearchRegex = new RegExp(atCodeStrBase + "-[LR][0-9]+@", "gi");
atCodeMatchArray = textLine.match(numericLenSearchRegex);
if ((atCodeMatchArray != null) && (atCodeMatchArray.length > 0))
{
for (var idx in atCodeMatchArray)
{
// Extract the length specified between the -L or -R and the final @.
var dashJustifyIndex = findDashJustifyIndex(atCodeMatchArray[idx]);
var substLen = atCodeMatchArray[idx].substring(dashJustifyIndex+2, atCodeMatchArray[idx].length-1);
textLine = ReplaceMsgAtCodeFormatStr(pMsgHdr, pSubBoardCode, pNumMsgs, pDisplayMsgNum, textLine, substLen, atCodeMatchArray[idx],
pDateTimeStr, useBBSLocalTimeZone, mainMsgAttrStr, mainMsgAttrStr,
auxMsgAttrStr, netMsgAttrStr, allMsgAttrStr, dashJustifyIndex);
}
}
}
// In case there weren't any complex @-codes, do replacements for the basic
// @-codes. Set the group & sub-board information as Personal Mail or the
// sub-board currently being read.
var grpIdx = -1;
var groupName = "";
var groupDesc = "";
var subName = "";
var subDesc = "";
var msgConf = "";
var fileDir = "";
var fileDirLong = "";
if (pSubBoardCode == "mail")
{
var subName = "Personal mail";
var subDesc = "Personal mail";
}
else
{
grpIdx = msg_area.sub[pSubBoardCode].grp_index;
groupName = msg_area.sub[pSubBoardCode].grp_name;
groupDesc = msg_area.grp_list[grpIdx].description;
subName = msg_area.sub[pSubBoardCode].name;
subDesc = msg_area.sub[pSubBoardCode].description;
msgConf = msg_area.grp_list[msg_area.sub[pSubBoardCode].grp_index].name + " "
+ msg_area.sub[pSubBoardCode].name;
}
if ((typeof(bbs.curlib) == "number") && (typeof(bbs.curdir) == "number"))
{
if ((typeof(file_area.lib_list[bbs.curlib]) == "object") && (typeof(file_area.lib_list[bbs.curlib].dir_list[bbs.curdir]) == "object"))
{
fileDir = file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].name;
fileDirLong = file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].description;
}
}
var messageNum = (typeof(pDisplayMsgNum) == "number" ? pDisplayMsgNum : pMsgHdr["offset"]+1);
var msgUpvotes = 0;
var msgDownvotes = 0;
var msgVoteScore = 0;
if (pMsgHdr.hasOwnProperty("total_votes") && pMsgHdr.hasOwnProperty("upvotes"))
{
msgUpvotes = pMsgHdr.upvotes;
msgDownvotes = pMsgHdr.total_votes - pMsgHdr.upvotes;
msgVoteScore = pMsgHdr.upvotes - msgDownvotes;
}
var newTxtLine = textLine.replace(/@MSG_SUBJECT@/gi, pMsgHdr["subject"])
.replace(/@MSG_FROM@/gi, pMsgHdr["from"])
.replace(/@MSG_FROM_EXT@/gi, (typeof(pMsgHdr["from_ext"]) == "string" ? pMsgHdr["from_ext"] : ""))
.replace(/@MSG_TO@/gi, pMsgHdr["to"])
.replace(/@MSG_TO_NAME@/gi, pMsgHdr["to"])
.replace(/@MSG_TO_EXT@/gi, (typeof(pMsgHdr["to_ext"]) == "string" ? pMsgHdr["to_ext"] : ""))
.replace(/@MSG_DATE@/gi, pDateTimeStr)
.replace(/@MSG_ATTR@/gi, mainMsgAttrStr)
.replace(/@MSG_AUXATTR@/gi, auxMsgAttrStr)
.replace(/@MSG_NETATTR@/gi, netMsgAttrStr)
.replace(/@MSG_ALLATTR@/gi, allMsgAttrStr)
.replace(/@MSG_NUM_AND_TOTAL@/gi, messageNum.toString() + "/" + pNumMsgs)
.replace(/@MSG_NUM@/gi, messageNum.toString())
.replace(/@MSG_ID@/gi, (typeof(pMsgHdr["id"]) == "string" ? pMsgHdr["id"] : ""))
.replace(/@MSG_REPLY_ID@/gi, (typeof(pMsgHdr["reply_id"]) == "string" ? pMsgHdr["reply_id"] : ""))
.replace(/@MSG_FROM_NET@/gi, (typeof(pMsgHdr["from_net_addr"]) == "string" ? pMsgHdr["from_net_addr"] : ""))
.replace(/@MSG_TO_NET@/gi, (typeof(pMsgHdr["to_net_addr"]) == "string" ? pMsgHdr["to_net_addr"] : ""))
.replace(/@MSG_TIMEZONE@/gi, (useBBSLocalTimeZone ? system.zonestr(system.timezone) : system.zonestr(pMsgHdr["when_written_zone"])))
.replace(/@GRP@/gi, groupName)
.replace(/@GRPL@/gi, groupDesc)
.replace(/@SUB@/gi, subName)
.replace(/@SUBL@/gi, subDesc)
.replace(/@CONF@/gi, msgConf)
.replace(/@BBS@/gi, system.name)
.replace(/@BOARDNAME@/gi, system.name)
.replace(/@SYSOP@/gi, system.operator)
.replace(/@DATE@/gi, system.datestr())
.replace(/@LOCATION@/gi, system.location)
.replace(/@DIR@/gi, fileDir)
.replace(/@DIRL@/gi, fileDirLong)
.replace(/@ALIAS@/gi, user.alias)
.replace(/@NAME@/gi, (user.name.length > 0 ? user.name : user.alias))
.replace(/@USER@/gi, user.alias)
.replace(/@MSG_UPVOTES@/gi, msgUpvotes)
.replace(/@MSG_DOWNVOTES@/gi, msgDownvotes)
.replace(/@MSG_SCORE@/gi, msgVoteScore);
if (!pAllowCLS)
newTxtLine = newTxtLine.replace(/@CLS@/gi, "");
return newTxtLine;
}
// Returns a string describing all message attributes (main, auxiliary, and net).
//
// Parameters:
// pMsgHdr: A message header object.
//
// Return value: A string describing all of the message attributes
function makeAllMsgAttrStr(pMsgHdr)
{
if ((pMsgHdr == null) || (typeof(pMsgHdr) != "object"))
return "";
var msgAttrStr = makeMainMsgAttrStr(pMsgHdr.attr);
var auxAttrStr = makeAuxMsgAttrStr(pMsgHdr.auxattr);
if (auxAttrStr.length > 0)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += auxAttrStr;
}
var netAttrStr = makeNetMsgAttrStr(pMsgHdr.netattr);
if (netAttrStr.length > 0)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += netAttrStr;
}
return msgAttrStr;
}
// Returns a string describing the main message attributes. Makes use of the
// gMainMsgAttrStrs object for the main message attributes and description
// strings.
//
// Parameters:
// pMainMsgAttrs: The bit field for the main message attributes
// (normally, the 'attr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the main message attributes
function makeMainMsgAttrStr(pMainMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pMainMsgAttrs) == "number")
{
for (var prop in gMainMsgAttrStrs)
{
if ((pMainMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gMainMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
return msgAttrStr;
}
// Returns a string describing auxiliary message attributes. Makes use of the
// gAuxMsgAttrStrs object for the auxiliary message attributes and description
// strings.
//
// Parameters:
// pAuxMsgAttrs: The bit field for the auxiliary message attributes
// (normally, the 'auxattr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the auxiliary message attributes
function makeAuxMsgAttrStr(pAuxMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pAuxMsgAttrs) == "number")
{
for (var prop in gAuxMsgAttrStrs)
{
if ((pAuxMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gAuxMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
return msgAttrStr;
}
// Returns a string describing network message attributes. Makes use of the
// gNetMsgAttrStrs object for the network message attributes and description
// strings.
//
// Parameters:
// pNetMsgAttrs: The bit field for the network message attributes
// (normally, the 'netattr' property of a header object)
// pIfEmptyString: Optional - A string to use if there are no attributes set
//
// Return value: A string describing the network message attributes
function makeNetMsgAttrStr(pNetMsgAttrs, pIfEmptyString)
{
var msgAttrStr = "";
if (typeof(pNetMsgAttrs) == "number")
{
for (var prop in gNetMsgAttrStrs)
{
if ((pNetMsgAttrs & prop) == prop)
{
if (msgAttrStr.length > 0)
msgAttrStr += ", ";
msgAttrStr += gNetMsgAttrStrs[prop];
}
}
}
if ((msgAttrStr.length == 0) && (typeof(pIfEmptyString) == "string"))
msgAttrStr = pIfEmptyString;
return msgAttrStr;
}
// Adjusts a message's when-written time to the BBS's local time.
//
// Parameters:
// pMsgHdr: A message header object
//
// Return value: The message's when_written_time adjusted to the BBS's local time.
// If the message header doesn't have a when_written_time or
// when_written_zone property, then this function will return -1.
function MsgWrittenTimeToLocalBBSTime(pMsgHdr)
{
if (!pMsgHdr.hasOwnProperty("when_written_time") || !pMsgHdr.hasOwnProperty("when_written_zone_offset") || !pMsgHdr.hasOwnProperty("when_imported_zone_offset"))
return -1;
var timeZoneDiffMinutes = pMsgHdr.when_imported_zone_offset - pMsgHdr.when_written_zone_offset;
//var timeZoneDiffMinutes = pMsgHdr.when_written_zone - system.timezone;
var timeZoneDiffSeconds = timeZoneDiffMinutes * 60;
var msgWrittenTimeAdjusted = pMsgHdr.when_written_time + timeZoneDiffSeconds;
return msgWrittenTimeAdjusted;
}