aboutsummaryrefslogtreecommitdiffstats
path: root/web/dist
diff options
context:
space:
mode:
Diffstat (limited to 'web/dist')
-rw-r--r--web/dist/vanilla/app.js257
-rw-r--r--web/dist/vanilla/index.html49
-rw-r--r--web/dist/vanilla/style.css173
3 files changed, 0 insertions, 479 deletions
diff --git a/web/dist/vanilla/app.js b/web/dist/vanilla/app.js
deleted file mode 100644
index 65a0833..0000000
--- a/web/dist/vanilla/app.js
+++ /dev/null
@@ -1,257 +0,0 @@
-document.addEventListener('DOMContentLoaded', () => {
- fetchFeeds();
- fetchItems(); // Default to fetching recent items
-
- const searchInput = document.getElementById('search-input');
- searchInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- const query = searchInput.value.trim();
- if (query) {
- document.getElementById('feed-title').textContent = `Search: ${query}`;
- document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active'));
- fetchItems(null, null, query);
- }
- }
- });
-});
-
-export async function fetchFeeds(apiBase = '') {
- try {
- const response = await fetch(`${apiBase}/api/feed/`);
- if (!response.ok) throw new Error('Failed to fetch feeds');
- const feeds = await response.json();
- renderFeeds(feeds);
- return feeds;
- } catch (err) {
- console.error(err);
- const nav = document.getElementById('feeds-nav');
- if (nav) nav.innerHTML = '<div class="error">Error loading feeds</div>';
- throw err;
- }
-}
-
-export async function fetchItems(feedId = null, filter = null, query = null, apiBase = '') {
- const listEl = document.getElementById('entries-list');
- if (listEl) listEl.innerHTML = '<div class="loading">Loading items...</div>';
-
- let url = `${apiBase}/api/stream/`;
- const params = new URLSearchParams();
- if (feedId) params.append('feed_id', feedId);
- if (filter === 'unread') params.append('read_filter', 'unread');
- if (filter === 'starred') params.append('starred', 'true');
- if (query) params.append('q', query);
-
- if ([...params].length > 0) {
- url += '?' + params.toString();
- }
-
- try {
- const response = await fetch(url);
- if (!response.ok) throw new Error('Failed to fetch items');
- const items = await response.json();
- renderItems(items);
- return items;
- } catch (err) {
- console.error(err);
- if (listEl) listEl.innerHTML = '<div class="error">Error loading items</div>';
- throw err;
- }
-}
-
-export function renderFeeds(feeds) {
- const nav = document.getElementById('feeds-nav');
- if (!nav) return;
-
- // Clear existing items but keep search container if present
- const searchContainer = nav.querySelector('.search-container');
- nav.innerHTML = '';
- if (searchContainer) nav.appendChild(searchContainer);
-
- const allLink = document.createElement('div');
- allLink.className = 'feed-item';
- allLink.textContent = 'All Items';
- allLink.onclick = () => {
- document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active'));
- allLink.classList.add('active');
- const title = document.getElementById('feed-title');
- if (title) title.textContent = 'All Items';
- fetchItems();
- };
- nav.appendChild(allLink);
-
- const unreadLink = document.createElement('div');
- unreadLink.className = 'feed-item';
- unreadLink.textContent = 'Unread Items';
- unreadLink.onclick = () => {
- document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active'));
- unreadLink.classList.add('active');
- const title = document.getElementById('feed-title');
- if (title) title.textContent = 'Unread Items';
- fetchItems(null, 'unread');
- };
- nav.appendChild(unreadLink);
-
- const starredLink = document.createElement('div');
- starredLink.className = 'feed-item';
- starredLink.textContent = 'Starred Items';
- starredLink.onclick = () => {
- document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active'));
- starredLink.classList.add('active');
- const title = document.getElementById('feed-title');
- if (title) title.textContent = 'Starred Items';
- fetchItems(null, 'starred');
- };
- nav.appendChild(starredLink);
-
- if (Array.isArray(feeds)) {
- 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');
- const title = document.getElementById('feed-title');
- if (title) title.textContent = feed.title;
- fetchItems(feed.id);
- };
- nav.appendChild(div);
- });
- }
-}
-
-export function renderItems(items) {
- const list = document.getElementById('entries-list');
- if (!list) return;
- list.innerHTML = '';
-
- if (!items || items.length === 0) {
- list.innerHTML = '<div class="empty">No items found.</div>';
- 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 = `
- <header class="entry-header">
- <div class="entry-controls">
- <button class="btn-star ${item.starred ? 'active' : ''}" data-id="${item.id}" data-starred="${item.starred}">${item.starred ? '★' : '☆'}</button>
- <button class="btn-read ${item.read ? 'read' : 'unread'}" data-id="${item.id}" data-read="${item.read}">${item.read ? 'Mark Unread' : 'Mark Read'}</button>
- </div>
- <a href="${item.url}" class="entry-title ${item.read ? 'read' : ''}" target="_blank">${item.title}</a>
- <div class="entry-meta">
- ${item.feed ? `<span class="feed-name">${item.feed.title}</span> • ` : ''}
- <span class="date">${date}</span>
- </div>
- </header>
- <div class="entry-content">
- ${item.description || ''}
- </div>
- `;
-
- // Add event listeners programmatically to avoid inline onclick with modules
- const starBtn = article.querySelector('.btn-star');
- starBtn.onclick = () => toggleStar(item.id, item.starred, starBtn);
-
- const readBtn = article.querySelector('.btn-read');
- readBtn.onclick = () => toggleRead(item.id, item.read, readBtn);
-
- list.appendChild(article);
- });
-}
-
-export async function toggleStar(id, currentStatus, btn, apiBase = '') {
- const newStatus = !currentStatus;
- try {
- const response = await fetch(`${apiBase}/api/item/${id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: id, starred: newStatus })
- });
- if (!response.ok) throw new Error('Failed to toggle star');
-
- // Update UI
- btn.textContent = newStatus ? '★' : '☆';
- btn.classList.toggle('active');
- btn.onclick = () => toggleStar(id, newStatus, btn, apiBase);
-
- // Update data attributes
- btn.dataset.starred = newStatus;
-
- return newStatus;
- } catch (err) {
- console.error(err);
- alert('Error toggling star');
- throw err;
- }
-}
-
-export async function toggleRead(id, currentStatus, btn, apiBase = '') {
- const newStatus = !currentStatus;
- try {
- const response = await fetch(`${apiBase}/api/item/${id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: id, read: newStatus })
- });
- if (!response.ok) throw new Error('Failed to toggle read');
-
- // Update UI
- btn.textContent = newStatus ? 'Mark Unread' : 'Mark Read';
- btn.classList.toggle('read');
- btn.classList.toggle('unread');
- btn.onclick = () => toggleRead(id, newStatus, btn, apiBase);
-
- // Update data attributes
- btn.dataset.read = newStatus;
-
- // Find title and dim it if read
- const header = btn.closest('.entry-header');
- if (header) {
- const title = header.querySelector('.entry-title');
- if (title) {
- if (newStatus) {
- title.classList.add('read');
- } else {
- title.classList.remove('read');
- }
- }
- }
-
- return newStatus;
- } catch (err) {
- console.error(err);
- alert('Error toggling read status');
- throw err;
- }
-}
-
-export function init() {
- if (typeof document !== 'undefined') {
- // Only run if we're in a browser environment with these elements
- if (document.getElementById('feeds-nav')) {
- fetchFeeds();
- fetchItems();
-
- const searchInput = document.getElementById('search-input');
- if (searchInput) {
- searchInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- const query = searchInput.value.trim();
- if (query) {
- const title = document.getElementById('feed-title');
- if (title) title.textContent = `Search: ${query}`;
- document.querySelectorAll('.feed-item').forEach(el => el.classList.remove('active'));
- fetchItems(null, null, query);
- }
- }
- });
- }
- }
- }
-}
diff --git a/web/dist/vanilla/index.html b/web/dist/vanilla/index.html
deleted file mode 100644
index c504f6f..0000000
--- a/web/dist/vanilla/index.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Neko Reader (Vanilla)</title>
- <link rel="stylesheet" href="style.css">
- <style>
- /* Minimal reset for now, proper styles in style.css */
- body,
- html {
- margin: 0;
- padding: 0;
- height: 100%;
- font-family: sans-serif;
- }
- </style>
-</head>
-
-<body>
- <div id="app">
- <aside id="sidebar">
- <header>
- <h1>🐱 Neko</h1>
- </header>
- <nav id="feeds-nav">
- <div class="search-container">
- <input type="text" id="search-input" placeholder="Search items..." />
- </div>
- <div class="loading">Loading feeds...</div>
- </nav>
- </aside>
- <main id="main">
- <header id="main-header">
- <h2 id="feed-title">All Items</h2>
- </header>
- <div id="entries-list">
- <div class="loading">Loading items...</div>
- </div>
- </main>
- </div>
- <script type="module">
- import { init } from './app.js';
- document.addEventListener('DOMContentLoaded', init);
- </script>
-</body>
-
-</html> \ No newline at end of file
diff --git a/web/dist/vanilla/style.css b/web/dist/vanilla/style.css
deleted file mode 100644
index 573248d..0000000
--- a/web/dist/vanilla/style.css
+++ /dev/null
@@ -1,173 +0,0 @@
-: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;
-}
-
-.search-container {
- padding: 0.5rem;
- border-bottom: 1px solid var(--border-color);
-}
-
-#search-input {
- width: 100%;
- padding: 0.5rem;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- box-sizing: border-box;
-}
-
-#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;
- position: relative;
- padding-left: 0;
-}
-
-.entry-controls {
- display: inline-block;
- vertical-align: middle;
- margin-right: 0.5rem;
-}
-
-.btn-star,
-.btn-read {
- background: none;
- border: none;
- cursor: pointer;
- font-size: 1rem;
- padding: 2px 4px;
- margin-right: 4px;
- color: #ccc;
-}
-
-.btn-star.active {
- color: orange;
-}
-
-.btn-star:hover,
-.btn-read:hover {
- color: #888;
-}
-
-.entry-title {
- font-size: 1.2rem;
- font-weight: bold;
- color: var(--link-color);
- text-decoration: none;
- display: inline-block;
- /* Changed to inline-block for alignment */
- vertical-align: middle;
- margin-bottom: 0.25rem;
-}
-
-.entry-title.read {
- font-weight: normal;
- color: #555;
- text-decoration: none;
-}
-
-.entry-meta {
- font-size: 0.85rem;
- color: #666;
- margin-top: 4px;
-}
-
-.entry-content {
- line-height: 1.6;
- max-width: 800px;
-}
-
-.entry-content img {
- max-width: 100%;
- height: auto;
-} \ No newline at end of file