From a113bc13e569049c59baa2165d28a992d7bdde7b Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sun, 15 Feb 2026 18:05:38 -0800 Subject: Vanilla JS (v3): Final parity with React (Search, Settings, Shortcuts) --- .thicket/tickets.jsonl | 3 +- frontend-vanilla/src/main.ts | 280 ++++++++++++++++++++++++++-------- frontend-vanilla/src/store.test.ts | 29 +++- frontend-vanilla/src/store.ts | 24 ++- frontend-vanilla/src/style.css | 114 ++++++++++++-- web/dist/v3/assets/index-A9upXj8Y.css | 1 - web/dist/v3/assets/index-BoWfbp6N.js | 72 --------- web/dist/v3/assets/index-CPnxXrEk.css | 1 + web/dist/v3/assets/index-FNdWoCuA.js | 102 +++++++++++++ web/dist/v3/index.html | 4 +- 10 files changed, 474 insertions(+), 156 deletions(-) delete mode 100644 web/dist/v3/assets/index-A9upXj8Y.css delete mode 100644 web/dist/v3/assets/index-BoWfbp6N.js create mode 100644 web/dist/v3/assets/index-CPnxXrEk.css create mode 100644 web/dist/v3/assets/index-FNdWoCuA.js diff --git a/.thicket/tickets.jsonl b/.thicket/tickets.jsonl index b7aaf3d..531062d 100644 --- a/.thicket/tickets.jsonl +++ b/.thicket/tickets.jsonl @@ -23,7 +23,7 @@ {"id":"NK-6b4a2e","title":"v2 frontend BLUE LINKS","description":"Make most of the links BLUE and BOLD like in the old legacy version. Thanks","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T00:44:47.147880845Z","updated":"2026-02-14T01:09:26.770086073Z"} {"id":"NK-6o87rr","title":"Vanilla JS: Implement Pagination","description":"Implement 'Load More' or infinite scroll for item list in vanilla JS prototype.","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T04:47:40.618957267Z","updated":"2026-02-14T04:47:40.618957267Z"} {"id":"NK-6q9nyg","title":"Refactor HTTP-dependent functions for testability","description":"Several functions use http.Get or external libraries directly (GetFullContent uses goose, ResolveFeedURL uses http.Get + goquery, imageProxyHandler uses http.Client). Refactor these to accept interfaces for HTTP fetching so they can be unit tested with mocks. This is the primary blocker for reaching 90% coverage.","type":"cleanup","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T03:54:37.630148644Z","updated":"2026-02-14T02:44:05.328784994Z"} -{"id":"NK-7bha4u","title":"Vanilla JS (v3): Tags, Filters, and Paging","description":"","type":"feature","status":"open","priority":2,"labels":null,"assignee":"","created":"2026-02-16T01:44:59.371359809Z","updated":"2026-02-16T01:44:59.371359809Z"} +{"id":"NK-7bha4u","title":"Vanilla JS (v3): Tags, Filters, and Paging","description":"","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-16T01:44:59.371359809Z","updated":"2026-02-16T02:01:57.891648743Z"} {"id":"NK-7jh6re","title":"sidebar still ugly","description":"still very ugly, even compared to the original v1 static version\n\neither make it nicer or just copy the v1 version more directly","type":"feature","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T17:59:17.948112909Z","updated":"2026-02-14T18:01:26.48034794Z"} {"id":"NK-7tzbql","title":"Fix TUI Content View Navigation and Interaction","description":"The TUI content view (reading a single item) is currently non-functional or severely limited. Users cannot easily navigate back, scroll, or interact with the content. This task involves improving the 'viewContent' state in the TUI.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T05:02:57.382793121Z","updated":"2026-02-13T05:06:15.144485446Z"} {"id":"NK-7u97bb","title":"Freeing up space by purging very old items","description":"I have been running neko for so long that my production database is 1.4GB. Come up with a tool (ok to run it from command line) that purges some super old feed items to save space. Probably needs some variables on age, etc. Think carefully about the algorithm! it should be accessible from the CLI to start, although maybe we should show \"db size\" in settings too with an option to clean up.","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-15T03:01:05.643515805Z","updated":"2026-02-15T18:51:26.631274215Z"} @@ -76,6 +76,7 @@ {"id":"NK-hidz4w","title":"Add Local Git Hooks","description":"Create a script/make target to install a pre-push hook that runs 'make check'. This enforces quality gates locally, keeping the CI clean.","type":"task","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-15T16:23:36.906173277Z","updated":"2026-02-15T17:25:28.129358055Z"} {"id":"NK-hj6f9p","title":"Investigate E2E Tag View flakiness","description":"The E2E test for /v2/tag/Tech was temporarily disabled because it was timing out/failing. Investigate if it's a race condition or a routing issue.","type":"bug","status":"open","priority":4,"labels":null,"assignee":"","created":"2026-02-15T01:04:54.404114014Z","updated":"2026-02-15T19:14:17.974207248Z"} {"id":"NK-hspao2","title":"Vanilla JS: Implement Test Infrastructure","description":"Setup testing infrastructure for vanilla JS prototype to ensure 80% coverage. Refactor app.js for testability and add unit tests.","type":"task","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T05:13:11.587767054Z","updated":"2026-02-14T05:13:11.587767054Z"} +{"id":"NK-htk1zc","title":"Vanilla JS (v3): Settings, Search, and Keyboard Shortcuts","description":"","type":"feature","status":"open","priority":2,"labels":null,"assignee":"","created":"2026-02-16T02:02:01.850554958Z","updated":"2026-02-16T02:02:01.850554958Z"} {"id":"NK-hy162w","title":"URLs in UI are api/feed output, not loadable HTML","description":"After clicking in the sidebar, you get to a URL like http://localhost:9001/feed/38?filter=all but if you hit \"reload\" in the browser that retuns a blob of JSON to the browser! Oops. Maybe just don't change the user visible URL at all. If we do change the URLs, maybe just use #/feed/38/filter=all or something similar that is just client side for the JS.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-15T22:00:10.335296177Z","updated":"2026-02-15T22:22:41.252479327Z"} {"id":"NK-hyej38","title":"[ui] when a left menu item is \"active\" make it bold","description":"The \"default\" is UNREAD - this should be in the \"bold\" state when you're seeing that. When you filter out to \"ALL\" that should instead be bold. Same with the individual feeds if one is selected. And Starred.","type":"epic","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T00:47:25.74838134Z","updated":"2026-02-14T01:25:07.267016355Z"} {"id":"NK-iklxn4","title":"infinite scroll on ipad/mobile","description":"On a mobile device, the infinite scrooll didn't seem to be working properly and triggering as I scrolled to the bottom. Are the right triggers set up for mobile browsers as well as desktop -- this was on an ipad.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-15T16:12:01.013697894Z","updated":"2026-02-15T16:35:49.542636756Z"} diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts index 4012386..0d47575 100644 --- a/frontend-vanilla/src/main.ts +++ b/frontend-vanilla/src/main.ts @@ -6,48 +6,74 @@ import { router } from './router'; import type { Feed, Item, Category } from './types'; import { createFeedItem } from './components/FeedItem'; +// Extend Window interface for app object +declare global { + interface Window { + app: any; + } +} + // Cache elements const appEl = document.querySelector('#app')!; // Initial Layout -appEl.innerHTML = ` -
- -
-
-

