From 408f88e4d26048396a01bf82e36eb133db3feb24 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Fri, 13 Feb 2026 20:39:19 -0800 Subject: feat: add vanilla JS frontend prototype (NK-2xsgef) --- vanilla/app.js | 95 +++++++++++++++++++++++++++++++++++++++++ vanilla/index.html | 34 +++++++++++++++ vanilla/style.css | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 vanilla/app.js create mode 100644 vanilla/index.html create mode 100644 vanilla/style.css (limited to 'vanilla') diff --git a/vanilla/app.js b/vanilla/app.js new file mode 100644 index 0000000..d1b2337 --- /dev/null +++ b/vanilla/app.js @@ -0,0 +1,95 @@ +document.addEventListener('DOMContentLoaded', () => { + fetchFeeds(); + fetchItems(); // Default to fetching recent items +}); + +async function fetchFeeds() { + try { + const response = await fetch('/api/feed/'); + if (!response.ok) throw new Error('Failed to fetch feeds'); + const feeds = await response.json(); + renderFeeds(feeds); + } catch (err) { + console.error(err); + document.getElementById('feeds-nav').innerHTML = '
Error loading feeds
'; + } +} + +async function fetchItems(feedId = null) { + const listEl = document.getElementById('entries-list'); + listEl.innerHTML = '
Loading items...
'; + + let url = '/api/stream/'; + if (feedId) { + url += `?feed_id=${feedId}`; + } + + try { + const response = await fetch(url); + if (!response.ok) throw new Error('Failed to fetch items'); + const items = await response.json(); + renderItems(items); + } catch (err) { + console.error(err); + listEl.innerHTML = '
Error loading items
'; + } +} + +function renderFeeds(feeds) { + const nav = document.getElementById('feeds-nav'); + nav.innerHTML = ''; + + const allLink = document.createElement('div'); + allLink.className = 'feed-item'; + allLink.textContent = 'All Items'; + allLink.onclick = () => { + document.getElementById('feed-title').textContent = 'All Items'; + fetchItems(); + }; + nav.appendChild(allLink); + + feeds.forEach(feed => { + const div = document.createElement('div'); + div.className = 'feed-item'; + div.textContent = feed.title || feed.url; + div.title = feed.url; + div.onclick = () => { + document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active')); + div.classList.add('active'); + document.getElementById('feed-title').textContent = feed.title; + fetchItems(feed.id); + }; + nav.appendChild(div); + }); +} + +function renderItems(items) { + const list = document.getElementById('entries-list'); + list.innerHTML = ''; + + if (items.length === 0) { + list.innerHTML = '
No items found.
'; + return; + } + + items.forEach(item => { + const article = document.createElement('article'); + article.className = 'entry'; + + const date = new Date(item.published_at || item.created_at).toLocaleString(); + + article.innerHTML = ` +
+ ${item.title} + +
+
+ ${item.description || ''} +
+ `; + list.appendChild(article); + }); +} diff --git a/vanilla/index.html b/vanilla/index.html new file mode 100644 index 0000000..98d505b --- /dev/null +++ b/vanilla/index.html @@ -0,0 +1,34 @@ + + + + + + Neko Reader (Vanilla) + + + + +
+ +
+
+

All Items

+
+
+
Loading items...
+
+
+
+ + + diff --git a/vanilla/style.css b/vanilla/style.css new file mode 100644 index 0000000..f83011f --- /dev/null +++ b/vanilla/style.css @@ -0,0 +1,123 @@ +:root { + --bg-color: #f6f6f6; + --sidebar-bg: #eaeaea; + --item-bg: #fff; + --text-color: #222; + --link-color: #0000EE; + /* Standard blue link */ + --border-color: #ddd; + --selected-bg: #e8f0fe; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + overflow: hidden; + /* App container handles scrolling */ +} + +#app { + display: flex; + height: 100vh; +} + +#sidebar { + width: 250px; + background-color: var(--sidebar-bg); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; +} + +#sidebar header { + padding: 1rem; + border-bottom: 1px solid var(--border-color); +} + +#sidebar h1 { + margin: 0; + font-size: 1.2rem; +} + +#feeds-nav { + flex: 1; + overflow-y: auto; + padding: 0.5rem; +} + +.feed-item { + padding: 0.5rem; + cursor: pointer; + border-radius: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.feed-item:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.feed-item.active { + font-weight: bold; + background-color: rgba(0, 0, 0, 0.1); +} + +#main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: #fff; +} + +#main-header { + padding: 1rem; + border-bottom: 1px solid var(--border-color); + background-color: #fcfcfc; +} + +#main-header h2 { + margin: 0; + font-size: 1.5rem; +} + +#entries-list { + flex: 1; + overflow-y: auto; + padding: 1rem; +} + +.entry { + background-color: var(--item-bg); + border-bottom: 1px solid var(--border-color); + padding: 1rem 0; +} + +.entry-header { + margin-bottom: 0.5rem; +} + +.entry-title { + font-size: 1.2rem; + font-weight: bold; + color: var(--link-color); + text-decoration: none; + display: block; + margin-bottom: 0.25rem; +} + +.entry-meta { + font-size: 0.85rem; + color: #666; +} + +.entry-content { + line-height: 1.6; + max-width: 800px; +} + +.entry-content img { + max-width: 100%; + height: auto; +} \ No newline at end of file -- cgit v1.2.3