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) --- README.md | 2 +- serve.py | 2 +- static/scripts/auto_refresh.js | 1 + static/scripts/dynamic_input.js | 84 +++++++++++++++++++++++++++++++++++++++++ static/styles/main.css | 5 ++- views/add_feed.tpl | 1 + views/index.tpl | 7 +++- views/manage_feed.tpl | 1 + views/tag_input.tpl | 12 ++++-- 9 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 static/scripts/dynamic_input.js diff --git a/README.md b/README.md index 39b7e8b..6e24e3b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ See `LICENSE.txt` in the root of this repository for the text of the license. ## To do -* Add JS to make the feed/tag input situation work like one would normally expect rather than like it's 1985. (Progressive enhancement, though.) +* Add JS to make the feed filter input situation work like one would normally expect rather than like it's 1985. (Progressive enhancement, though.) * Add support for authentication * Allow specifying update interval on a per-feed basis diff --git a/serve.py b/serve.py index ee51c99..080168d 100755 --- a/serve.py +++ b/serve.py @@ -43,7 +43,7 @@ core = tagrss.TagRss(storage_path=storage_path) def forgiving_parse_int(inp, default: int) -> int: try: return int(inp) - except (ValueError, TypeError): + except (TypeError, ValueError): return default 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; diff --git a/views/add_feed.tpl b/views/add_feed.tpl index 04a1008..fc40403 100644 --- a/views/add_feed.tpl +++ b/views/add_feed.tpl @@ -5,6 +5,7 @@ Add Feed | TagRSS + < home diff --git a/views/index.tpl b/views/index.tpl index 046d317..5009da2 100644 --- a/views/index.tpl +++ b/views/index.tpl @@ -47,6 +47,7 @@ } +

TagRSS

@@ -64,8 +65,10 @@
- - % include("hover_help.tpl", text="Space-separated feed IDs.") + + + % include("hover_help.tpl", text="Space-separated feed IDs.") +
% include( % "tag_input.tpl", diff --git a/views/manage_feed.tpl b/views/manage_feed.tpl index cdeaf98..4a07083 100644 --- a/views/manage_feed.tpl +++ b/views/manage_feed.tpl @@ -5,6 +5,7 @@ Manage Feed | TagRSS + < home diff --git a/views/tag_input.tpl b/views/tag_input.tpl index 409360f..7005eeb 100644 --- a/views/tag_input.tpl +++ b/views/tag_input.tpl @@ -1,5 +1,9 @@ -
- - - % include("hover_help.tpl", text="Space-separated. Backslashes escape spaces.") +
+ + + +
-- cgit v1.2.3-57-g22cb