mirror of
https://github.com/Wolvan/poll.horse.git
synced 2024-11-22 04:58:00 +01:00
Add non-JS way of adding options
A new button has been added (which gets automatically removed by JS) that lets a user add a new option. Also, an XSS exploit has been fixed.
This commit is contained in:
parent
071a35814b
commit
cb0ec9dfa1
4 changed files with 37 additions and 14 deletions
|
@ -8,6 +8,7 @@
|
|||
<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">
|
||||
<link rel="stylesheet" href="/static/css/index.css">
|
||||
<script type="text/javascript">
|
||||
const MAX_POLL_OPTIONS = "{{ MAX_POLL_OPTIONS }}";
|
||||
const MAX_CHARACTER_LENGTH = "{{ MAX_CHARACTER_LENGTH }}";
|
||||
|
@ -33,6 +34,7 @@
|
|||
{{ FORM_OPTION_DIVS }}
|
||||
</section>
|
||||
<section class="poll-footer">
|
||||
<div id="extend-message">Used <span id="options-counter">0</span>/{{ MAX_POLL_OPTIONS }} options.<br>New boxes appear automatically.</div>
|
||||
<select id="dupe-check" name="dupe-check">
|
||||
<option value="ip" {{ FORM_DUPECHECK_IP }}>IP-based duplication checking</option>
|
||||
<option value="cookie" {{ FORM_DUPECHECK_COOKIE }}>Cookie-based duplication checking</option>
|
||||
|
@ -45,6 +47,7 @@
|
|||
</label>
|
||||
<br>
|
||||
<input type="submit" name="submit" value="Create poll" id="submit-button">
|
||||
<input type="submit" name="submit" value="Add options" formaction="#" formmethod="get" id="add-options-button">
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
|
|
4
frontend/static/css/index.css
Normal file
4
frontend/static/css/index.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
#extend-message {
|
||||
padding-bottom: 5px;
|
||||
display: none;
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
(() => {
|
||||
const inputList = [];
|
||||
const pollOptionsAnchor = document.querySelector(".poll-options");
|
||||
const extendMessageDiv = document.querySelector("#extend-message");
|
||||
const inputCounter = document.querySelector("#options-counter");
|
||||
const addOptionButton = document.querySelector("#add-options-button");
|
||||
|
||||
function createPollOptionInput() {
|
||||
if (inputList.length >= parseInt(MAX_POLL_OPTIONS)) return;
|
||||
|
@ -15,18 +18,29 @@
|
|||
optionEl.appendChild(input);
|
||||
|
||||
input.addEventListener("keyup", () => {
|
||||
if (inputList.every(el => el.value) && pollOptionsAnchor) pollOptionsAnchor.appendChild(createPollOptionInput());
|
||||
if (inputList.every(el => el.value) && pollOptionsAnchor) {
|
||||
pollOptionsAnchor.appendChild(createPollOptionInput());
|
||||
}
|
||||
});
|
||||
inputList.push(input);
|
||||
inputCounter.textContent = inputList.length;
|
||||
|
||||
return optionEl;
|
||||
}
|
||||
|
||||
pollOptionsAnchor.querySelectorAll(".poll-option input").forEach(el => {
|
||||
el.addEventListener("keyup", () => {
|
||||
if (inputList.every(el => el.value) && pollOptionsAnchor) pollOptionsAnchor.appendChild(createPollOptionInput());
|
||||
if (inputList.every(el => el.value) && pollOptionsAnchor) {
|
||||
pollOptionsAnchor.appendChild(createPollOptionInput());
|
||||
}
|
||||
});
|
||||
inputList.push(el);
|
||||
});
|
||||
|
||||
inputCounter.textContent = inputList.length;
|
||||
|
||||
if (extendMessageDiv) extendMessageDiv.style.display = "block";
|
||||
if (addOptionButton) addOptionButton.remove();
|
||||
|
||||
return createPollOptionInput;
|
||||
})();
|
|
@ -173,10 +173,12 @@ export default function init(router: Router): void {
|
|||
});
|
||||
|
||||
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 options = (typeof req.query.options === "string" ? req.query.options.split("\uFFFE") : Array.isArray(req.query["poll-option"]) ? req.query["poll-option"] as string[] : [])
|
||||
.filter(i => i);
|
||||
if (options.length < 3)
|
||||
for (let i = options.length; i < 2; ++i) options.push("");
|
||||
if (options.length < MAX_POLL_OPTIONS) options.push("");
|
||||
|
||||
const pollOptionDivs = options.map(option => `
|
||||
<div class="poll-option">
|
||||
<input type="text" name="poll-option" maxlength="${MAX_CHARACTER_LENGTH}" placeholder="Enter your option here" value="${xss(option)}">
|
||||
|
@ -185,13 +187,13 @@ export default function init(router: Router): void {
|
|||
|
||||
displayPage(req, res, "index.html", {
|
||||
"BACKEND_BASE_PATH": (program.opts().backendBaseUrl || ""),
|
||||
"FORM_SUBMISSION_ERROR": req.query.error,
|
||||
"FORM_SUBMISSION_ERROR": xss(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_TITLE": xss((req.query.title || req.query["poll-title"] || "") + ""),
|
||||
"FORM_DUPECHECK_IP": req.query.dupecheck === "ip" || req.query["dupe-check"] === "ip" ? "selected" : "",
|
||||
"FORM_DUPECHECK_COOKIE": req.query.dupecheck === "cookie" || req.query["dupe-check"] === "cookie" ? "selected" : "",
|
||||
"FORM_DUPECHECK_NONE": req.query.dupecheck === "none" || req.query["dupe-check"] === "none" ? "selected" : "",
|
||||
"FORM_MULTI_SELECT": req.query.multiselect === "true" || req.query["multi-select"] === "on" ? "checked" : "",
|
||||
"FORM_CAPTCHA": req.query.captcha === "true" ? "checked" : "",
|
||||
"FORM_OPTION_DIVS": pollOptionDivs,
|
||||
"MAX_POLL_OPTIONS": MAX_POLL_OPTIONS,
|
||||
|
|
Loading…
Reference in a new issue