diff options
-rw-r--r-- | README.md | 2 | ||||
-rwxr-xr-x | serve.py | 2 | ||||
-rw-r--r-- | static/scripts/auto_refresh.js | 1 | ||||
-rw-r--r-- | static/scripts/dynamic_input.js | 84 | ||||
-rw-r--r-- | static/styles/main.css | 5 | ||||
-rw-r--r-- | views/add_feed.tpl | 1 | ||||
-rw-r--r-- | views/index.tpl | 7 | ||||
-rw-r--r-- | views/manage_feed.tpl | 1 | ||||
-rw-r--r-- | views/tag_input.tpl | 12 |
9 files changed, 105 insertions, 10 deletions
@@ -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 @@ -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 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Add Feed | TagRSS</title> <link href="/static/styles/main.css" rel="stylesheet"> + <script src="/static/scripts/dynamic_input.js" defer></script> </head> <body> <a href="/" class="no-visited-indication">< home</a> 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 @@ } </style> <script src="/static/scripts/auto_refresh.js" defer></script> + <script src="/static/scripts/dynamic_input.js" defer></script> </head> <body> <h1>TagRSS</h1> @@ -64,8 +65,10 @@ <form> <div class="side-by-side-help-container"> <label for="included-feeds-input">Included feeds:</label> - <input type="text" name="included_feeds" value="{{' '.join([str(feed_id) for feed_id in included_feeds]) if included_feeds else ''}}" id="included-feeds-input"> - % include("hover_help.tpl", text="Space-separated feed IDs.") + <span> + <input type="text" name="included_feeds" value="{{' '.join([str(feed_id) for feed_id in included_feeds]) if included_feeds else ''}}" id="included-feeds-input"> + % include("hover_help.tpl", text="Space-separated feed IDs.") + </span> </div> % 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 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Manage Feed | TagRSS</title> <link href="/static/styles/main.css" rel="stylesheet"> + <script src="/static/scripts/dynamic_input.js" defer></script> </head> <body> <a href="/" class="no-visited-indication">< home</a> 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 @@ -<div class="side-by-side-help-container"> - <label for="tag-input">{{get("label", "Tags:")}}</label> - <input type="text" name="{{input_name}}" value="{{get('input_value', '')}}" id="tag-input"> - % include("hover_help.tpl", text="Space-separated. Backslashes escape spaces.") +<div class="side-by-side-help-container tag-input-container"> + <label for="tags-input">{{get("label", "Tags:")}}</label> + <noscript> + <input type="text" name="{{input_name}}" value="{{get('input_value', '')}}" id="tags-input"> + % include("hover_help.tpl", text="Space-separated. Backslashes escape spaces.") + </noscript> + <span style="display: none;" id="tags-input-name-span">{{input_name}}</span> + <span style="display: none;" id="tags-input-initial-value-span">{{get("input_value", "")}}</span> </div> |