mirror of
https://github.com/Wolvan/poll.horse.git
synced 2024-11-21 20:47:59 +01:00
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:
parent
678342a9c6
commit
c00ea29b4f
6 changed files with 59 additions and 8 deletions
|
@ -4,5 +4,9 @@
|
|||
"commonjs": false,
|
||||
"es6": true,
|
||||
"node": false
|
||||
},
|
||||
"globals": {
|
||||
"MAX_POLL_OPTIONS": "readonly",
|
||||
"MAX_CHARACTER_LENGTH": "readonly"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }}
|
||||
|
|
32
frontend/static/js/index.js
Normal file
32
frontend/static/js/index.js
Normal 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
4
src/Config.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
|
||||
export const MAX_POLL_OPTIONS = 255;
|
||||
export const MAX_CHARACTER_LENGTH = 300;
|
|
@ -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=${
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue