From cba29e6aae637b04ff6eaf28f74bc15b6242b9ea Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Mon, 16 Feb 2026 19:37:50 -0800 Subject: Remove legacy V2 React frontend and update build/test scripts to focus on Vanilla JS (V3) --- frontend/coverage/src/App.css.html | 478 ------------ frontend/coverage/src/App.tsx.html | 505 ------------- frontend/coverage/src/components/FeedItem.css.html | 526 ------------- frontend/coverage/src/components/FeedItem.tsx.html | 430 ----------- .../coverage/src/components/FeedItems.css.html | 151 ---- .../coverage/src/components/FeedItems.tsx.html | 838 --------------------- frontend/coverage/src/components/FeedList.css.html | 724 ------------------ frontend/coverage/src/components/FeedList.tsx.html | 721 ------------------ frontend/coverage/src/components/Login.css.html | 274 ------- frontend/coverage/src/components/Login.tsx.html | 286 ------- frontend/coverage/src/components/Settings.css.html | 802 -------------------- frontend/coverage/src/components/Settings.tsx.html | 793 ------------------- frontend/coverage/src/components/index.html | 266 ------- frontend/coverage/src/index.html | 146 ---- 14 files changed, 6940 deletions(-) delete mode 100644 frontend/coverage/src/App.css.html delete mode 100644 frontend/coverage/src/App.tsx.html delete mode 100644 frontend/coverage/src/components/FeedItem.css.html delete mode 100644 frontend/coverage/src/components/FeedItem.tsx.html delete mode 100644 frontend/coverage/src/components/FeedItems.css.html delete mode 100644 frontend/coverage/src/components/FeedItems.tsx.html delete mode 100644 frontend/coverage/src/components/FeedList.css.html delete mode 100644 frontend/coverage/src/components/FeedList.tsx.html delete mode 100644 frontend/coverage/src/components/Login.css.html delete mode 100644 frontend/coverage/src/components/Login.tsx.html delete mode 100644 frontend/coverage/src/components/Settings.css.html delete mode 100644 frontend/coverage/src/components/Settings.tsx.html delete mode 100644 frontend/coverage/src/components/index.html delete mode 100644 frontend/coverage/src/index.html (limited to 'frontend/coverage/src') diff --git a/frontend/coverage/src/App.css.html b/frontend/coverage/src/App.css.html deleted file mode 100644 index d23e87b..0000000 --- a/frontend/coverage/src/App.css.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - - Code coverage report for src/App.css - - - - - - - - - -
-
-

