From b42091406ba4f4abe8591a793e2334d38613aaf5 Mon Sep 17 00:00:00 2001 From: Arjun Satarkar Date: Sat, 5 Aug 2023 01:32:00 +0530 Subject: Make tag input nicer with JS (if enabled) --- static/scripts/auto_refresh.js | 1 + static/scripts/dynamic_input.js | 84 +++++++++++++++++++++++++++++++++++++++++ static/styles/main.css | 5 ++- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 static/scripts/dynamic_input.js (limited to 'static') diff --git a/static/scripts/auto_refresh.js b/static/scripts/auto_refresh.js index 5e7223e..fd94497 100644 --- a/static/scripts/auto_refresh.js +++ b/static/scripts/auto_refresh.js @@ -1,3 +1,4 @@ +"use strict"; (() => { const onFrontPage = () => { const searchParams = new URLSearchParams(window.location.search); diff --git a/static/scripts/dynamic_input.js b/static/scripts/dynamic_input.js new file mode 100644 index 0000000..df1c8fe --- /dev/null +++ b/static/scripts/dynamic_input.js @@ -0,0 +1,84 @@ +"use strict"; +(() => { + const outerContainer = document.querySelector("div.tag-input-container"); + const rawInput = document.createElement("input"); + const name = outerContainer.querySelector("span#tags-input-name-span").innerText; + rawInput.setAttribute("type", "hidden"); + rawInput.setAttribute("name", name); + outerContainer.appendChild(rawInput); + + function parseSpaceSeparatedTags(inp) { + const tags = new Set(); + let tag = "" + let escaped = false; + for (const c of inp) { + switch (c) { + case "\\": + if (!escaped) { + escaped = true; + continue; + } + case " ": + if (!escaped) { + tags.add(tag); + tag = ""; + continue; + } + } + escaped = false; + tag += c; + } + if (tag) { + tags.add(tag); + } + return Array.from(tags).sort((a, b) => a.localeCompare(b)); + } + + const dynamicInputContainer = document.createElement("span"); + + function createDynamicInput() { + const dynamicInput = document.createElement("input"); + dynamicInput.setAttribute("class", "dynamic-tag-input"); + // So autocomplete will work + dynamicInput.setAttribute("name", "dynamic_tag_input"); + dynamicInput.addEventListener("input", handleInput); + return dynamicInput; + }; + + function handleInput(e) { + const sources = document.querySelectorAll("input.dynamic-tag-input"); + const lastDynamicInput = sources[sources.length - 1]; + if (e.currentTarget === lastDynamicInput && e.currentTarget.value) { + dynamicInputContainer.appendChild(createDynamicInput()); + } else if (e.currentTarget === sources[sources.length - 2] && !e.currentTarget.value) { + dynamicInputContainer.removeChild(lastDynamicInput); + } + let serialised = ""; + for (const [i, source] of sources.entries()) { + const tag = source.value; + if (!tag) { continue; } + if (i > 0) { + serialised += " "; + } + serialised += tag.replaceAll("\\", "\\\\").replaceAll(" ", "\\ "); + } + rawInput.value = serialised; + }; + + const firstDynamicInput = createDynamicInput(); + firstDynamicInput.setAttribute("id", "tags-input"); + dynamicInputContainer.appendChild(firstDynamicInput); + + const initialValue = outerContainer.querySelector("span#tags-input-initial-value-span").innerText; + if (initialValue) { + const tags = parseSpaceSeparatedTags(initialValue); + let input = dynamicInputContainer.querySelector("input.dynamic-tag-input:last-of-type"); + for (const tag of tags) { + input.value = tag; + input = createDynamicInput(); + dynamicInputContainer.appendChild(input); + } + } + + outerContainer.appendChild(dynamicInputContainer); +})(); diff --git a/static/styles/main.css b/static/styles/main.css index f6c6a27..1bc1709 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -57,11 +57,12 @@ span.tag { display: table-row; } - div.side-by-side-help-container>* { + div.side-by-side-help-container>label, + div.side-by-side-help-container input { display: table-cell; } - div.side-by-side-help-container>.hover-help:focus::after { + div.side-by-side-help-container .hover-help:focus::after { content: attr(title); font-size: small; padding-left: 0.2em; -- cgit v1.2.3-57-g22cb