aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjun Satarkar <me@arjunsatarkar.net>2023-07-28 22:26:31 +0000
committerArjun Satarkar <me@arjunsatarkar.net>2023-07-28 22:26:31 +0000
commit6e0ea8d6c871bd79e80b87de6cc88ea1cc5e6118 (patch)
treef12756431fcab9ffe4d976f17ce3e98088dc4f98
parent5a546a35351362a8da73fbbd1380de6a094069d9 (diff)
downloadtagrss-6e0ea8d6c871bd79e80b87de6cc88ea1cc5e6118.tar
tagrss-6e0ea8d6c871bd79e80b87de6cc88ea1cc5e6118.tar.gz
tagrss-6e0ea8d6c871bd79e80b87de6cc88ea1cc5e6118.zip
Add filtering by tag and feed, improve modularity of some HTML
-rw-r--r--README.md3
-rwxr-xr-xserve.py19
-rw-r--r--tagrss.py62
-rw-r--r--views/add_feed.tpl2
-rw-r--r--views/hover_help.tpl1
-rw-r--r--views/index.tpl35
-rw-r--r--views/list_feeds.tpl4
-rw-r--r--views/manage_feed.tpl2
-rw-r--r--views/tag_hover_help.tpl1
9 files changed, 113 insertions, 16 deletions
diff --git a/README.md b/README.md
index 58300e2..2fe658a 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,9 @@ See `LICENSE.txt` in the root of this repository for the text of the license.
## To do
-* Add filtering by tag/feed
+* Add JS to make the feed/tag input situation work like one would normally expect rather than like it's 1985. (Progressive enhancement, though.)
* Do more user input validation
* Handle more `requests` and `feedparser` error conditions
-* Add logging
* Add some reasonably high internal limit on tag count
* Add support for authentication
* Allow specifying update interval on a per-feed basis
diff --git a/serve.py b/serve.py
index f1061b1..be650a7 100755
--- a/serve.py
+++ b/serve.py
@@ -71,9 +71,20 @@ def index():
per_page: int = min(MAX_PER_PAGE_ENTRIES, int(bottle.request.query.get("per_page", DEFAULT_PER_PAGE_ENTRIES))) # type: ignore
page_num = int(bottle.request.query.get("page_num", 1)) # type: ignore
offset = (page_num - 1) * per_page
+ included_feeds_str: typing.Optional[str] = bottle.request.query.get("included_feeds", None) # type: ignore
+ included_feeds: typing.Optional[list[int]] = None
+ if included_feeds_str:
+ try:
+ included_feeds = [int(feed_id) for feed_id in included_feeds_str.split(" ")]
+ except ValueError:
+ pass
+ included_tags_str: typing.Optional[str] = bottle.request.query.get("included_tags", None) # type: ignore
+ included_tags: typing.Optional[list[str]] = None
+ if included_tags_str:
+ included_tags = parse_space_separated_tags(included_tags_str)
with core_lock:
- total_pages: int = max(1, math.ceil(core.get_entry_count() / per_page))
- entries = core.get_entries(limit=per_page, offset=offset)
+ total_pages: int = max(1, math.ceil(core.get_entry_count(included_feeds=included_feeds) / per_page))
+ entries = core.get_entries(limit=per_page, offset=offset, included_feeds=included_feeds, included_tags=included_tags)
return bottle.template(
"index",
entries=entries,
@@ -82,6 +93,10 @@ def index():
total_pages=total_pages,
per_page=per_page,
max_per_page=MAX_PER_PAGE_ENTRIES,
+ included_feeds=included_feeds,
+ included_tags=included_tags,
+ included_feeds_str=included_feeds_str,
+ included_tags_str=included_tags_str,
core=core,
)
diff --git a/tagrss.py b/tagrss.py
index 7c37bec..6ac5270 100644
--- a/tagrss.py
+++ b/tagrss.py
@@ -89,13 +89,34 @@ class TagRss:
self.store_feed_entries(feed_id, parsed_feed, epoch_downloaded)
def get_entries(
- self, *, limit: int, offset: int = 0
+ self,
+ *,
+ limit: int,
+ offset: int = 0,
+ included_feeds: typing.Optional[typing.Collection[int]] = None,
+ included_tags: typing.Optional[typing.Collection[str]] = None,
) -> list[dict[str, typing.Any]]:
+ where_clause: str = ""
+ if included_feeds or included_tags:
+ where_clause = "WHERE 1"
+ if included_feeds:
+ where_clause += f" AND feed_id IN ({','.join('?' * len(included_feeds))})"
+ if included_tags:
+ where_clause += (
+ " AND feed_id IN (SELECT feed_id FROM feed_tags WHERE tag = ?)"
+ * len(included_tags)
+ )
with self.connection:
resp = self.connection.execute(
- "SELECT id, feed_id, title, link, epoch_published, epoch_updated FROM entries \
+ f"SELECT id, feed_id, title, link, epoch_published, epoch_updated FROM entries \
+ {where_clause} \
ORDER BY id DESC LIMIT ? OFFSET ?;",
- (limit, offset),
+ (
+ *(included_feeds if included_feeds else ()),
+ *(included_tags if included_tags else ()),
+ limit,
+ offset,
+ ),
).fetchall()
entries = []
@@ -177,11 +198,36 @@ class TagRss:
)
return feeds
- def get_entry_count(self) -> int:
- with self.connection:
- return self.connection.execute("SELECT count from entry_count;").fetchone()[
- 0
- ]
+ def get_entry_count(
+ self,
+ *,
+ included_feeds: typing.Optional[typing.Collection[int]] = None,
+ included_tags: typing.Optional[typing.Collection[str]] = None,
+ ) -> int:
+ if not (included_feeds or included_tags):
+ with self.connection:
+ return self.connection.execute(
+ "SELECT count from entry_count;"
+ ).fetchone()[0]
+ else:
+ where_clause: str = "WHERE 1"
+ if included_feeds:
+ where_clause += (
+ f" AND feed_id IN ({','.join('?' * len(included_feeds))})"
+ )
+ if included_tags:
+ where_clause += (
+ " AND feed_id IN (SELECT feed_id FROM feed_tags WHERE tag = ?)"
+ * len(included_tags)
+ )
+ with self.connection:
+ return self.connection.execute(
+ f"SELECT COUNT(*) FROM entries {where_clause};",
+ (
+ *(included_feeds if included_feeds else ()),
+ *(included_tags if included_tags else ()),
+ ),
+ ).fetchone()[0]
def get_feed_count(self) -> int:
with self.connection:
diff --git a/views/add_feed.tpl b/views/add_feed.tpl
index b08ea0e..0110bac 100644
--- a/views/add_feed.tpl
+++ b/views/add_feed.tpl
@@ -21,7 +21,7 @@
<br>
<div class="side-by-side-help-container">
<input type="text" placeholder="Tags" name="tags">
- <span class="hover-help" tabindex="0" title="Space separated. Backslashes escape spaces.">🛈</span>
+ % include("tag_hover_help.tpl")
</div>
<input type="submit" value="Add">
</form>
diff --git a/views/hover_help.tpl b/views/hover_help.tpl
new file mode 100644
index 0000000..5d5eb97
--- /dev/null
+++ b/views/hover_help.tpl
@@ -0,0 +1 @@
+<span class="hover-help" tabindex="0" title="{{text}}">🛈</span>
diff --git a/views/index.tpl b/views/index.tpl
index acb6fd1..52e997f 100644
--- a/views/index.tpl
+++ b/views/index.tpl
@@ -51,6 +51,33 @@
<a href="/list_feeds" class="no-visited-indication">List feeds</a>
</p>
</nav>
+ <details {{"open" if (included_feeds or included_tags) else ""}}>
+ <summary>Filter</summary>
+ <form>
+ <div class="side-by-side-help-container">
+ <label>Included feeds:
+ <input type="text" name="included_feeds" value="{{' '.join([str(feed_id) for feed_id in included_feeds]) if included_feeds else ''}}">
+ </label>
+ % include("hover_help.tpl", text="Space-separated feed IDs.")
+ </div>
+ <div class="side-by-side-help-container">
+ <label>Included tags:
+ <input type="text" name="included_tags" value="{{included_tags_str}}">
+ </label>
+ % include("tag_hover_help.tpl")
+ </div>
+ <input type="submit" value="Filter">
+ <input type="number" value="{{page_num}}" min="1" max="{{total_pages}}" name="page_num" style="display: none;">
+ <input type="number" value="{{per_page}}" min="1" max="{{max_per_page}}" name="per_page" style="display: none;">
+ </form>
+ <form>
+ <input type="text" name="included_feeds" value="" style="display: none;">
+ <input type="text" name="included_tags" value="" style="display: none;">
+ <input type="submit" value="Clear filters">
+ <input type="number" value="{{page_num}}" min="1" max="{{total_pages}}" name="page_num" style="display: none;">
+ <input type="number" value="{{per_page}}" min="1" max="{{max_per_page}}" name="per_page" style="display: none;">
+ </form>
+ </details>
<table>
<thead>
<tr>
@@ -92,7 +119,7 @@
<td class="td-feed">
<div>
<a href="/manage_feed?feed={{entry['feed_id']}}" class="no-visited-indication">âš™</a>
- {{core.get_feed_title(entry["feed_id"])}}
+ {{core.get_feed_title(entry["feed_id"])}} <small>(</small>{{entry["feed_id"]}}<small>)</small>
</div>
</td>
</tr>
@@ -107,6 +134,12 @@
<input type="number" value="{{per_page}}" min="1" max="{{max_per_page}}" name="per_page">
</label>
<input type="submit" value="Go">
+ % if included_feeds:
+ <input type="text" name="included_feeds" value="{{included_feeds_str}}" style="display: none;">
+ % end
+ % if included_tags:
+ <input type="text" name="included_feeds" value="{{included_tags_str}}" style="display: none;">
+ % end
</form>
% include("footer.tpl")
</body>
diff --git a/views/list_feeds.tpl b/views/list_feeds.tpl
index 6377289..3c60f06 100644
--- a/views/list_feeds.tpl
+++ b/views/list_feeds.tpl
@@ -13,6 +13,7 @@
<thead>
<tr>
<th>#</th>
+ <th>ID</th>
<th>Feed</th>
<th>Source</th>
<th>Manage</th>
@@ -22,7 +23,8 @@
% for i, feed in enumerate(feeds):
<tr>
<td>{{i + 1 + offset}}</td>
- <td>{{feed["title"]}}</td>
+ <td>{{feed["id"]}}</td>
+ <td>{{feed["title"]}} (<a href="/?included_feeds={{feed['id']}}" class="no-visited-indication">filter</a>)</td>
<td><a href="{{feed['source']}}" class="no-visited-indication">🔗</a></td>
<td><a href="/manage_feed?feed={{feed['id']}}" class="no-visited-indication">âš™</a></td>
</tr>
diff --git a/views/manage_feed.tpl b/views/manage_feed.tpl
index c110ad2..0ff127e 100644
--- a/views/manage_feed.tpl
+++ b/views/manage_feed.tpl
@@ -46,7 +46,7 @@
<label>Tags:
<input type="text" name="tags" value="{{feed['serialised_tags']}}">
</label>
- <span class="hover-help" tabindex="0" title="Space separated. Backslashes escape spaces.">🛈</span>
+ % include("tag_hover_help.tpl")
</div>
<input type="submit" value="Update" name="update_feed">
</form>
diff --git a/views/tag_hover_help.tpl b/views/tag_hover_help.tpl
new file mode 100644
index 0000000..9007e55
--- /dev/null
+++ b/views/tag_hover_help.tpl
@@ -0,0 +1 @@
+% include("hover_help.tpl", text="Space-separated. Backslashes escape spaces.")