All Items

-
-
-
-
-
-
Select an item to read
-
-
-
-`; +function renderLayout() { + appEl.className = `theme-${store.theme} font-${store.fontTheme}`; + appEl.innerHTML = ` +
+ +
+
+

All Items

+
+
+
+
+
+
Select an item to read
+
+
+
+ `; + + // Attach search listener + const searchInput = document.getElementById('search-input') as HTMLInputElement; + searchInput?.addEventListener('input', (e) => { + const query = (e.target as HTMLInputElement).value; + window.app.setSearch(query); + }); +} + +renderLayout(); const feedListEl = document.getElementById('feed-list')!; const tagListEl = document.getElementById('tag-list')!; @@ -56,10 +82,13 @@ const viewTitleEl = document.getElementById('view-title')!; const itemListEl = document.getElementById('item-list-container')!; const itemDetailEl = document.getElementById('item-detail-content')!; +let activeItemId: number | null = null; + // --- Rendering Functions --- function renderFeeds() { const { feeds, activeFeedId } = store; + if (!feedListEl) return; feedListEl.innerHTML = feeds.map((feed: Feed) => createFeedItem(feed, feed._id === activeFeedId) ).join(''); @@ -67,6 +96,7 @@ function renderFeeds() { function renderTags() { const { tags, activeTagName } = store; + if (!tagListEl) return; tagListEl.innerHTML = tags.map((tag: Category) => `
  • @@ -78,6 +108,7 @@ function renderTags() { function renderFilters() { const { filter } = store; + if (!filterListEl) return; filterListEl.querySelectorAll('.filter-item').forEach(el => { el.classList.toggle('active', el.getAttribute('data-filter') === filter); }); @@ -85,6 +116,7 @@ function renderFilters() { function renderItems() { const { items, loading } = store; + if (!itemListEl) return; if (loading && items.length === 0) { itemListEl.innerHTML = '

    Loading items...

    '; @@ -99,7 +131,7 @@ function renderItems() { itemListEl.innerHTML = `