From dbee188b1fa2fad2c3eba605382a21be4da96141 Mon Sep 17 00:00:00 2001
From: nightfox <>
Date: Thu, 12 Jan 2017 03:17:27 +0000
Subject: [PATCH] Added the ability to vote on a topic from the result viewer
 interface.  Also, updated the documentation a bit. SlyVote now has just
 enough functionality to be a useful voting both door, but I still want to add
 & update a few things.  I want to implement the "Answer all topics"
 functionality, add the ability in the result viewer to type a topic number
 and jump to it, add the ability for sysops to delete voting topics (poll
 messages) from SlyVote, and also improve some of the screen displays a bit. 
 I might also possibly add a help/informational screen.

---
 xtrn/SlyVote/Read Me.txt | 45 +++++++++++++++++++-----
 xtrn/SlyVote/SlyVote.js  | 76 +++++++++++++++++++++++++++++++---------
 2 files changed, 97 insertions(+), 24 deletions(-)

diff --git a/xtrn/SlyVote/Read Me.txt b/xtrn/SlyVote/Read Me.txt
index 4f3a488d7f..dff58b2e18 100644
--- a/xtrn/SlyVote/Read Me.txt	
+++ b/xtrn/SlyVote/Read Me.txt	
@@ -1,6 +1,6 @@
                                    SlyVote
-                                 Version 0.01 Beta
-                           Release date: 2017-01-01
+                                 Version 0.02 Beta
+                           Release date: 2017-01-11
 
                                      by
 
@@ -13,8 +13,6 @@
 
 
 
-This file describes SlyVote.
-
 Contents
 ========
 1. Disclaimer
@@ -36,8 +34,12 @@ SlyVote is a voting booth door for Synchronet, which allows users to add and
 vote on various topics with multiple-choice answers.  SlyVote makes use of the
 voting capabilities in the Synchronet messagebase, which were added to
 Synchronet 3.17.  Thus, SlyVote requires version 3.17 or higher of Synchronet,
-along with the latest sbbsdefs.js and possibly other JavaScript files included
-with Synchronet.
+along with the latest JavaScript files for Synchronet, such as the following:
+- sbbsdefs.js
+- text.js
+- frame.js
+- scrollbar.js
+- Possibly other JavaScript files
 
 SlyVote requires an ANSI terminal, since SlyVote makes use of lightbar menus
 which do cursor movement, etc.
@@ -51,6 +53,14 @@ yet.
 
 3. Installation & Setup
 =======================
+First, ensure that you have an up-to-date Synchronet system running Synchronet
+3.17 or higher, and ensure that your Synchronet JavaScript files (in
+sbbs/exec/load) are up to date.  Before the official Synchronet 3.17 is/was
+released, daily 3.17 beta builds can be downloaded from Vertrauen (the home
+BBS of Synchronet).  See the section below titled "Updating Synchronet".
+
+Setting up SlyVote
+------------------
 SlyVote is comprised of the following files:
 1. SlyVote.js             The SlyVote script
 
@@ -113,6 +123,22 @@ can add a menu command to run SlyVote as follows:
 bbs.exec("?../xtrn/SlyVote/SlyVote.js");
 
 
+Updating Synchronet
+-------------------
+SlyVote requires Synchronet 3.17 or higher, along with the latest Synchronet
+JavaScript files.
+Daily-built Windows beta binaries can be downloaded from this URL:
+ftp://vert.synchro.net/main/SBBS/sbbs_dev.zip
+Daily-built Linux x64 binaries can be downloaded here:
+ftp://vert.synchro.net/main/SBBS/sbbs_dev.tgz
+To update your JavaScript files, first download the sbbs_run archive - Either
+one of the following:
+With CRLF line endings: ftp://vert.synchro.net/main/SBBS/sbbs_run.zip
+With CR line endings: ftp://vert.synchro.net/main/SBBS/sbbs_run.tgz
+Then, extract the archive and copy the files from sbbs/exec/load into your
+sbbs/exec/load directory.
+
+
 4. Configuration file
 =====================
 The format of SlyVote.cfg is setting=value.  SlyVote.cfg supports the following
@@ -127,5 +153,8 @@ subBoardCodes                         A comma-separated list of internal
                                       specified if you only want to use one
                                       sub-board with SlyVote.  Also,
                                       subBoardCodes can appear multiple times
