diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 676509a..9bb4be6 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -4,5 +4,9 @@ "commonjs": false, "es6": true, "node": false + }, + "globals": { + "MAX_POLL_OPTIONS": "readonly", + "MAX_CHARACTER_LENGTH": "readonly" } } diff --git a/frontend/html/index.html b/frontend/html/index.html index c022e6a..48b2484 100644 --- a/frontend/html/index.html +++ b/frontend/html/index.html @@ -8,6 +8,11 @@ + +
@@ -22,7 +27,7 @@
- +
{{ FORM_OPTION_DIVS }} diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js new file mode 100644 index 0000000..5fae1fd --- /dev/null +++ b/frontend/static/js/index.js @@ -0,0 +1,32 @@ +"use strict"; +(() => { + const inputList = []; + const pollOptionsAnchor = document.querySelector(".poll-options"); + + function createPollOptionInput() { + if (inputList.length >= parseInt(MAX_POLL_OPTIONS)) return; + const optionEl = document.createElement("div"); + optionEl.classList.add("poll-option"); + const input = document.createElement("input"); + input.type = "text"; + input.maxLength = MAX_CHARACTER_LENGTH; + input.name = "poll-option"; + input.placeholder = "Enter your option here"; + optionEl.appendChild(input); + + input.addEventListener("keydown", () => { + if (inputList.every(el => el.value) && pollOptionsAnchor) pollOptionsAnchor.appendChild(createPollOptionInput()); + }); + inputList.push(input); + + return optionEl; + } + + pollOptionsAnchor.querySelectorAll(".poll-option input").forEach(el => { + el.addEventListener("keydown", () => { + if (inputList.every(el => el.value) && pollOptionsAnchor) pollOptionsAnchor.appendChild(createPollOptionInput()); + }); + inputList.push(el); + }); + return createPollOptionInput; +})(); \ No newline at end of file diff --git a/src/Config.ts b/src/Config.ts new file mode 100644 index 0000000..aceafac --- /dev/null +++ b/src/Config.ts @@ -0,0 +1,4 @@ +"use strict"; + +export const MAX_POLL_OPTIONS = 255; +export const MAX_CHARACTER_LENGTH = 300; \ No newline at end of file diff --git a/src/backend.ts b/src/backend.ts index 3d03f44..6ed54cf 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -5,6 +5,7 @@ import persist from "node-persist"; import { program } from "commander"; import { resolve } from "path"; import { BackendPoll as Poll, DupeCheckMode } from "./Poll"; +import { MAX_POLL_OPTIONS, MAX_CHARACTER_LENGTH } from "./Config"; function randomString(length = 10, charset = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789") { let result = ""; @@ -69,6 +70,8 @@ export default async function init(router: Router): Promise { }): 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"; + if (pollData.options.filter(i => i).length > MAX_POLL_OPTIONS) + return "Only " + MAX_POLL_OPTIONS + " options are allowed"; let id = randomString(8); while (await polls.getItem(id)) id = randomString(6); @@ -84,10 +87,10 @@ export default async function init(router: Router): Promise { dupeCheckMode === "cookie" ? randomString(16) : null; const poll: Poll = { id, - title: (pollData.title || "").trim().slice(0, 300), + title: (pollData.title || "").trim().slice(0, MAX_CHARACTER_LENGTH), options: (() => { const result: { [option: string]: number } = {}; - for (const option of pollData.options.map(i => i.trim().slice(0, 300))) { + for (const option of pollData.options.map(i => i.trim().slice(0, MAX_CHARACTER_LENGTH))) { if (option) result[option] = 0; } return result; @@ -105,7 +108,7 @@ export default async function init(router: Router): Promise { router.post("/poll", async (req, res) => { try { const poll = await createPoll({ - title: (req.body.title || "").trim().slice(0, 300), + title: (req.body.title || "").trim().slice(0, MAX_CHARACTER_LENGTH), options: req.body.options, dupeCheckMode: req.body.dupeCheckMode, multiSelect: req.body.multiSelect || false, @@ -130,7 +133,7 @@ export default async function init(router: Router): Promise { router.post("/poll-form", async (req, res) => { try { const poll = await createPoll({ - title: (req.body["poll-title"] || "").trim().slice(0, 300), + title: (req.body["poll-title"] || "").trim().slice(0, MAX_CHARACTER_LENGTH), options: req.body["poll-option"], dupeCheckMode: req.body["dupe-check"], multiSelect: req.body["multi-select"] === "on", @@ -142,7 +145,7 @@ export default async function init(router: Router): Promise { }&title=${ encodeURIComponent(req.body["poll-title"]) }&options=${ - encodeURIComponent((req.body["poll-option"] || []).join("\uFFFE")) + encodeURIComponent((req.body["poll-option"] || []).slice(0, MAX_POLL_OPTIONS).join("\uFFFE")) }&dupecheck=${ encodeURIComponent(req.body["dupe-check"]) }&multiselect=${ diff --git a/src/frontend.ts b/src/frontend.ts index b6865e4..07bfb7e 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -6,6 +6,7 @@ import { Router, Request, Response } from "express"; import fetch from 'node-fetch'; import { program } from "commander"; import { FrontendPoll as Poll, PollResult } from "./Poll"; +import { MAX_CHARACTER_LENGTH, MAX_POLL_OPTIONS } from "./Config"; const RenderBuffer = new WeakMap(); const RenderReplacements = new WeakMap(); @@ -134,7 +135,7 @@ export default function init(router: Router): void { .slice(0, 3); const pollOptionDivs = options.map(option => `
- +
`).join(""); @@ -148,7 +149,9 @@ export default function init(router: Router): void { "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 + "FORM_OPTION_DIVS": pollOptionDivs, + "MAX_POLL_OPTIONS": MAX_POLL_OPTIONS, + "MAX_CHARACTER_LENGTH": MAX_CHARACTER_LENGTH }); }); } \ No newline at end of file