Implement Form Expansion

If more than 3 options are written down, additional inputs will load in
to allow more options. The maximum cap of options currently is set to
255 but can be configured in Config.ts.
Likewise, the input length can also be controlled from there.
This commit is contained in:
Wolvan 2022-01-01 14:36:25 +01:00
parent 678342a9c6
commit c00ea29b4f
6 changed files with 59 additions and 8 deletions

View file

@ -4,5 +4,9 @@
"commonjs": false,
"es6": true,
"node": false
},
"globals": {
"MAX_POLL_OPTIONS": "readonly",
"MAX_CHARACTER_LENGTH": "readonly"
}
}

View file

@ -8,6 +8,11 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/main.css">
<script type="text/javascript">
const MAX_POLL_OPTIONS = "{{ MAX_POLL_OPTIONS }}";
const MAX_CHARACTER_LENGTH = "{{ MAX_CHARACTER_LENGTH }}";
</script>
<script type="text/javascript" src="/static/js/index.js" defer="true" async="true"></script>
</head>
<body>
<header>
@ -22,7 +27,7 @@
<div class="notepad-border"></div>
<form action="{{ BACKEND_BASE_PATH }}/_backend/poll-form" method="POST">
<section class="poll-title">
<input id="poll-title" name="poll-title" type="text" maxlength="300" placeholder="Enter your question here" value="{{ FORM_TITLE }}">
<input id="poll-title" name="poll-title" type="text" maxlength="{{ MAX_CHARACTER_LENGTH }}" placeholder="Enter your question here" value="{{ FORM_TITLE }}">
</section>
<section id="poll-options" class="poll-options">
{{ FORM_OPTION_DIVS }}

View file

@ -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;
})();

4
src/Config.ts Normal file
View file

@ -0,0 +1,4 @@
"use strict";
export const MAX_POLL_OPTIONS = 255;
export const MAX_CHARACTER_LENGTH = 300;

View file

@ -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<void> {
}): Promise<Poll | string> {
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<void> {
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<void> {
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<void> {
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<void> {
}&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=${

View file

@ -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 => `
<div class="poll-option">
<input type="text" name="poll-option" maxlength="300" placeholder="Enter your option here" value="${option}">
<input type="text" name="poll-option" maxlength="${MAX_CHARACTER_LENGTH}" placeholder="Enter your option here" value="${option}">
</div>
`).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
});
});
}