-                                      in SlyVote.cfg, and all sub-boards will
-                                      be used.
\ No newline at end of file
+                                      in SlyVote.cfg, and all specified
+                                      sub-boards will be used.  If any of the
+                                      specified sub-board codes don't exist or
+                                      refer to sub-boards that don't allow
+                                      voting, then they will not be used.
diff --git a/xtrn/SlyVote/SlyVote.js b/xtrn/SlyVote/SlyVote.js
index 67f8315081..5e2b4b1cad 100644
--- a/xtrn/SlyVote/SlyVote.js
+++ b/xtrn/SlyVote/SlyVote.js
@@ -10,6 +10,12 @@
  * 2016-12-29 Eric Oulashin     Version 0.01 Beta
  *                              Started
  */
+ 
+ // TODO:
+ // - Answer all topics
+ // - In the result viewer, allow users to type in a topic number and jump to it
+ // - Allow sysops to delete polls
+ // - Add a help screen?
 
 
 load("sbbsdefs.js");
@@ -53,8 +59,8 @@ load("scrollbar.js");
 load("DDLightbarMenu.js");
 
 // Version information
-var SLYVOTE_VERSION = "0.01 Beta";
-var SLYVOTE_DATE = "2017-01-10";
+var SLYVOTE_VERSION = "0.03 Beta";
+var SLYVOTE_DATE = "2017-01-11";
 
 // Determine the script's startup directory.
 // This code is a trick that was created by Deuce, suggested by Rob Swindell
@@ -214,8 +220,6 @@ else
 	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;
@@ -316,7 +320,7 @@ function ChooseVoteTopic()
 		console.print("\1n\1y\1h" + voteTopicInfo.errorMsg + "\1n");
 		console.crlf();
 		console.pause();
-		return;
+		return MAIN_MENU;
 	}
 	else if (voteTopicInfo.msgHdrs.length == 0)
 	{
@@ -324,7 +328,7 @@ function ChooseVoteTopic()
 		console.print("\1n\1cThere are no polls to vote on in this section\1n");
 		console.crlf();
 		console.pause();
-		return;
+		return MAIN_MENU;
 	}
 
 	// Display the list of voting topics
@@ -361,7 +365,7 @@ function ChooseVoteTopic()
 			//topicsMenu.Erase();
 			console.gotoxy(18, pleaseSelectTextRow);
 			printf("%" + strip_ctrl(pleaseSectTopicText).length + "s", "");
-			var voteRetObj = VoteOnTopic(gSubBoardCode, userChoice, startCol, listTopRow, textLen, menuHeight);
+			var voteRetObj = ChooseVoteTopic(gSubBoardCode, userChoice, startCol, listTopRow, textLen, menuHeight);
 			drawTopicsMenu = true;
 			if (voteRetObj.errorMsg.length > 0)
 			{
@@ -390,7 +394,7 @@ function ChooseVoteTopic()
 	return nextProgramState;
 }
 
-// Lets the user vote on a topic
+// Lets the user choose a topic to vote on, and then vote on it
 //
 // Parameters:
 //  pSubBoardCode: The internal code of the sub-board
@@ -403,7 +407,7 @@ function ChooseVoteTopic()
 // 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)
