aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjun Satarkar <me@arjunsatarkar.net>2023-08-04 20:02:00 +0000
committerArjun Satarkar <me@arjunsatarkar.net>2023-08-04 20:03:29 +0000
commitb42091406ba4f4abe8591a793e2334d38613aaf5 (patch)
tree6b1e30d7a42cb41cff2ef50887b6010516152329
parent8f5488436b90d85969abb8bdcbb0ecf14d8936bd (diff)
downloadtagrss-main.tar
tagrss-main.tar.gz
tagrss-main.zip
Make tag input nicer with JS (if enabled)HEADmain
-rw-r--r--README.md2
-rwxr-xr-xserve.py2
-rw-r--r--static/scripts/auto_refresh.js1
-rw-r--r--static/scripts/dynamic_input.js84
-rw-r--r--static/styles/main.css5
-rw-r--r--views/add_feed.tpl1
-rw-r--r--views/index.tpl7
-rw-r--r--views/manage_feed.tpl1
-rw-r--r--views/tag_input.tpl12
9 files changed, 105 insertions, 10 deletions
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 @@
<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">&lt; 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">&lt; 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>