All files / src App.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
/* Resets and Base Styles */
-* {
-  box-sizing: border-box;
-}
- 
-body {
-  margin: 0;
-}
- 
-/* Dashboard Layout */
-.dashboard {
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-  overflow: hidden;
-  /* Prevent body scroll */
-}
- 
-/* Header styles removed as we moved to sidebar navigation */
- 
-.dashboard-content {
-  display: flex;
-  flex: 1;
-  overflow: hidden;
-  position: relative;
-}
- 
-.dashboard-sidebar {
-  width: 11rem;
-  background: transparent;
-  border-right: 1px solid var(--border-color);
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-  transition: margin-left 0.4s ease;
-  /* No padding here, handled in FeedList */
-}
- 
-.dashboard-sidebar.hidden {
-  margin-left: -11rem;
-}
- 
-.dashboard-main {
-  flex: 1;
-  padding: 2rem;
-  overflow-y: auto;
-  background: var(--bg-color);
-  margin-left: 0;
-}
- 
-.dashboard-main>* {
-  max-width: 35em;
-  margin: 0 auto;
-}
- 
-.fixed-toggle {
-  position: absolute;
-  top: 1rem;
-  left: 1rem;
-  z-index: 1000;
-  background: var(--bg-color);
-  /* Added bg to be visible over content if needed */
-  border: none;
-  font-size: 2rem;
-  line-height: 1;
-  cursor: pointer;
-  padding: 0.2rem;
-  color: var(--text-color);
-  border-radius: 50%;
-  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
- 
-.fixed-toggle:hover {
-  transform: scale(1.1);
-}
- 
-/* Mobile Responsiveness */
-@media (max-width: 768px) {
-  .dashboard-sidebar {
-    position: fixed;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    z-index: 1100;
-    box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
-    width: 14rem;
-    /* Slightly wider on mobile for better target area */
-  }
- 
-  .dashboard-sidebar.hidden {
-    margin-left: -14rem;
-  }
- 
-  .dashboard-main {
-    padding: 1rem;
-    padding-top: 4rem;
-    /* Space for the toggle button */
-  }
- 
-  .dashboard-main>* {
-    max-width: 100%;
-  }
- 
-  /* When sidebar is visible on mobile, we show a backdrop */
-  .sidebar-backdrop {
-    position: fixed;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background: rgba(0, 0, 0, 0.4);
-    z-index: 1050;
-    animation: fadeIn 0.3s ease;
-  }
- 
-  .dashboard.sidebar-visible::after {
-    display: none;
-  }
-}
- 
-@keyframes fadeIn {
-  from {
-    opacity: 0;
-  }
- 
-  to {
-    opacity: 1;
-  }
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html deleted file mode 100644 index 6ec66af..0000000 --- a/frontend/coverage/src/App.tsx.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - Code coverage report for src/App.tsx - - - - - - - - - -
-
-

All files / src App.tsx

-
- -
- 65.71% - Statements - 23/35 -
- - -
- 60% - Branches - 15/25 -
- - -
- 53.84% - Functions - 7/13 -
- - -
- 64.7% - Lines - 22/34 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141  -  -  -  -  -  -  -  -2x -2x -  -2x -1x -  -1x -1x -  -  -  -  -  -  -  -2x -1x -  -  -1x -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -1x -1x -  -  -  -  -  -  -1x -1x -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -  -2x -  -  -  -  -2x -  -  -  -  -2x -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import React, { useEffect, useState } from 'react';
-import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
-import Login from './components/Login';
-import './App.css';
-import { apiFetch } from './utils';
- 
-// Protected Route wrapper
-function RequireAuth({ children }: { children: React.ReactElement }) {
-  const [auth, setAuth] = useState<boolean | null>(null);
-  const location = useLocation();
- 
-  useEffect(() => {
-    apiFetch('/api/auth')
-      .then((res) => {
-        if (res.ok) {
-          setAuth(true);
-        } else E{
-          setAuth(false);
-        }
-      })
-      .catch(() => setAuth(false));
-  }, []);
- 
-  if (auth === null) {
-    return <div>Loading...</div>;
-  }
- 
-  Iif (!auth) {
-    return <Navigate to="/login" state={{ from: location }} replace />;
-  }
- 
-  return children;
-}
- 
-import FeedList from './components/FeedList';
-import FeedItems from './components/FeedItems';
-import Settings from './components/Settings';
- 
-interface DashboardProps {
-  theme: string;
-  setTheme: (t: string) => void;
-  fontTheme: string;
-  setFontTheme: (t: string) => void;
-}
- 
-function Dashboard({ theme, setTheme, fontTheme, setFontTheme }: DashboardProps) {
-  const [sidebarVisible, setSidebarVisible] = useState(window.innerWidth > 768);
- 
-  useEffect(() => {
-    const handleResize = () => {
-      if (window.innerWidth > 768) {
-        setSidebarVisible(true);
-      } else {
-        setSidebarVisible(false);
-      }
-    };
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
-  }, []);
- 
-  return (
-    <div
-      className={`dashboard ${sidebarVisible ? 'sidebar-visible' : 'sidebar-hidden'} theme-${theme} font-${fontTheme}`}
-    >
-      <div className="dashboard-content">
-        {(!sidebarVisible || window.innerWidth <= 768) && (
-          <button
-            className="sidebar-toggle fixed-toggle"
-            onClick={() => setSidebarVisible(!sidebarVisible)}
-            title={sidebarVisible ? "Hide Sidebar" : "Show Sidebar"}
-          >
-            🐱
-          </button>
-        )}
-        {sidebarVisible && (
-          <div
-            className="sidebar-backdrop"
-            onClick={() => setSidebarVisible(false)}
-          />
-        )}
-        <aside className={`dashboard-sidebar ${sidebarVisible ? '' : 'hidden'}`}>
-          <FeedList
-            theme={theme}
-            setTheme={setTheme}
-            setSidebarVisible={setSidebarVisible}
-            isMobile={window.innerWidth <= 768}
-          />
-        </aside>
-        <main className="dashboard-main">
-          <Routes>
-            <Route path="/feed/:feedId" element={<FeedItems />} />
-            <Route path="/tag/:tagName" element={<FeedItems />} />
-            <Route path="/settings" element={<Settings fontTheme={fontTheme} setFontTheme={setFontTheme} />} />
-            <Route path="/" element={<FeedItems />} />
-          </Routes>
-        </main>
-      </div>
-    </div>
-  );
-}
- 
-function App() {
-  const [theme, setTheme] = useState(localStorage.getItem('neko-theme') || 'light');
-  const [fontTheme, setFontTheme] = useState(localStorage.getItem('neko-font-theme') || 'default');
- 
-  const handleSetTheme = (newTheme: string) => {
-    setTheme(newTheme);
-    localStorage.setItem('neko-theme', newTheme);
-  };
- 
-  const handleSetFontTheme = (newFontTheme: string) => {
-    setFontTheme(newFontTheme);
-    localStorage.setItem('neko-font-theme', newFontTheme);
-  };
- 
-  const basename = window.location.pathname.startsWith('/v2') ? '/v2' : '/';
- 
-  return (
-    <BrowserRouter basename={basename}>
-      <Routes>
-        <Route path="/login" element={<Login />} />
-        <Route
-          path="/*"
-          element={
-            <RequireAuth>
-              <Dashboard
-                theme={theme}
-                setTheme={handleSetTheme}
-                fontTheme={fontTheme}
-                setFontTheme={handleSetFontTheme}
-              />
-            </RequireAuth>
-          }
-        />
-      </Routes>
-    </BrowserRouter>
-  );
-}
- 
-export default App;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.css.html b/frontend/coverage/src/components/FeedItem.css.html deleted file mode 100644 index 192959f..0000000 --- a/frontend/coverage/src/components/FeedItem.css.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - Code coverage report for src/components/FeedItem.css - - - - - - - - - -
-
-

All files / src/components FeedItem.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
.feed-item {
-  padding: 1rem;
-  margin-top: 5rem;
-  list-style: none;
-  border-bottom: none;
-}
- 
-/* removed read/unread specific font-weight to keep it always bold as requested */
- 
-.item-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-  margin-bottom: 0.5rem;
-}
- 
-.item-title {
-  font-family: var(--font-heading);
-  font-size: 1.8rem;
-  font-weight: bold;
-  text-decoration: none;
-  color: var(--link-color);
-  display: block;
-  flex: 1;
-}
- 
-.item-title:hover {
-  text-decoration: none;
-  color: var(--link-color);
-}
- 
-.item-actions {
-  display: flex;
-  gap: 0.5rem;
-  margin-left: 1rem;
-}
- 
-/* Legacy controls were simple text/links, but buttons are fine if minimal */
-.star-btn {
-  background: none;
-  border: none;
-  cursor: pointer;
-  font-size: 1.25rem;
-  padding: 0 0 0 0.5rem;
-  vertical-align: middle;
-  transition: color 0.2s;
-  line-height: 1;
-}
- 
-.star-btn.is-starred {
-  color: blue;
-}
- 
-.star-btn.is-unstarred {
-  color: var(--text-color);
-  opacity: 0.3;
-}
- 
-.star-btn:hover {
-  color: blue;
-}
- 
-.action-btn {
-  background: var(--sidebar-bg);
-  border: 1px solid var(--border-color, #ccc);
-  cursor: pointer;
-  padding: 2px 6px;
-  font-size: 1rem;
-  color: blue;
-  font-weight: bold;
-}
- 
-.action-btn:hover {
-  background-color: #eee;
-}
- 
-.dateline {
-  margin-top: 0;
-  font-weight: normal;
-  font-size: 0.75em;
-  color: #ccc;
-  margin-bottom: 1rem;
-}
- 
-.dateline a {
-  color: #ccc;
-  text-decoration: none;
-}
- 
-.item-description {
-  color: var(--text-color);
-  line-height: 1.5;
-  font-size: 1rem;
-  margin-top: 1rem;
-}
- 
-.item-description img {
-  max-width: 100%;
-  height: auto;
-  display: block;
-  margin: 1rem 0;
-}
- 
-.item-description blockquote {
-  padding: 1rem 1rem 0 1rem;
-  border-left: 4px solid var(--sidebar-bg);
-  color: var(--text-color);
-  opacity: 0.8;
-  margin-left: 0;
-}
- 
-.scrape-btn {
-  background: var(--bg-color);
-  border: 1px solid var(--border-color, #ccc);
-  color: blue;
-  cursor: pointer;
-  font-family: var(--font-heading);
-  font-weight: bold;
-  font-size: 0.8rem;
-  padding: 2px 6px;
-  margin-left: 0.5rem;
-}
- 
-.scrape-btn:hover {
-  background: var(--sidebar-bg);
-}
- 
-@media (max-width: 768px) {
-  .feed-item {
-    margin-top: 2rem;
-    padding: 0.5rem;
-  }
- 
-  .item-title {
-    font-size: 1.4rem;
-    word-break: break-word;
-  }
- 
-  .item-header {
-    flex-direction: column;
-    gap: 0.5rem;
-  }
- 
-  .item-actions {
-    margin-left: 0;
-    margin-bottom: 0.5rem;
-  }
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.tsx.html b/frontend/coverage/src/components/FeedItem.tsx.html deleted file mode 100644 index 6df1fec..0000000 --- a/frontend/coverage/src/components/FeedItem.tsx.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - Code coverage report for src/components/FeedItem.tsx - - - - - - - - - -
-
-

All files / src/components FeedItem.tsx

-
- -
- 78.12% - Statements - 25/32 -
- - -
- 86.95% - Branches - 20/23 -
- - -
- 83.33% - Functions - 10/12 -
- - -
- 80.64% - Lines - 25/31 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116  -  -  -  -  -  -  -  -  -  -  -56x -56x -  -56x -22x -  -  -56x -1x -  -  -56x -1x -  -1x -1x -  -1x -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -1x -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -56x -1x -1x -1x -  -1x -1x -  -  -1x -1x -  -  -  -  -  -  -  -56x -  -  -  -  -  -  -  -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useState, useEffect } from 'react';
-import type { Item } from '../types';
-import './FeedItem.css';
- 
-import { apiFetch } from '../utils';
- 
-interface FeedItemProps {
-  item: Item;
-}
- 
-export default function FeedItem({ item: initialItem }: FeedItemProps) {
-  const [item, setItem] = useState(initialItem);
-  const [loading, setLoading] = useState(false);
- 
-  useEffect(() => {
-    setItem(initialItem);
-  }, [initialItem]);
- 
-  const toggleStar = () => {
-    updateItem({ ...item, starred: !item.starred });
-  };
- 
-  const updateItem = (newItem: Item) => {
-    setLoading(true);
-    // Optimistic update
-    const previousItem = item;
-    setItem(newItem);
- 
-    apiFetch(`/api/item/${newItem._id}`, {
-      method: 'PUT',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify({
-        _id: newItem._id,
-        read: newItem.read,
-        starred: newItem.starred,
-      }),
-    })
-      .then((res) => {
-        Iif (!res.ok) {
-          throw new Error('Failed to update item');
-        }
-        return res.json();
-      })
-      .then(() => {
-        // Confirm with server response if needed, but for now we trust the optimistic update
-        // or we could setItem(updated) if the server returns the full object
-        setLoading(false);
-      })
-      .catch((err) => {
-        console.error('Error updating item:', err);
-        // Revert on error
-        setItem(previousItem);
-        setLoading(false);
-      });
-  };
- 
-  const loadFullContent = (e: React.MouseEvent) => {
-    e.stopPropagation();
-    setLoading(true);
-    apiFetch(`/api/item/${item._id}`)
-      .then((res) => {
-        Iif (!res.ok) throw new Error('Failed to fetch full content');
-        return res.json();
-      })
-      .then((data) => {
-        setItem({ ...item, ...data });
-        setLoading(false);
-      })
-      .catch((err) => {
-        console.error('Error fetching full content:', err);
-        setLoading(false);
-      });
-  };
- 
-  return (
-    <li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}>
-      <div className="item-header">
-        <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title">
-          {item.title || '(No Title)'}
-        </a>
-        <button
-          onClick={(e) => {
-            e.stopPropagation();
-            toggleStar();
-          }}
-          className={`star-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`}
-          title={item.starred ? 'Unstar' : 'Star'}
-        >
-          ★
-        </button>
-      </div>
-      <div className="dateline">
-        <a href={item.url} target="_blank" rel="noopener noreferrer">
-          {new Date(item.publish_date).toLocaleDateString()}
-          {item.feed_title && ` - ${item.feed_title}`}
-        </a>
-        <div className="item-actions" style={{ display: 'inline-block', float: 'right' }}>
-          {!item.full_content && (
-            <button onClick={loadFullContent} className="scrape-btn" title="Load Full Content">
-              text
-            </button>
-          )}
-        </div>
-      </div>
-      {(item.full_content || item.description) && (
-        <div
-          className="item-description"
-          dangerouslySetInnerHTML={{ __html: item.full_content || item.description }}
-        />
-      )}
-    </li>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.css.html b/frontend/coverage/src/components/FeedItems.css.html deleted file mode 100644 index 66a3307..0000000 --- a/frontend/coverage/src/components/FeedItems.css.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - Code coverage report for src/components/FeedItems.css - - - - - - - - - -
-
-

All files / src/components FeedItems.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
.feed-items {
-  padding: 1rem 0;
-  /* Removing horizontal padding to avoid double-padding with FeedItem */
-}
- 
-.feed-items h2 {
-  margin-top: 0;
-  border-bottom: 2px solid var(--border-color);
-  padding-bottom: 0.5rem;
-}
- 
-.item-list {
-  list-style: none;
-  padding: 0;
-}
- 
-.loading-more {
-  padding: 2rem;
-  text-align: center;
-  color: #888;
-  font-size: 0.9rem;
-  min-height: 50px;
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.tsx.html b/frontend/coverage/src/components/FeedItems.tsx.html deleted file mode 100644 index 9811743..0000000 --- a/frontend/coverage/src/components/FeedItems.tsx.html +++ /dev/null @@ -1,838 +0,0 @@ - - - - - - Code coverage report for src/components/FeedItems.tsx - - - - - - - - - -
-
-

All files / src/components FeedItems.tsx

-
- -
- 89.23% - Statements - 116/130 -
- - -
- 76.19% - Branches - 64/84 -
- - -
- 87.09% - Functions - 27/31 -
- - -
- 89.07% - Lines - 106/119 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252  -  -  -  -  -  -  -  -36x -36x -36x -  -36x -36x -36x -36x -36x -36x -  -36x -11x -3x -  -8x -8x -  -11x -  -11x -11x -  -11x -2x -9x -1x -  -  -11x -3x -  -  -  -11x -11x -  -  -  -11x -  -11x -  -  -  -  -11x -11x -  -  -  -11x -11x -11x -  -  -11x -  -10x -  -  -10x -  -  -9x -3x -  -6x -  -9x -9x -9x -  -  -1x -1x -1x -  -  -  -36x -8x -8x -  -  -  -  -36x -5x -5x -5x -  -  -  -36x -2x -  -3x -  -2x -  -  -  -  -  -  -36x -1x -  -2x -  -1x -  -  -  -  -  -  -36x -31x -6x -  -6x -5x -5x -5x -5x -5x -1x -  -5x -  -  -  -  -5x -2x -  -  -5x -  -1x -  -  -  -  -  -  -  -1x -1x -1x -1x -  -1x -  -  -  -  -31x -31x -  -  -  -  -  -36x -  -31x -  -1x -  -1x -1x -1x -1x -1x -1x -  -  -  -  -  -  -  -  -  -31x -  -1x -1x -1x -  -  -  -  -  -  -31x -31x -31x -  -  -31x -31x -  -31x -31x -31x -  -  -  -  -36x -21x -  -20x -  -  -  -  -  -  -44x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useEffect, useState } from 'react';
-import { useParams, useSearchParams } from 'react-router-dom';
-import type { Item } from '../types';
-import FeedItem from './FeedItem';
-import './FeedItems.css';
-import { apiFetch } from '../utils';
- 
-export default function FeedItems() {
-  const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>();
-  const [searchParams] = useSearchParams();
-  const filterFn = searchParams.get('filter') || 'unread';
- 
-  const [items, setItems] = useState<Item[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [loadingMore, setLoadingMore] = useState(false);
-  const [hasMore, setHasMore] = useState(true);
-  const [error, setError] = useState('');
-  const [selectedIndex, setSelectedIndex] = useState(-1);
- 
-  const fetchItems = (maxId?: string) => {
-    if (maxId) {
-      setLoadingMore(true);
-    } else {
-      setLoading(true);
-      setItems([]);
-    }
-    setError('');
- 
-    let url = '/api/stream';
-    const params = new URLSearchParams();
- 
-    if (feedId) {
-      params.append('feed_id', feedId);
-    } else if (tagName) {
-      params.append('tag', tagName);
-    }
- 
-    if (maxId) {
-      params.append('max_id', maxId);
-    }
- 
-    // Apply filters
-    const searchQuery = searchParams.get('q');
-    Iif (searchQuery) {
-      params.append('q', searchQuery);
-    }
- 
-    Iif (filterFn === 'all') {
-      params.append('read_filter', 'all');
-    I} else if (filterFn === 'starred') {
-      params.append('starred', 'true');
-      params.append('read_filter', 'all');
-    } else {
-      // default to unread
-      Eif (!searchQuery) {
-        params.append('read_filter', 'unread');
-      }
-    }
- 
-    const queryString = params.toString();
-    Eif (queryString) {
-      url += `?${queryString}`;
-    }
- 
-    apiFetch(url)
-      .then((res) => {
-        Iif (!res.ok) {
-          throw new Error('Failed to fetch items');
-        }
-        return res.json();
-      })
-      .then((data) => {
-        if (maxId) {
-          setItems((prev) => [...prev, ...data]);
-        } else {
-          setItems(data);
-        }
-        setHasMore(data.length > 0);
-        setLoading(false);
-        setLoadingMore(false);
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-        setLoadingMore(false);
-      });
-  };
- 
-  useEffect(() => {
-    fetchItems();
-    setSelectedIndex(-1);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [feedId, tagName, filterFn, searchParams]);
- 
- 
-  const scrollToItem = (index: number) => {
-    const element = document.getElementById(`item-${index}`);
-    Eif (element) {
-      element.scrollIntoView({ behavior: 'auto', block: 'start' });
-    }
-  };
- 
-  const markAsRead = (item: Item) => {
-    const updatedItem = { ...item, read: true };
-    // Optimistic update
-    setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
- 
-    apiFetch(`/api/item/${item._id}`, {
-      method: 'PUT',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ read: true, starred: item.starred }),
-    }).catch((err) => console.error('Failed to mark read', err));
-  };
- 
-  const toggleStar = (item: Item) => {
-    const updatedItem = { ...item, starred: !item.starred };
-    // Optimistic update
-    setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
- 
-    apiFetch(`/api/item/${item._id}`, {
-      method: 'PUT',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ read: item.read, starred: !item.starred }),
-    }).catch((err) => console.error('Failed to toggle star', err));
-  };
- 
-  useEffect(() => {
-    const handleKeyDown = (e: KeyboardEvent) => {
-      Iif (items.length === 0) return;
- 
-      if (e.key === 'j') {
-        setSelectedIndex((prev) => {
-          const nextIndex = Math.min(prev + 1, items.length - 1);
-          Eif (nextIndex !== prev) {
-            const item = items[nextIndex];
-            if (!item.read) {
-              markAsRead(item);
-            }
-            scrollToItem(nextIndex);
-          }
- 
-          // If we're now on the last item and there are more items to load,
-          // trigger loading them so the next 'j' press will work
-          if (nextIndex === items.length - 1 && hasMore && !loadingMore) {
-            fetchItems(String(items[items.length - 1]._id));
-          }
- 
-          return nextIndex;
-        });
-      I} else if (e.key === 'k') {
-        setSelectedIndex((prev) => {
-          const nextIndex = Math.max(prev - 1, 0);
-          if (nextIndex !== prev) {
-            scrollToItem(nextIndex);
-          }
-          return nextIndex;
-        });
-      E} else if (e.key === 's') {
-        setSelectedIndex((currentIndex) => {
-          Eif (currentIndex >= 0 && currentIndex < items.length) {
-            toggleStar(items[currentIndex]);
-          }
-          return currentIndex;
-        });
-      }
-    };
- 
-    window.addEventListener('keydown', handleKeyDown);
-    return () => window.removeEventListener('keydown', handleKeyDown);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [items, hasMore, loadingMore]);
- 
- 
- 
-  useEffect(() => {
-    // Observer for marking items as read
-    const itemObserver = new IntersectionObserver(
-      (entries) => {
-        entries.forEach((entry) => {
-          // If item is not intersecting and is above the viewport, it's been scrolled past
-          Eif (!entry.isIntersecting && entry.boundingClientRect.top < 0) {
-            const index = Number(entry.target.getAttribute('data-index'));
-            Eif (!isNaN(index) && index >= 0 && index < items.length) {
-              const item = items[index];
-              Eif (!item.read) {
-                markAsRead(item);
-              }
-            }
-          }
-        });
-      },
-      { root: null, threshold: 0 }
-    );
- 
-    // Observer for infinite scroll (less aggressive, must be fully visible)
-    const sentinelObserver = new IntersectionObserver(
-      (entries) => {
-        entries.forEach((entry) => {
-          Eif (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) {
-            fetchItems(String(items[items.length - 1]._id));
-          }
-        });
-      },
-      { root: null, threshold: 1.0 }
-    );
- 
-    items.forEach((_, index) => {
-      const el = document.getElementById(`item-${index}`);
-      Eif (el) itemObserver.observe(el);
-    });
- 
-    const sentinel = document.getElementById('load-more-sentinel');
-    if (sentinel) sentinelObserver.observe(sentinel);
- 
-    return () => {
-      itemObserver.disconnect();
-      sentinelObserver.disconnect();
-    };
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [items, loadingMore, hasMore]);
- 
-  if (loading) return <div className="feed-items-loading">Loading items...</div>;
-  if (error) return <div className="feed-items-error">Error: {error}</div>;
- 
-  return (
-    <div className="feed-items">
-      {items.length === 0 ? (
-        <p>No items found.</p>
-      ) : (
-        <ul className="item-list">
-          {items.map((item, index) => (
-            <div
-              id={`item-${index}`}
-              key={item._id}
-              data-index={index}
-              data-selected={index === selectedIndex}
-              onClick={() => setSelectedIndex(index)}
-            >
-              <FeedItem item={item} />
-            </div>
-          ))}
-          {hasMore && (
-            <div id="load-more-sentinel" className="loading-more">
-              {loadingMore ? 'Loading more...' : ''}
-            </div>
-          )}
-        </ul>
-      )}
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.css.html b/frontend/coverage/src/components/FeedList.css.html deleted file mode 100644 index d892e77..0000000 --- a/frontend/coverage/src/components/FeedList.css.html +++ /dev/null @@ -1,724 +0,0 @@ - - - - - - Code coverage report for src/components/FeedList.css - - - - - - - - - -
-
-

All files / src/components FeedList.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
.feed-list {
-  padding: 1rem;
-  font-family: var(--font-heading);
-  color: #777;
-  /* specific v1 color */
-  font-size: 0.8rem;
-  background: var(--sidebar-bg);
-  min-height: 100%;
-  flex: 1;
-}
- 
-.feed-list h1.logo {
-  font-size: 2rem;
-  /* match v1 */
-  margin: 0 0 1rem 0;
-  line-height: 1;
-  cursor: pointer;
-  position: sticky;
-  top: 0;
-  background: var(--sidebar-bg);
-  z-index: 10;
-  padding-bottom: 0.5rem;
-  color: var(--text-color);
-}
- 
-/* Override logo color if necessary for themes */
-.theme-light .feed-list h1.logo {
-  color: #333;
-}
- 
-.theme-dark .feed-list h1.logo {
-  color: #eee;
-}
- 
-.search-section {
-  margin-bottom: 1rem;
-}
- 
-.search-input {
-  width: 100%;
-  padding: 0.25rem;
-  border: 1px solid var(--border-color, #999);
-  background: var(--bg-color);
-  color: var(--text-color);
-  font-size: 0.8rem;
-  font-family: inherit;
-  border-radius: 0;
-  /* v1 didn't have rounded inputs usually */
-}
- 
-.section-header {
-  font-size: 1rem;
-  /* v1 h4 size? */
-  font-weight: bold;
-  margin: 1rem 0 0.25rem 0;
-  cursor: pointer;
-  user-select: none;
-  font-family: var(--font-heading);
-  color: #333;
-  /* Darker than list items */
-  text-transform: lowercase;
-  font-variant: small-caps;
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
-}
- 
-.caret {
-  display: inline-block;
-  font-size: 0.6rem;
-  transition: transform 0.2s ease;
-  color: #777;
-}
- 
-.caret.expanded {
-  transform: rotate(90deg);
-}
- 
-.filter-list,
-.tag-list-items,
-.feed-list-items,
-.nav-list {
-  list-style: none;
-  padding: 0;
-  margin: 0;
-}
- 
-.filter-list li,
-.nav-list li {
-  margin-bottom: 0.1rem;
-}
- 
-.filter-list a,
-.nav-list a,
-.tag-link,
-.feed-title,
-.logout-link {
-  text-decoration: none;
-  color: var(--link-color, blue);
-  font-size: 0.8rem;
-  /* Matches v1 .75em approx */
-  display: block;
-  cursor: pointer;
-  background: none;
-  border: none;
-  padding: 0;
-  font-family: inherit;
-  font-variant: small-caps;
-  text-transform: lowercase;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
- 
-.filter-list a:hover,
-.nav-list a:hover,
-.tag-link:hover,
-.feed-title:hover,
-.logout-link:hover {
-  text-decoration: underline;
-  color: var(--link-color, blue);
-}
- 
-.filter-list a.active,
-.tag-link.active,
-.feed-title.active {
-  font-weight: bold;
-  color: #000;
-  /* Active state black */
-}
- 
-.tag-item,
-.sidebar-feed-item {
-  margin-bottom: 0;
-}
- 
-.feed-category {
-  display: none;
-}
- 
-.nav-section {
-  margin-top: 2rem;
-  border-top: 1px solid var(--border-color, #eee);
-  padding-top: 1rem;
-}
- 
-.logout-link {
-  text-align: left;
-  width: 100%;
-  color: #777;
-  display: block;
-}
- 
-.nav-link,
-.logout-link {
-  padding: 0.25rem 0;
-}
- 
-.logout-link:hover {
-  color: var(--link-color, blue);
-  text-decoration: underline;
-}
- 
-.theme-section {
-  margin-top: 1rem;
-}
- 
-.theme-selector {
-  display: flex;
-  gap: 0.5rem;
-  margin-top: 0.5rem;
-}
- 
-.theme-selector button {
-  background: rgba(0, 0, 0, 0.05);
-  border: none;
-  cursor: pointer;
-  padding: 0.4rem;
-  font-size: 1rem;
-  border-radius: 8px;
-  line-height: 1;
-  transition: all 0.2s ease;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
- 
-.theme-selector button:hover {
-  background: rgba(0, 0, 0, 0.1);
-  transform: translateY(-2px);
-}
- 
-.theme-selector button.active {
-  background: var(--border-color, #999);
-  color: white;
-  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-}
- 
-.theme-dark .theme-selector button {
-  background: rgba(255, 255, 255, 0.1);
-}
- 
-.theme-dark .theme-selector button:hover {
-  background: rgba(255, 255, 255, 0.2);
-}
- 
-/* Scrollbar styling for webkit */
-.dashboard-sidebar::-webkit-scrollbar {
-  width: 4px;
-}
- 
-.dashboard-sidebar::-webkit-scrollbar-thumb {
-  background-color: var(--border-color, #999);
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.tsx.html b/frontend/coverage/src/components/FeedList.tsx.html deleted file mode 100644 index 4061422..0000000 --- a/frontend/coverage/src/components/FeedList.tsx.html +++ /dev/null @@ -1,721 +0,0 @@ - - - - - - Code coverage report for src/components/FeedList.tsx - - - - - - - - - -
-
-

All files / src/components FeedList.tsx

-
- -
- 87.27% - Statements - 48/55 -
- - -
- 70% - Branches - 35/50 -
- - -
- 78.94% - Functions - 15/19 -
- - -
- 90% - Lines - 45/50 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -  -22x -  -22x -11x -11x -  -  -  -  -  -22x -  -  -22x -1x -1x -1x -  -  -  -22x -2x -  -  -22x -  -  -  -22x -1x -1x -  -  -  -22x -9x -  -7x -7x -  -  -7x -7x -  -  -  -7x -7x -7x -  -  -1x -1x -  -  -  -22x -13x -  -12x -2x -  -  -12x -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useEffect, useState } from 'react';
-import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'react-router-dom';
-import type { Feed, Category } from '../types';
-import './FeedList.css';
-import './FeedListVariants.css';
-import { apiFetch } from '../utils';
- 
-export default function FeedList({
-  theme,
-  setTheme,
-  setSidebarVisible,
-  isMobile,
-}: {
-  theme: string;
-  setTheme: (t: string) => void;
-  setSidebarVisible: (visible: boolean) => void;
-  isMobile: boolean;
-}) {
-  const [feeds, setFeeds] = useState<Feed[]>([]);
-  const [tags, setTags] = useState<Category[]>([]);
-  const [loading, setLoading] = useState(true);
-  const [error, setError] = useState('');
-  const [feedsExpanded, setFeedsExpanded] = useState(false);
-  const [tagsExpanded, setTagsExpanded] = useState(true);
-  const [searchQuery, setSearchQuery] = useState('');
-  const navigate = useNavigate();
-  const [searchParams] = useSearchParams();
-  const location = useLocation();
-  const { feedId, tagName } = useParams();
- 
-  const sidebarVariant = searchParams.get('sidebar') || localStorage.getItem('neko-sidebar-variant') || 'glass';
- 
-  useEffect(() => {
-    const variant = searchParams.get('sidebar');
-    Iif (variant) {
-      localStorage.setItem('neko-sidebar-variant', variant);
-    }
-  }, [searchParams]);
- 
-  const currentFilter =
-    searchParams.get('filter') ||
-    (location.pathname === '/' && !feedId && !tagName ? 'unread' : '');
- 
-  const handleSearch = (e: React.FormEvent) => {
-    e.preventDefault();
-    Eif (searchQuery.trim()) {
-      navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`);
-    }
-  };
- 
-  const toggleFeeds = () => {
-    setFeedsExpanded(!feedsExpanded);
-  };
- 
-  const toggleTags = () => {
-    setTagsExpanded(!tagsExpanded);
-  };
- 
-  const handleLinkClick = () => {
-    Eif (isMobile) {
-      setSidebarVisible(false);
-    }
-  };
- 
-  useEffect(() => {
-    Promise.all([
-      apiFetch('/api/feed/').then((res) => {
-        Iif (!res.ok) throw new Error('Failed to fetch feeds');
-        return res.json() as Promise<Feed[]>;
-      }),
-      apiFetch('/api/tag').then((res) => {
-        Iif (!res.ok) throw new Error('Failed to fetch tags');
-        return res.json() as Promise<Category[]>;
-      }),
-    ])
-      .then(([feedsData, tagsData]) => {
-        setFeeds(feedsData);
-        setTags(tagsData);
-        setLoading(false);
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  }, []);
- 
-  if (loading) return <div className="feed-list-loading">Loading feeds...</div>;
-  if (error) return <div className="feed-list-error">Error: {error}</div>;
- 
-  const handleLogout = () => {
-    apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/login'));
-  };
- 
-  return (
-    <div className={`feed-list variant-${sidebarVariant}`}>
-      <h1 className="logo" onClick={() => setSidebarVisible(false)}>
-        🐱
-      </h1>
- 
-      <div className="search-section">
-        <form onSubmit={handleSearch} className="search-form">
-          <input
-            type="search"
-            placeholder="search..."
-            value={searchQuery}
-            onChange={(e) => setSearchQuery(e.target.value)}
-            className="search-input"
-          />
-        </form>
-      </div>
- 
-      <div className="filter-section">
-        <ul className="filter-list">
-          <li className="unread_filter">
-            <Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : ''} onClick={handleLinkClick}>
-              unread
-            </Link>
-          </li>
-          <li className="all_filter">
-            <Link to="/?filter=all" className={currentFilter === 'all' ? 'active' : ''} onClick={handleLinkClick}>
-              all
-            </Link>
-          </li>
-          <li className="starred_filter">
-            <Link to="/?filter=starred" className={currentFilter === 'starred' ? 'active' : ''} onClick={handleLinkClick}>
-              starred
-            </Link>
-          </li>
-        </ul>
-      </div>
- 
-      <div className="tag-section">
-        <h4 onClick={toggleTags} className="section-header">
-          <span className={`caret ${tagsExpanded ? 'expanded' : ''}`}>▶</span> Tags
-        </h4>
-        {tagsExpanded && (
-          <ul className="tag-list-items">
-            {tags.map((tag) => (
-              <li key={tag.title} className="tag-item">
-                <Link
-                  to={`/tag/${encodeURIComponent(tag.title)}`}
-                  className={`tag-link ${tagName === tag.title ? 'active' : ''}`}
-                  onClick={handleLinkClick}
-                >
-                  {tag.title}
-                </Link>
-              </li>
-            ))}
-          </ul>
-        )}
-      </div>
- 
-      <div className="feed-section">
-        <h4 onClick={toggleFeeds} className="section-header">
-          <span className={`caret ${feedsExpanded ? 'expanded' : ''}`}>▶</span> Feeds
-        </h4>
-        {feedsExpanded &&
-          (feeds.length === 0 ? (
-            <p>No feeds found.</p>
-          ) : (
-            <ul className="feed-list-items">
-              {feeds.map((feed) => (
-                <li key={feed._id} className="sidebar-feed-item">
-                  <Link
-                    to={`/feed/${feed._id}`}
-                    className={`feed-title ${feedId === String(feed._id) ? 'active' : ''}`}
-                    onClick={handleLinkClick}
-                  >
-                    {feed.title || feed.url}
-                  </Link>
-                </li>
-              ))}
-            </ul>
-          ))}
-      </div>
- 
-      <div className="nav-section">
-        <ul className="nav-list">
-          <li>
-            <Link to="/settings" className="nav-link" onClick={handleLinkClick}>
-              settings
-            </Link>
-          </li>
-          <li>
-            <button onClick={handleLogout} className="logout-link">
-              logout
-            </button>
-          </li>
-        </ul>
-      </div>
- 
-      <div className="theme-section">
-        <div className="theme-selector">
-          <button
-            onClick={() => setTheme('light')}
-            className={theme === 'light' ? 'active' : ''}
-            title="Light Theme"
-          >
-            ☀️
-          </button>
-          <button
-            onClick={() => setTheme('dark')}
-            className={theme === 'dark' ? 'active' : ''}
-            title="Dark Theme"
-          >
-            🌙
-          </button>
-        </div>
-      </div>
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/Login.css.html b/frontend/coverage/src/components/Login.css.html deleted file mode 100644 index 140a86b..0000000 --- a/frontend/coverage/src/components/Login.css.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - Code coverage report for src/components/Login.css - - - - - - - - - -
-
-

All files / src/components Login.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
.login-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 100vh;
-  background-color: #f5f5f5;
-}
- 
-.login-form {
-  background: white;
-  padding: 2rem;
-  border-radius: 8px;
-  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
-  width: 100%;
-  max-width: 400px;
-}
- 
-.login-form h1 {
-  margin-bottom: 2rem;
-  text-align: center;
-  color: #333;
-}
- 
-.form-group {
-  margin-bottom: 1.5rem;
-}
- 
-.form-group label {
-  display: block;
-  margin-bottom: 0.5rem;
-  font-weight: bold;
-  color: #555;
-}
- 
-.form-group input {
-  width: 100%;
-  padding: 0.75rem;
-  border: 1px solid #ddd;
-  border-radius: 4px;
-  font-size: 1rem;
-}
- 
-.error-message {
-  color: #dc3545;
-  margin-bottom: 1rem;
-  text-align: center;
-}
- 
-button[type='submit'] {
-  width: 100%;
-  padding: 0.75rem;
-  background-color: #007bff;
-  color: white;
-  border: none;
-  border-radius: 4px;
-  font-size: 1rem;
-  cursor: pointer;
-  transition: background-color 0.2s;
-}
- 
-button[type='submit']:hover {
-  background-color: #0056b3;
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/Login.tsx.html b/frontend/coverage/src/components/Login.tsx.html deleted file mode 100644 index 111dcba..0000000 --- a/frontend/coverage/src/components/Login.tsx.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - Code coverage report for src/components/Login.tsx - - - - - - - - - -
-
-

All files / src/components Login.tsx

-
- -
- 100% - Statements - 20/20 -
- - -
- 83.33% - Branches - 5/6 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 20/20 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68  -  -  -  -  -  -  -17x -17x -17x -17x -  -17x -3x -3x -  -3x -  -3x -3x -3x -  -3x -  -  -  -  -2x -1x -  -1x -1x -  -  -1x -  -  -  -17x -  -  -  -  -  -  -  -  -  -3x -  -  -  -  -  -  -  -  -3x -  -  -  -  -  -  -  -  -  - 
import { useState, type FormEvent } from 'react';
-import { useNavigate } from 'react-router-dom';
-import './Login.css';
- 
-import { apiFetch } from '../utils';
- 
-export default function Login() {
-  const [username, setUsername] = useState('neko');
-  const [password, setPassword] = useState('');
-  const [error, setError] = useState('');
-  const navigate = useNavigate();
- 
-  const handleSubmit = async (e: FormEvent) => {
-    e.preventDefault();
-    setError('');
- 
-    try {
-      // Use URLSearchParams to send as form-urlencoded, matching backend expectation
-      const params = new URLSearchParams();
-      params.append('username', username);
-      params.append('password', password);
- 
-      const res = await apiFetch('/api/login', {
-        method: 'POST',
-        body: params,
-      });
- 
-      if (res.ok) {
-        navigate('/');
-      } else {
-        const data = await res.json();
-        setError(data.message || 'Login failed');
-      }
-    } catch (_err) {
-      setError('Network error');
-    }
-  };
- 
-  return (
-    <div className="login-container">
-      <form onSubmit={handleSubmit} className="login-form">
-        <h1>neko rss mode</h1>
-        <div className="form-group">
-          <label htmlFor="username">username</label>
-          <input
-            id="username"
-            type="text"
-            value={username}
-            onChange={(e) => setUsername(e.target.value)}
-          />
-        </div>
-        <div className="form-group">
-          <label htmlFor="password">password</label>
-          <input
-            id="password"
-            type="password"
-            value={password}
-            onChange={(e) => setPassword(e.target.value)}
-            autoFocus
-          />
-        </div>
-        {error && <div className="error-message">{error}</div>}
-        <button type="submit">login</button>
-      </form>
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.css.html b/frontend/coverage/src/components/Settings.css.html deleted file mode 100644 index 4109bba..0000000 --- a/frontend/coverage/src/components/Settings.css.html +++ /dev/null @@ -1,802 +0,0 @@ - - - - - - Code coverage report for src/components/Settings.css - - - - - - - - - -
-
-

All files / src/components Settings.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
.settings-page.variant-glass {
-  padding: 2.5rem;
-  max-width: 800px;
-  margin: 0 auto;
-  background: rgba(255, 255, 255, 0.05);
-  backdrop-filter: blur(12px);
-  -webkit-backdrop-filter: blur(12px);
-  border-radius: 24px;
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  font-family: system-ui, -apple-system, sans-serif;
-  color: var(--text-color);
-  margin-top: 2rem;
-  margin-bottom: 2rem;
-}
- 
-.settings-page.variant-glass h2,
-.settings-page.variant-glass h3 {
-  font-weight: 700;
-  letter-spacing: -0.02em;
-  color: var(--text-color);
-  opacity: 0.9;
-}
- 
-.add-feed-section,
-.appearance-section,
-.import-section,
-.export-section,
-.feed-list-section {
-  background: rgba(255, 255, 255, 0.03);
-  padding: 1.5rem;
-  border-radius: 16px;
-  margin-bottom: 2rem;
-  border: 1px solid rgba(255, 255, 255, 0.05);
-  transition: all 0.3s ease;
-}
- 
-.add-feed-section:hover,
-.appearance-section:hover,
-.import-section:hover,
-.export-section:hover,
-.feed-list-section:hover {
-  background: rgba(255, 255, 255, 0.06);
-  border-color: rgba(255, 255, 255, 0.1);
-}
- 
-.font-selector {
-  display: flex;
-  align-items: center;
-  gap: 1rem;
-}
- 
-.font-select {
-  padding: 0.6rem 1rem;
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  background: rgba(0, 0, 0, 0.1);
-  color: var(--text-color);
-  border-radius: 20px;
-  font-size: 1rem;
-  min-width: 200px;
-  cursor: pointer;
-  outline: none;
-  transition: border-color 0.2s;
-}
- 
-.font-select:focus {
-  border-color: rgba(255, 255, 255, 0.3);
-}
- 
-.add-feed-form {
-  display: flex;
-  gap: 1rem;
-}
- 
-.feed-input {
-  flex: 1;
-  padding: 0.6rem 1.2rem;
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  background: rgba(0, 0, 0, 0.1);
-  color: var(--text-color);
-  border-radius: 20px;
-  font-size: 1rem;
-  outline: none;
-  transition: border-color 0.2s;
-}
- 
-.feed-input:focus {
-  border-color: rgba(255, 255, 255, 0.3);
-}
- 
-.error-message {
-  color: #ff5252;
-  margin-top: 1rem;
-  font-weight: 600;
-}
- 
-.settings-feed-list {
-  list-style: none;
-  padding: 0;
-  border: 1px solid rgba(255, 255, 255, 0.05);
-  border-radius: 12px;
-  overflow: hidden;
-}
- 
-.settings-feed-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 1.2rem;
-  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
-  transition: background 0.2s;
-}
- 
-.settings-feed-item:hover {
-  background: rgba(255, 255, 255, 0.02);
-}
- 
-.settings-feed-item:last-child {
-  border-bottom: none;
-}
- 
-.feed-info {
-  display: flex;
-  flex-direction: column;
-  gap: 0.2rem;
-}
- 
-.feed-title {
-  font-weight: 600;
-  font-size: 1.05rem;
-  opacity: 0.9;
-}
- 
-.feed-url {
-  color: var(--text-color);
-  opacity: 0.5;
-  font-size: 0.85rem;
-}
- 
-.delete-btn {
-  background: rgba(255, 82, 82, 0.15);
-  color: #ff8a80;
-  border: 1px solid rgba(255, 82, 82, 0.2);
-  padding: 0.5rem 1rem;
-  border-radius: 12px;
-  cursor: pointer;
-  font-weight: 600;
-  transition: all 0.2s;
-}
- 
-.delete-btn:hover:not(:disabled) {
-  background: rgba(255, 82, 82, 0.3);
-  color: #fff;
-  border-color: rgba(255, 82, 82, 0.4);
-  transform: scale(1.05);
-}
- 
-.import-export-section {
-  display: flex;
-  gap: 2rem;
-}
- 
-@media (max-width: 600px) {
-  .settings-page.variant-glass {
-    padding: 1.5rem;
-    margin-top: 1rem;
-  }
- 
-  .add-feed-form {
-    flex-direction: column;
-  }
- 
-  .import-export-section {
-    flex-direction: column;
-    gap: 1rem;
-  }
- 
-  .settings-feed-item {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 1rem;
-  }
-}
- 
-.import-form {
-  display: flex;
-  flex-direction: column;
-  gap: 1.2rem;
-}
- 
-.file-input {
-  font-size: 0.9rem;
-  max-width: 100%;
-  color: var(--text-color);
-  opacity: 0.8;
-}
- 
-.export-buttons {
-  display: flex;
-  gap: 0.8rem;
-  flex-wrap: wrap;
-}
- 
-.export-btn {
-  display: inline-block;
-  padding: 0.6rem 1.2rem;
-  background: rgba(255, 255, 255, 0.05);
-  color: var(--text-color);
-  text-decoration: none;
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  border-radius: 12px;
-  font-weight: 600;
-  transition: all 0.2s;
-}
- 
-.export-btn:hover {
-  background: rgba(255, 255, 255, 0.1);
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
- 
-button:not(.delete-btn) {
-  cursor: pointer;
-  padding: 0.6rem 1.2rem;
-  border-radius: 12px;
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  background: rgba(255, 255, 255, 0.1);
-  color: var(--text-color);
-  font-weight: 600;
-  transition: all 0.2s;
-}
- 
-button:not(.delete-btn):hover:not(:disabled) {
-  background: rgba(255, 255, 255, 0.2);
-  transform: scale(1.02);
-}
- 
-button:disabled {
-  opacity: 0.4;
-  cursor: not-allowed;
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.tsx.html b/frontend/coverage/src/components/Settings.tsx.html deleted file mode 100644 index 892218e..0000000 --- a/frontend/coverage/src/components/Settings.tsx.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - Code coverage report for src/components/Settings.tsx - - - - - - - - - -
-
-

All files / src/components Settings.tsx

-
- -
- 80% - Statements - 60/75 -
- - -
- 66.66% - Branches - 20/30 -
- - -
- 85.18% - Functions - 23/27 -
- - -
- 87.87% - Lines - 58/66 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237  -  -  -  -  -  -  -  -  -  -  -34x -  -34x -34x -34x -  -34x -  -  -34x -9x -9x -  -9x -9x -  -  -9x -9x -  -  -  -  -  -  -  -34x -  -7x -  -  -  -34x -2x -2x -  -2x -2x -  -  -  -  -  -2x -1x -  -  -1x -1x -  -  -1x -1x -  -  -  -34x -1x -  -1x -1x -  -  -  -1x -1x -1x -  -  -  -  -  -  -  -34x -1x -1x -  -1x -1x -1x -1x -  -1x -  -  -  -  -1x -1x -  -  -1x -1x -1x -  -  -  -  -  -  -  -34x -1x -1x -  -  -  -1x -1x -  -  -1x -1x -  -  -  -  -  -  -  -34x -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import React, { useEffect, useState } from 'react';
-import type { Feed } from '../types';
-import './Settings.css';
-import { apiFetch } from '../utils';
- 
-interface SettingsProps {
-  fontTheme?: string;
-  setFontTheme?: (t: string) => void;
-}
- 
-export default function Settings({ fontTheme, setFontTheme }: SettingsProps) {
-  const [feeds, setFeeds] = useState<Feed[]>([]);
-  /* ... existing state ... */
-  const [newFeedUrl, setNewFeedUrl] = useState('');
-  const [loading, setLoading] = useState(false);
-  const [error, setError] = useState<string | null>(null);
- 
-  const [importFile, setImportFile] = useState<File | null>(null);
- 
-  /* ... existing fetchFeeds ... */
-  const fetchFeeds = React.useCallback(() => {
-    setLoading(true);
-    apiFetch('/api/feed/')
-      .then((res) => {
-        Iif (!res.ok) throw new Error('Failed to fetch feeds');
-        return res.json();
-      })
-      .then((data) => {
-        setFeeds(data);
-        setLoading(false);
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  }, []);
- 
-  useEffect(() => {
-    // eslint-disable-next-line
-    fetchFeeds();
-  }, [fetchFeeds]);
- 
-  /* ... existing handlers ... */
-  const handleAddFeed = (e: React.FormEvent) => {
-    e.preventDefault();
-    Iif (!newFeedUrl) return;
- 
-    setLoading(true);
-    apiFetch('/api/feed/', {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ url: newFeedUrl }),
-    })
-      .then((res) => {
-        if (!res.ok) throw new Error('Failed to add feed');
-        return res.json();
-      })
-      .then(() => {
-        setNewFeedUrl('');
-        fetchFeeds();
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  };
- 
-  const handleDeleteFeed = (id: number) => {
-    Iif (!globalThis.confirm('Are you sure you want to delete this feed?')) return;
- 
-    setLoading(true);
-    apiFetch(`/api/feed/${id}`, {
-      method: 'DELETE',
-    })
-      .then((res) => {
-        Iif (!res.ok) throw new Error('Failed to delete feed');
-        setFeeds(feeds.filter((f) => f._id !== id));
-        setLoading(false);
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  };
- 
-  const handleImport = (e: React.FormEvent) => {
-    e.preventDefault();
-    Iif (!importFile) return;
- 
-    setLoading(true);
-    const formData = new FormData();
-    formData.append('file', importFile);
-    formData.append('format', 'opml');
- 
-    apiFetch('/api/import', {
-      method: 'POST',
-      body: formData,
-    })
-      .then((res) => {
-        Iif (!res.ok) throw new Error('Failed to import feeds');
-        return res.json();
-      })
-      .then(() => {
-        setImportFile(null);
-        fetchFeeds();
-        alert('Import successful!');
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  };
- 
-  const handleCrawl = () => {
-    setLoading(true);
-    apiFetch('/api/crawl', {
-      method: 'POST',
-    })
-      .then((res) => {
-        Iif (!res.ok) throw new Error('Failed to start crawl');
-        return res.json();
-      })
-      .then(() => {
-        setLoading(false);
-        alert('Crawl started!');
-      })
-      .catch((err) => {
-        setError(err.message);
-        setLoading(false);
-      });
-  };
- 
-  return (
-    <div className="settings-page variant-glass">
-      <h2>Settings</h2>
- 
-      {setFontTheme && (
-        <div className="appearance-section">
-          <h3>Appearance</h3>
-          <div className="font-selector">
-            <label htmlFor="font-theme-select">Font Theme:</label>
-            <select
-              id="font-theme-select"
-              value={fontTheme || 'default'}
-              onChange={(e) => setFontTheme(e.target.value)}
-              className="font-select"
-            >
-              <option value="default">Default</option>
-              <option value="serif">Serif</option>
-              <option value="sans">Sans-Serif</option>
-              <option value="mono">Monospace</option>
-            </select>
-          </div>
-        </div>
-      )}
- 
-      <div className="add-feed-section">
-        <h3>Add New Feed</h3>
-        <form onSubmit={handleAddFeed} className="add-feed-form">
-          <input
-            type="url"
-            value={newFeedUrl}
-            onChange={(e) => setNewFeedUrl(e.target.value)}
-            placeholder="https://example.com/feed.xml"
-            required
-            className="feed-input"
-            disabled={loading}
-          />
-          <button type="submit" disabled={loading}>
-            Add Feed
-          </button>
-        </form>
-      </div>
- 
-      <div className="import-export-section">
-        <div className="import-section">
-          <h3>Import Feeds (OPML)</h3>
-          <form onSubmit={handleImport} className="import-form">
-            <input
-              type="file"
-              accept=".opml,.xml,.txt"
-              aria-label="Import Feeds"
-              onChange={(e) => setImportFile(e.target.files?.[0] || null)}
-              className="file-input"
-              disabled={loading}
-            />
-            <button type="submit" disabled={!importFile || loading}>
-              Import
-            </button>
-          </form>
-        </div>
- 
-        <div className="export-section">
-          <h3>Export Feeds</h3>
-          <div className="export-buttons">
-            <a href="/api/export/opml" className="export-btn">OPML</a>
-            <a href="/api/export/text" className="export-btn">Text</a>
-            <a href="/api/export/json" className="export-btn">JSON</a>
-          </div>
-        </div>
- 
-        <div className="crawl-section">
-          <h3>Actions</h3>
-          <button onClick={handleCrawl} disabled={loading} className="crawl-btn">
-            Crawl All Feeds Now
-          </button>
-        </div>
-      </div>
- 
-      {error && <p className="error-message">{error}</p>}
- 
-      <div className="feed-list-section">
-        <h3>Manage Feeds</h3>
-        {loading && <p>Loading...</p>}
-        <ul className="settings-feed-list">
-          {feeds.map((feed) => (
-            <li key={feed._id} className="settings-feed-item">
-              <div className="feed-info">
-                <span className="feed-title">{feed.title || '(No Title)'}</span>
-                <span className="feed-url">{feed.url}</span>
-              </div>
-              <button
-                onClick={() => handleDeleteFeed(feed._id)}
-                className="delete-btn"
-                disabled={loading}
-                title="Delete Feed"
-              >
-                Delete
-              </button>
-            </li>
-          ))}
-        </ul>
-      </div>
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/index.html b/frontend/coverage/src/components/index.html deleted file mode 100644 index fc333bb..0000000 --- a/frontend/coverage/src/components/index.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - Code coverage report for src/components - - - - - - - - - -
-
-

All files src/components

-
- -
- 86.21% - Statements - 269/312 -
- - -
- 74.61% - Branches - 144/193 -
- - -
- 84.94% - Functions - 79/93 -
- - -
- 88.81% - Lines - 254/286 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
FeedItem.css -
-
0%0/00%0/00%0/00%0/0
FeedItem.tsx -
-
78.12%25/3286.95%20/2383.33%10/1280.64%25/31
FeedItems.css -
-
0%0/00%0/00%0/00%0/0
FeedItems.tsx -
-
89.23%116/13076.19%64/8487.09%27/3189.07%106/119
FeedList.css -
-
0%0/00%0/00%0/00%0/0
FeedList.tsx -
-
87.27%48/5570%35/5078.94%15/1990%45/50
FeedListVariants.css -
-
0%0/00%0/00%0/00%0/0
Login.css -
-
0%0/00%0/00%0/00%0/0
Login.tsx -
-
100%20/2083.33%5/6100%4/4100%20/20
Settings.css -
-
0%0/00%0/00%0/00%0/0
Settings.tsx -
-
80%60/7566.66%20/3085.18%23/2787.87%58/66
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html deleted file mode 100644 index 091ffa0..0000000 --- a/frontend/coverage/src/index.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - Code coverage report for src - - - - - - - - - -
-
-

All files src

-
- -
- 70.21% - Statements - 33/47 -
- - -
- 65.71% - Branches - 23/35 -
- - -
- 60% - Functions - 9/15 -
- - -
- 71.11% - Lines - 32/45 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
App.css -
-
0%0/00%0/00%0/00%0/0
App.tsx -
-
65.71%23/3560%15/2553.84%7/1364.7%22/34
utils.ts -
-
83.33%10/1280%8/10100%2/290.9%10/11
-
-
-
- - - - - - - - \ No newline at end of file -- cgit v1.2.3