+function ChooseVoteTopic(pSubBoardCode, pMsgNum, pStartCol, pStartRow, pMenuWidth, pMenuHeight)
 {
 	var retObj = {
 		errorMsg: "",
@@ -443,7 +447,7 @@ function VoteOnTopic(pSubBoardCode, pMsgNum, pStartCol, pStartRow, pMenuWidth, p
 				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);
+				var voteRetObj = VoteOnTopic(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);
@@ -480,7 +484,8 @@ function VoteOnTopic(pSubBoardCode, pMsgNum, pStartCol, pStartRow, pMenuWidth, p
 		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.errorMsg = bbs.text(typeof(VotedAlready) != "undefined" ? VotedAlready : 780);
+			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;
 		}
 
@@ -904,7 +909,7 @@ function GetPollTextAndOpts(pMsgHdr)
 //               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)
+function VoteOnTopic(pSubBoardCode, pMsgbase, pMsgHdr, pUser, pUserVoteNumber, pRemoveNLsFromVoteText)
 {
 	var retObj = new Object();
 	retObj.BBSHasVoteFunction = (typeof(pMsgbase.vote_msg) === "function");
@@ -1022,7 +1027,7 @@ function VoteOnMessage(pSubBoardCode, pMsgbase, pMsgHdr, pUser, pUserVoteNumber,
 			if (typeof(pUserVoteNumber) != "number")
 			{
 				console.clear("\1n");
-				var selectHdr = bbs.text(SelectItemHdr);
+				var selectHdr = bbs.text(typeof(SelectItemHdr) != "undefined" ? SelectItemHdr : 501);
 				printf("\1n" + selectHdr + "\1n", pMsgHdr.subject);
 				var optionFormatStr = "\1n\1c\1h%2d\1n\1c: \1h%s\1n";
 				var optionNum = 1;
@@ -1037,7 +1042,7 @@ function VoteOnMessage(pSubBoardCode, pMsgbase, pMsgHdr, pUser, pUserVoteNumber,
 				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);
+				var selectPromptText = bbs.text(typeof(SelectItemWhich) != "undefined" ? SelectItemWhich : 503);
 				selectPromptText = selectPromptText.replace(/%[uU]/, 1).replace(/%[dD]/, 1);
 				console.mnemonics(selectPromptText);
 				// Get & process the selection from the user
@@ -1265,7 +1270,7 @@ function ViewVoteResults(pSubBoardCode)
 				pollMsgHdrs.push(msgHdrs[prop]);
 		}
 		delete msgHdrs; // Free some memory
-
+		
 		// If there are no polls, then just return
 		if (pollMsgHdrs.length == 0)
 		{
@@ -1306,12 +1311,20 @@ function ViewVoteResults(pSubBoardCode)
 		// User input loop
 		var currentMsgIdx = 0;
 		var drawMsg = true;
+		var drawKeyHelpLine = false;
 		var continueOn = true;
 		while (continueOn)
 		{
 			// Do garbage collection to ensure low memory usage
 			js.gc(true);
 
+			// Display the key help line if specified to do so
+			if (drawKeyHelpLine)
+			{
+				console.gotoxy(1, console.screen_rows);
+				console.print("\1n" + keyHelpLine);
+			}
+
 			// 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);
@@ -1339,6 +1352,7 @@ function ViewVoteResults(pSubBoardCode)
 				displayFrame.draw();
 			var scrollRetObj = ScrollFrame(displayFrame, displayFrameScrollbar, 0, "\1n", false, 1, console.screen_rows);
 			drawMsg = true;
+			drawKeyHelpLine = false;
 			if (scrollRetObj.lastKeypress == KEY_LEFT)
 			{
 				// Go back one poll topic
@@ -1373,7 +1387,37 @@ function ViewVoteResults(pSubBoardCode)
 			}
 			else if (scrollRetObj.lastKeypress == gReaderKeys.vote)
 			{
-				// TODO: Vote on the topic
+				// Let the user vote on the topic in interactive mode (this uses
+				// traditional style interaction rather than usinga lightbar).
+				var voteRetObj = VoteOnTopic(pSubBoardCode, msgbase, pollMsgHdrs[currentMsgIdx], user, null, true);
+				drawKeyHelpLine = true;
+				// If the user's vote was saved, then update the message header so that it includes
+				// the user's vote information.
+				if (voteRetObj.savedVote)
+				{
+					var msgHdrs = msgbase.get_all_msg_headers(true);
+					pollMsgHdrs[currentMsgIdx] = msgHdrs[pollMsgHdrs[currentMsgIdx].number];
+					delete msgHdrs;
+				}
+				// If there is an error message, then display it.
+				if (voteRetObj.errorMsg.length > 0)
+				{
+					console.gotoxy(1, gMessageRow);
+					if (voteRetObj.mnemonicsRequiredForErrorMsg)
+					{
+						console.mnemonics(voteRetObj.errorMsg);
+						mswait(ERROR_PAUSE_WAIT_MS);
+						console.gotoxy(1, gMessageRow);
+						printf("\1n%" + console.screen_columns + "s", "");
+					}
+					else
+					{
+						console.print("\1n\1y\1h" + voteRetObj.errorMsg);
+						mswait(ERROR_PAUSE_WAIT_MS);
+						console.gotoxy(1, gMessageRow);
+						printf("\1n%" + strip_ctrl(voteRetObj.errorMsg).length + "s", "");
+					}
+				}
 			}
 			else if (/[0-9]/.test(scrollRetObj.lastKeypress))
 			{
-- 
GitLab