diff --git a/frontend/html/index.html b/frontend/html/index.html
index a8463c5..a4958c3 100644
--- a/frontend/html/index.html
+++ b/frontend/html/index.html
@@ -17,33 +17,27 @@
diff --git a/frontend/static/css/main.css b/frontend/static/css/main.css
index d8baba9..953470e 100644
--- a/frontend/static/css/main.css
+++ b/frontend/static/css/main.css
@@ -88,6 +88,7 @@ main .notepad-border {
}
/* #endregion notepad */
/* #region notepad-footer */
+main .notepad .poll-footer input[type="submit"],
main .notepad .poll-footer button {
padding: 0 30px;
height: 50px;
@@ -100,6 +101,7 @@ main .notepad .poll-footer button {
color: #fff;
margin-right: 5px;
}
+main .notepad .poll-footer input[type="submit"]:hover,
main .notepad .poll-footer button:hover {
background-color: #b6b6b6;
}
diff --git a/src/Poll.ts b/src/Poll.ts
index 33ab9bb..9097eb1 100644
--- a/src/Poll.ts
+++ b/src/Poll.ts
@@ -1,9 +1,9 @@
"use strict";
-
+type DupeCheckMode = "none" | "ip" | "cookie";
type BasePoll = {
id: string,
title: string,
- dupeCheckMode: "none" | "ip" | "cookie",
+ dupeCheckMode: DupeCheckMode,
multiSelect: boolean,
captcha: boolean,
creationTime: Date,
@@ -30,5 +30,6 @@ type PollResult = {
export {
FrontendPoll,
BackendPoll,
- PollResult
+ PollResult,
+ DupeCheckMode
};
diff --git a/src/backend.ts b/src/backend.ts
index 182774e..3d03f44 100644
--- a/src/backend.ts
+++ b/src/backend.ts
@@ -4,7 +4,7 @@ import { Router } from "express";
import persist from "node-persist";
import { program } from "commander";
import { resolve } from "path";
-import { BackendPoll as Poll } from "./Poll";
+import { BackendPoll as Poll, DupeCheckMode } from "./Poll";
function randomString(length = 10, charset = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789") {
let result = "";
@@ -60,39 +60,96 @@ export default async function init(router: Router): Promise {
}
});
+ async function createPoll(pollData: {
+ title: string,
+ options: string[],
+ dupeCheckMode: DupeCheckMode,
+ multiSelect: boolean,
+ captcha: boolean
+ }): Promise {
+ if (!Array.isArray(pollData.options) || pollData.options.filter(i => i).length < 2)
+ return "Options must be an array and have at least 2 entries";
+
+ let id = randomString(8);
+ while (await polls.getItem(id)) id = randomString(6);
+ await polls.setItem(id, {});
+
+ const dupeCheckMode = (
+ ["none", "ip", "cookie"].includes((pollData.dupeCheckMode || "").toLowerCase()) ?
+ (pollData.dupeCheckMode || "").toLowerCase() : "ip"
+ ) as DupeCheckMode;
+ const dupeData =
+ dupeCheckMode === "none" ? null :
+ dupeCheckMode === "ip" ? [] :
+ dupeCheckMode === "cookie" ? randomString(16) : null;
+ const poll: Poll = {
+ id,
+ title: (pollData.title || "").trim().slice(0, 300),
+ options: (() => {
+ const result: { [option: string]: number } = {};
+ for (const option of pollData.options.map(i => i.trim().slice(0, 300))) {
+ if (option) result[option] = 0;
+ }
+ return result;
+ })(),
+ dupeCheckMode,
+ dupeData,
+ multiSelect: pollData.multiSelect || false,
+ captcha: pollData.captcha || false,
+ creationTime: new Date()
+ };
+ await polls.setItem(id, poll);
+ return poll;
+ }
+
router.post("/poll", async (req, res) => {
try {
- const options = req.body.options;
- if (!Array.isArray(options) || options.filter(i => i).length < 2)
- return res.status(400).json({ error: "Options must be an array and have at least 2 entries" });
- let id = randomString(8);
- while (await polls.getItem(id)) id = randomString(6);
- await polls.setItem(id, {});
- const dupeCheckMode = ["none", "ip", "cookie"].includes((req.body.dupeCheckMode || "").toLowerCase()) ? (req.body.dupeCheckMode || "").toLowerCase() : "ip";
- const dupeData =
- dupeCheckMode === "none" ? null :
- dupeCheckMode === "ip" ? [] :
- dupeCheckMode === "cookie" ? randomString(16) : null;
- const poll: Poll = {
- id,
+ const poll = await createPoll({
title: (req.body.title || "").trim().slice(0, 300),
- options: (() => {
- const result: { [option: string]: number } = {};
- for (const option of options.map(i => i.trim().slice(0, 300))) {
- if (option) result[option] = 0;
- }
- return result;
- })(),
- dupeCheckMode,
- dupeData,
+ options: req.body.options,
+ dupeCheckMode: req.body.dupeCheckMode,
multiSelect: req.body.multiSelect || false,
captcha: req.body.captcha || false,
- creationTime: new Date()
- };
- await polls.setItem(id, poll);
- res.json({
- id: id
});
+ if (typeof poll !== "string") res.json({
+ id: poll.id
+ });
+ else res.status(400).json({
+ error: poll
+ });
+ } catch (error) {
+ console.error(error);
+ if (error instanceof Error) res.status(500).json({
+ error: error.message
+ });
+ else res.status(500).json({
+ error: error
+ });
+ }
+ });
+ router.post("/poll-form", async (req, res) => {
+ try {
+ const poll = await createPoll({
+ title: (req.body["poll-title"] || "").trim().slice(0, 300),
+ options: req.body["poll-option"],
+ dupeCheckMode: req.body["dupe-check"],
+ multiSelect: req.body["multi-select"] === "on",
+ captcha: req.body["captcha"] === "on",
+ });
+ if (typeof poll !== "string") res.redirect("/" + poll.id);
+ else res.redirect(`/?error=${
+ encodeURIComponent(poll)
+ }&title=${
+ encodeURIComponent(req.body["poll-title"])
+ }&options=${
+ encodeURIComponent((req.body["poll-option"] || []).join("\uFFFE"))
+ }&dupecheck=${
+ encodeURIComponent(req.body["dupe-check"])
+ }&multiselect=${
+ req.body["multi-select"] === "on"
+ }&captcha=${
+ req.body["captcha"] === "on"
+ }`);
} catch (error) {
console.error(error);
if (error instanceof Error) res.status(500).json({
diff --git a/src/frontend.ts b/src/frontend.ts
index 71575f7..b6865e4 100644
--- a/src/frontend.ts
+++ b/src/frontend.ts
@@ -126,5 +126,29 @@ export default function init(router: Router): void {
res.redirect(`/`);
}
});
- router.get("/", (req, res) => displayPage(req, res, "index.html"));
+
+ router.get("/", (req, res) => {
+ const options = (typeof req.query.options === "string" ? req.query.options.split("\uFFFE") : [])
+ .filter(i => i)
+ .concat(Array(3).fill(""))
+ .slice(0, 3);
+ const pollOptionDivs = options.map(option => `
+
+
+
+ `).join("");
+
+ displayPage(req, res, "index.html", {
+ "BACKEND_BASE_PATH": (program.opts().backendBaseUrl || ""),
+ "FORM_SUBMISSION_ERROR": req.query.error,
+ "FORM_SUBMISSION_ERROR_SHOWN_CLASS": req.query.error ? "error-visible" : "",
+ "FORM_TITLE": req.query.title || "",
+ "FORM_DUPECHECK_IP": req.query.dupecheck === "ip" ? "selected" : "",
+ "FORM_DUPECHECK_COOKIE": req.query.dupecheck === "cookie" ? "selected" : "",
+ "FORM_DUPECHECK_NONE": req.query.dupecheck === "none" ? "selected" : "",
+ "FORM_MULTI_SELECT": req.query.multiselect === "true" ? "checked" : "",
+ "FORM_CAPTCHA": req.query.captcha === "true" ? "checked" : "",
+ "FORM_OPTION_DIVS": pollOptionDivs
+ });
+ });
}
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 0bf0811..05ebbb3 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -19,6 +19,7 @@ async function main(): Promise {
const app = express();
app.use(express.json());
+ app.use(express.urlencoded({ extended: true }));
app.use(compression());
app.use(cookiepaser());