simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 16f9dc02fc452395bc283b5dbbc68c237a583848
parent efeab221d48349d582a360a76abd8e7c5148c607
Author: Silas Brack <silasbrack@gmail.com>
Date:   Thu,  2 Oct 2025 17:01:24 +0200

feat: enable filtering for category

Diffstat:
Msrc/simple_web_app/app.py | 14++++++++------
Msrc/simple_web_app/queries/basic.sql | 16++++++++++++++--
Msrc/simple_web_app/templates/index.html | 2+-
Msrc/simple_web_app/templates/news_card.html | 2+-
Msrc/simple_web_app/templates/settings_tab.html | 139++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
5 files changed, 158 insertions(+), 15 deletions(-)

diff --git a/src/simple_web_app/app.py b/src/simple_web_app/app.py @@ -89,22 +89,24 @@ def format_timedelta(tdelta, fmt): async def show_home_page(request: Request): + t0 = time.time() page = request.query_params.get("page", default=0) page = int(page) if page is not None else page current_time = request.query_params.get("current_time", default=datetime.datetime.now(tz=datetime.UTC)) + category_id = request.query_params.get("category-id") limit = 5 offset = page * limit - t0 = time.time() - rows = await queries_basic.get_categories(request.state.conn, limit=20) - all_categories = [row["category"] for row in rows] + all_categories = await queries_basic.get_categories(request.state.conn, limit=20) - rows = await queries_basic.get_news(request.state.conn, limit=limit, offset=offset, max_published_time=current_time) + if category_id: + rows = await queries_basic.get_news_by_category(request.state.conn, category_id=category_id, limit=limit, offset=offset, max_published_time=current_time) + else: + rows = await queries_basic.get_news(request.state.conn, limit=limit, offset=offset, max_published_time=current_time) news = [dict(row) for row in rows] for new in news: - rows = await queries_basic.get_categories_for_news(request.state.conn, news_item_id=new["id"]) - categories = [row["category"] for row in rows] + categories = await queries_basic.get_categories_for_news(request.state.conn, news_item_id=new["id"]) new["categories"] = categories published = datetime.datetime.strptime(new["published"], "%Y-%m-%dT%H:%M:%S.%f%z") new["time_since_published"] = format_timedelta(datetime.datetime.now(tz=datetime.UTC) - published, "{days} days, {hours} hours ago") diff --git a/src/simple_web_app/queries/basic.sql b/src/simple_web_app/queries/basic.sql @@ -12,15 +12,27 @@ SELECT title, text FROM news_item_fts(:query) LIMIT :limit; +-- name: get_news_by_category(category_id, limit, offset, max_published_time) +SELECT ni.id, ni.title, ni.text, ni.published +FROM news_item AS ni +INNER JOIN news_item_category AS nic + ON nic.news_item_id = ni.id +WHERE ni.language = 'english' + AND (ni.published < :max_published_time) + AND nic.category_id = :category_id +ORDER BY ni.published DESC, ni.id ASC +LIMIT :limit +OFFSET :offset; + -- name: get_categories_for_news(news_item_id) -SELECT nic.category_id, c.name AS category +SELECT c.id, c.name FROM news_item_category AS nic INNER JOIN category AS c ON c.id = nic.category_id WHERE nic.news_item_id = :news_item_id; -- name: get_categories(limit) -SELECT id, name AS category +SELECT id, name FROM category ORDER BY id LIMIT :limit; diff --git a/src/simple_web_app/templates/index.html b/src/simple_web_app/templates/index.html @@ -18,7 +18,7 @@ <table> <tr> {% for category in categories %} - <th><div hx-get="/" hx-target="#news-cards" style="cursor: pointer;">{{ category }}</div></th> + <th><div hx-get="/?category-id={{ category.id }}" hx-target="#news-cards" style="cursor: pointer;">{{ category.name }}</div></th> {% endfor %} </tr> </table> diff --git a/src/simple_web_app/templates/news_card.html b/src/simple_web_app/templates/news_card.html @@ -1,7 +1,7 @@ <article> <header> {% for category in new.categories %} - <small><a href="">{{ category }}</a></small> + <small><a style="cursor: pointer;" hx-get="/?category-id={{ category.id }}" hx-target="#news-cards">{{ category.name }}</a></small> {% endfor %} </header> <details> diff --git a/src/simple_web_app/templates/settings_tab.html b/src/simple_web_app/templates/settings_tab.html @@ -113,15 +113,10 @@ Story Expand Mode </details> </label> {% elif tab == "sections" %} -<nav> -<ul><li> <hgroup> <h4>Article Sections</h4> <h6>Drag to reorder sections. Use the switch to enable/disable.</small> </hgroup> -</li></ul> -<ul><li><button>Toggle All</button></li></ul> -</nav> <article><nav><ul><li>Summary</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> <article><nav><ul><li>Primary Image</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> <article><nav><ul><li>Sources</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> @@ -135,5 +130,139 @@ Story Expand Mode <article><nav><ul><li>Business Angle</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> <article><nav><ul><li>Scientific Significance</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> <article><nav><ul><li>Travel Advisory</li></ul><ul><li><label><input name="summary" type="checkbox" role="switch" /></label></li></ul></nav></article> +<nav><ul></ul><ul><li> +<button class="secondary">Reset Order</button> +<button>Toggle All</button> +</li></ul><ul></ul></nav> +{% elif tab == "content-filter" %} +<hgroup> +<h4>Filter Presets</h4> +<p>Select one or more preset filters to quickly hide common topics</p> +</hgroup> +<select aria-label="Topics" multiple size="6"> + <option>Cheese</option> + <option selected>Fruits</option> + <option selected>Nuts</option> + <option>Chocolate</option> + <option>Crackers</option> +</select> +<hgroup> +<h4>Custom Keywords</h4> +<p>Add your own keywords to filter, separated by commas</p> +</hgroup> +<input type="text" name="custom-keywords" placeholder="e.g., celebrity name, topic" aria-label="Custom Keywords"> +<hgroup> +<h4>Filter Mode</h4> +<p>Choose how filtered content is handled</p> +</hgroup> +<fieldset> + <label> + <input type="radio" name="language" checked /> + Hide completely + </label> + <label> + <input type="radio" name="language" /> + Blur with warning + </label> +</fieldset> +<hgroup> +<h4>Filter Scope</h4> +<p>Choose which parts of stories to check for keywords</p> +</hgroup> +<select name="filter-scope" required> + <option>Title only</option> + <option>Title and summary</option> + <option selected>All content</option> +</select> +<nav> +<ul><li> +<hgroup> +<h4>Show Filtered Count</h4> +<p>Display number of filtered stories in each category</p> +</hgroup> +</li></ul> +<ul><li> +<input name="terms" type="checkbox" role="switch" /> +</li></ul> +</nav> +<hr> +<hgroup> +<h4>Backup & Restore</h4> +<p>Export your filter settings to a file or import from a previous backup</p> +</hgroup> +<button class="secondary">Export Settings</button> +<button>Import Settings</button> +<hr> +<button class="outline">Reset Content Filter Settings to Defaults</button> +{% elif tab == "syncing" %} +<p> +Kagi News can sync your settings and read history across all your devices. This data is stored securely on Kagi servers and associated with your Kagi Search account. Your synced data is not used for any other purpose, not shared with anyone, and is solely stored to provide the sync service to you. You have full control over what gets synced and can delete your data at any time. +</p> +<h4>Sync Preferences</h4> +<label> + <input name="sync-settings" type="checkbox" role="switch" aria-describedby="sync-settings-description"/> + Sync Settings +</label> +<label> + <input name="sync-read-history" type="checkbox" role="switch" aria-describedby="email-helper"/> + Sync Read History +</label> +<br> +<h4>Data Management</h4> +<p> +You are in control of your data. Export or delete your cloud data at any time. Deletion does not affect local data. +</p> +<button class="secondary">Export Data</button> +<button>Clear All Synced Data</button> +{% elif tab == "experimental" %} +<p>⚠️ These are experimental features that may be changed or removed at any time.</p> +<article> +<nav> +<ul><li> +<hgroup> +<h4>Show icons next to articles</h4> +<p>When enabled, small icons will be displayed next to articles to provide visual context.</p> +</hgroup> +</li></ul> +<ul><li> + <input name="show-icons-next-to-articles" type="checkbox" role="switch"/> +</li></ul> +</article> +<article> +<nav> +<ul><li> +<hgroup> +<h4>Show icons in category labels</h4> +<p>When enabled, small icons will be displayed next to category labels to provide visual context.</p> +</hgroup> +</li></ul> +<ul><li> + <input name="show-icons-next-to-category-labels" type="checkbox" role="switch"/> +</li></ul> +</article> +<article> +<nav> +<ul><li> +<hgroup> +<h4>Disable horizontal category swiping</h4> +<p>When enabled, horizontal swiping to change categories on mobile devices will be disabled.</p> +</hgroup> +</li></ul> +<ul><li> + <input name="disable-horizontal-category-swiping" type="checkbox" role="switch"/> +</li></ul> +</article> +<article> +<nav> +<ul><li> +<hgroup> +<h4>Show World Tension Index</h4> +<p>Display a global temperature reading of world stability based on current events.</p> +</hgroup> +</li></ul> +<ul><li> + <input name="show-icons-next-to-articles" type="checkbox" role="switch"/> +</li></ul> +</article> {% endif %} </section>