diff options
Diffstat (limited to 'frontend/coverage/src')
| -rw-r--r-- | frontend/coverage/src/App.css.html | 273 | ||||
| -rw-r--r-- | frontend/coverage/src/App.tsx.html | 205 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedItem.css.html | 298 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedItem.tsx.html | 380 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedItems.css.html | 183 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedItems.tsx.html | 597 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedList.css.html | 495 | ||||
| -rw-r--r-- | frontend/coverage/src/components/FeedList.tsx.html | 620 | ||||
| -rw-r--r-- | frontend/coverage/src/components/Login.css.html | 137 | ||||
| -rw-r--r-- | frontend/coverage/src/components/Login.tsx.html | 225 | ||||
| -rw-r--r-- | frontend/coverage/src/components/Settings.css.html | 453 | ||||
| -rw-r--r-- | frontend/coverage/src/components/Settings.tsx.html | 524 | ||||
| -rw-r--r-- | frontend/coverage/src/components/index.html | 504 | ||||
| -rw-r--r-- | frontend/coverage/src/index.html | 251 |
14 files changed, 3081 insertions, 2064 deletions
diff --git a/frontend/coverage/src/App.css.html b/frontend/coverage/src/App.css.html index a5aa753..5fca2a7 100644 --- a/frontend/coverage/src/App.css.html +++ b/frontend/coverage/src/App.css.html @@ -1,61 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/App.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../prettify.css" /> <link rel="stylesheet" href="../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> App.css</h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -128,69 +135,7 @@ <a name='L70'></a><a href='#L70'>70</a> <a name='L71'></a><a href='#L71'>71</a> <a name='L72'></a><a href='#L72'>72</a> -<a name='L73'></a><a href='#L73'>73</a> -<a name='L74'></a><a href='#L74'>74</a> -<a name='L75'></a><a href='#L75'>75</a> -<a name='L76'></a><a href='#L76'>76</a> -<a name='L77'></a><a href='#L77'>77</a> -<a name='L78'></a><a href='#L78'>78</a> -<a name='L79'></a><a href='#L79'>79</a> -<a name='L80'></a><a href='#L80'>80</a> -<a name='L81'></a><a href='#L81'>81</a> -<a name='L82'></a><a href='#L82'>82</a> -<a name='L83'></a><a href='#L83'>83</a> -<a name='L84'></a><a href='#L84'>84</a> -<a name='L85'></a><a href='#L85'>85</a> -<a name='L86'></a><a href='#L86'>86</a> -<a name='L87'></a><a href='#L87'>87</a> -<a name='L88'></a><a href='#L88'>88</a> -<a name='L89'></a><a href='#L89'>89</a> -<a name='L90'></a><a href='#L90'>90</a> -<a name='L91'></a><a href='#L91'>91</a> -<a name='L92'></a><a href='#L92'>92</a> -<a name='L93'></a><a href='#L93'>93</a> -<a name='L94'></a><a href='#L94'>94</a> -<a name='L95'></a><a href='#L95'>95</a> -<a name='L96'></a><a href='#L96'>96</a> -<a name='L97'></a><a href='#L97'>97</a> -<a name='L98'></a><a href='#L98'>98</a> -<a name='L99'></a><a href='#L99'>99</a> -<a name='L100'></a><a href='#L100'>100</a> -<a name='L101'></a><a href='#L101'>101</a> -<a name='L102'></a><a href='#L102'>102</a> -<a name='L103'></a><a href='#L103'>103</a> -<a name='L104'></a><a href='#L104'>104</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> +<a name='L73'></a><a href='#L73'>73</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -269,8 +214,6 @@ body { margin: 0; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } /* Dashboard Layout */ @@ -282,60 +225,28 @@ body { /* Prevent body scroll */ } -.dashboard-header { - /* Legacy didn't really have a top header, but we need one for settings/logout. - Keeping it minimal/flat or matching sidebar bg if we want to blend in. - For now, let's make it dark gray to stand out less or match legacy dark mode if applicable. - Actually, let's keep it distinct but apply the font styles. */ - background: #222; - color: white; - padding: 0.5rem 1rem; - display: flex; - justify-content: space-between; - align-items: center; - font-weight: bold; -} - -.dashboard-header h1 { - margin: 0; - font-size: 1.2rem; - font-variant: small-caps; - text-transform: lowercase; -} - -.nav-link, -.logout-btn { - font-weight: bold; - font-variant: small-caps; - text-transform: lowercase; - font-size: 1rem; - background: transparent; - border: none; - color: #ccc; - cursor: pointer; - margin-left: 1rem; -} - -.nav-link:hover, -.logout-btn:hover { - color: white; - text-decoration: underline; -} +/* Header styles removed as we moved to sidebar navigation */ .dashboard-content { display: flex; flex: 1; overflow: hidden; + position: relative; } .dashboard-sidebar { - width: 15rem; + width: 11rem; background: var(--sidebar-bg); - border-right: 1px solid #999; + border-right: 1px solid var(--border-color); display: flex; flex-direction: column; overflow-y: auto; - padding: 1rem; + transition: margin-left 0.4s ease; + /* No padding here, handled in FeedList */ +} + +.dashboard-sidebar.hidden { + margin-left: -11rem; } .dashboard-main { @@ -347,42 +258,44 @@ body { } .dashboard-main>* { - max-width: 600px; - margin: 0; + max-width: 35em; + margin: 0 auto; } -.logout-btn { +.fixed-toggle { + position: absolute; + top: 1rem; + left: 1rem; + z-index: 1000; background: transparent; - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 0.5rem 1rem; - border-radius: 4px; + border: none; + font-size: 2rem; + line-height: 1; cursor: pointer; - transition: all 0.2s; - font-size: 0.9rem; + padding: 0; + color: var(--text-color); + /* Inherit didn't work well if parent is transparent */ } -.logout-btn:hover { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.5); +.fixed-toggle:hover { + transform: scale(1.1); }</pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../sorter.js"></script> - <script src="../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../sorter.js"></script> + <script src="../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html index ed02366..cd305cc 100644 --- a/frontend/coverage/src/App.tsx.html +++ b/frontend/coverage/src/App.tsx.html @@ -1,61 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/App.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../prettify.css" /> <link rel="stylesheet" href="../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> <h1><a href="../index.html">All files</a> / <a href="index.html">src</a> App.tsx</h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Statements</span> - <span class="fraction">15/19</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">66.66% </span> - <span class="quiet">Branches</span> - <span class="fraction">4/6</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">77.77% </span> - <span class="quiet">Functions</span> - <span class="fraction">7/9</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Lines</span> - <span class="fraction">15/19</span> - </div> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">72.72% </span> + <span class="quiet">Statements</span> + <span class='fraction'>16/22</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">62.5% </span> + <span class="quiet">Branches</span> + <span class='fraction'>10/16</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">62.5% </span> + <span class="quiet">Functions</span> + <span class='fraction'>5/8</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">72.72% </span> + <span class="quiet">Lines</span> + <span class='fraction'>16/22</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line medium"></div> - <pre><table class="coverage"> + </div> + <div class='status-line medium'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -146,7 +153,17 @@ <a name='L88'></a><a href='#L88'>88</a> <a name='L89'></a><a href='#L89'>89</a> <a name='L90'></a><a href='#L90'>90</a> -<a name='L91'></a><a href='#L91'>91</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L91'></a><a href='#L91'>91</a> +<a name='L92'></a><a href='#L92'>92</a> +<a name='L93'></a><a href='#L93'>93</a> +<a name='L94'></a><a href='#L94'>94</a> +<a name='L95'></a><a href='#L95'>95</a> +<a name='L96'></a><a href='#L96'>96</a> +<a name='L97'></a><a href='#L97'>97</a> +<a name='L98'></a><a href='#L98'>98</a> +<a name='L99'></a><a href='#L99'>99</a> +<a name='L100'></a><a href='#L100'>100</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -185,16 +202,18 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">1x</span> -<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -215,8 +234,14 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> @@ -237,9 +262,10 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import React, { useEffect, useState } from 'react'; -import { BrowserRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom'; +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 }) { @@ -247,7 +273,7 @@ function RequireAuth({ children }: { children: React.ReactElement }) { const location = useLocation(); useEffect(() => { - fetch('/api/auth') + apiFetch('/api/auth') .then((res) => { if (res.ok) { setAuth(true); @@ -273,26 +299,25 @@ import FeedList from './components/FeedList'; import FeedItems from './components/FeedItems'; import Settings from './components/Settings'; -function Dashboard() { - const navigate = useNavigate(); - return ( - <div className="dashboard"> - <header className="dashboard-header"> - <h1>Neko Reader</h1> - <nav> - <button onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >n</span>avigate('/settings')} c</span>lassName="nav-link" style={{ color: 'white', marginRight: '1rem', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'inherit', fontFamily: 'inherit' }}>Settings</button> +function Dashboard({ theme, setTheme }: { theme: string; setTheme: (t: string) => void }) { + const [sidebarVisible, setSidebarVisible] = useState(true); - <button onClick={() => { - fetch('/api/logout', { method: 'POST' }) - .then(() => window.location.href = '/v2/login'); - }} className="logout-btn"> - Logout - </button> - </nav> - </header> + return ( + <div + className={`dashboard ${sidebarVisible ? 'sidebar-visible' : <span class="branch-1 cbranch-no" title="branch not covered" >'sidebar-hidden'}</span> theme-${theme}`} + > <div className="dashboard-content"> - <aside className="dashboard-sidebar"> - <FeedList /> + {!sidebarVisible && ( +<span class="branch-1 cbranch-no" title="branch not covered" > <button</span> + className="sidebar-toggle fixed-toggle" + onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etSidebarVisible(true)}</span> + title="Show Sidebar" + > + 🐱 + </button> + )} + <aside className={`dashboard-sidebar ${sidebarVisible ? '' : <span class="branch-1 cbranch-no" title="branch not covered" >'hidden'}</span>`}> + <FeedList theme={theme} setTheme={setTheme} setSidebarVisible={setSidebarVisible} /> </aside> <main className="dashboard-main"> <Routes> @@ -308,15 +333,24 @@ function Dashboard() { } function App() { + const [theme, setTheme] = useState(localStorage.getItem('neko-theme') || 'light'); + + const handleSetTheme = <span class="fstat-no" title="function not covered" >(n</span>ewTheme: string) => { +<span class="cstat-no" title="statement not covered" > setTheme(newTheme);</span> +<span class="cstat-no" title="statement not covered" > localStorage.setItem('neko-theme', newTheme);</span> + }; + + const basename = window.location.pathname.startsWith('/v2') ? '/v2' : <span class="branch-1 cbranch-no" title="branch not covered" >'/';</span> + return ( - <BrowserRouter basename="/v2"> + <BrowserRouter basename={basename}> <Routes> <Route path="/login" element={<Login />} /> <Route path="/*" element={ <RequireAuth> - <Dashboard /> + <Dashboard theme={theme} setTheme={handleSetTheme} /> </RequireAuth> } /> @@ -328,22 +362,21 @@ function App() { export default App; </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../sorter.js"></script> - <script src="../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../sorter.js"></script> + <script src="../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.css.html b/frontend/coverage/src/components/FeedItem.css.html index f6fe1a3..213077f 100644 --- a/frontend/coverage/src/components/FeedItem.css.html +++ b/frontend/coverage/src/components/FeedItem.css.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedItem.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedItem.css - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItem.css</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -175,7 +179,25 @@ <a name='L114'></a><a href='#L114'>114</a> <a name='L115'></a><a href='#L115'>115</a> <a name='L116'></a><a href='#L116'>116</a> -<a name='L117'></a><a href='#L117'>117</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L117'></a><a href='#L117'>117</a> +<a name='L118'></a><a href='#L118'>118</a> +<a name='L119'></a><a href='#L119'>119</a> +<a name='L120'></a><a href='#L120'>120</a> +<a name='L121'></a><a href='#L121'>121</a> +<a name='L122'></a><a href='#L122'>122</a> +<a name='L123'></a><a href='#L123'>123</a> +<a name='L124'></a><a href='#L124'>124</a> +<a name='L125'></a><a href='#L125'>125</a> +<a name='L126'></a><a href='#L126'>126</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -292,139 +314,147 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">.feed-item { - padding: 1rem; - margin-top: 5rem; - list-style: none; - border-bottom: none; + padding: 1rem; + margin-top: 5rem; + list-style: none; + border-bottom: none; } -.feed-item.read .item-title { - color: #888; - font-weight: normal; -} - -.feed-item.unread .item-title { - font-weight: bold; -} +/* 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; + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.5rem; } .item-title { - font-size: 1.25rem; - font-weight: bold; - /* Legacy headers were bold */ - text-decoration: none; - color: #333; - display: block; - flex: 1; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + 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: blue; - /* Legacy link color */ + text-decoration: none; + color: var(--link-color); } .item-actions { - display: flex; - gap: 0.5rem; - margin-left: 1rem; + 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.2rem; - padding: 0 0.5rem 0 0; - vertical-align: middle; - transition: color 0.2s; - line-height: 1; + 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: #ffd700; - /* Gold */ + color: blue; } .star-btn.is-unstarred { - color: #ccc; + color: var(--text-color); + opacity: 0.3; } .star-btn:hover { - color: #ffeb3b; + color: blue; } .action-btn { - background: whitesmoke; - border: none; - cursor: pointer; - padding: 2px 6px; - font-size: 1rem; - color: blue; - font-weight: bold; + 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; + background-color: #eee; } .dateline { - margin-top: 0; - font-weight: normal; - font-size: .75em; - color: #ccc; - margin-bottom: 1rem; + margin-top: 0; + font-weight: normal; + font-size: 0.75em; + color: #ccc; + margin-bottom: 1rem; } .dateline a { - color: #ccc; - text-decoration: none; + color: #ccc; + text-decoration: none; } .item-description { - color: #000; - line-height: 1.5; - font-size: 1rem; - margin-top: 1rem; + 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; + max-width: 100%; + height: auto; + display: block; + margin: 1rem 0; } .item-description blockquote { - padding: 1rem 1rem 0 1rem; - border-left: 4px solid #ddd; - color: #666; - margin-left: 0; + 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: 'Helvetica Neue'; + font-weight: bold; + font-size: 0.8rem; + padding: 2px 6px; + margin-left: 0.5rem; +} + +.scrape-btn:hover { + background: var(--sidebar-bg); }</pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.tsx.html b/frontend/coverage/src/components/FeedItem.tsx.html index 5512b78..6e76131 100644 --- a/frontend/coverage/src/components/FeedItem.tsx.html +++ b/frontend/coverage/src/components/FeedItem.tsx.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedItem.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedItem.tsx - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Statements</span> - <span class="fraction">15/19</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">88.88% </span> - <span class="quiet">Branches</span> - <span class="fraction">16/18</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">85.71% </span> - <span class="quiet">Functions</span> - <span class="fraction">6/7</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Lines</span> - <span class="fraction">15/19</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItem.tsx</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">78.12% </span> + <span class="quiet">Statements</span> + <span class='fraction'>25/32</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">86.95% </span> + <span class="quiet">Branches</span> + <span class='fraction'>20/23</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">83.33% </span> + <span class="quiet">Functions</span> + <span class='fraction'>10/12</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">80.64% </span> + <span class="quiet">Lines</span> + <span class='fraction'>25/31</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line medium"></div> - <pre><table class="coverage"> + </div> + <div class='status-line medium'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -143,7 +147,41 @@ <a name='L82'></a><a href='#L82'>82</a> <a name='L83'></a><a href='#L83'>83</a> <a name='L84'></a><a href='#L84'>84</a> -<a name='L85'></a><a href='#L85'>85</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L85'></a><a href='#L85'>85</a> +<a name='L86'></a><a href='#L86'>86</a> +<a name='L87'></a><a href='#L87'>87</a> +<a name='L88'></a><a href='#L88'>88</a> +<a name='L89'></a><a href='#L89'>89</a> +<a name='L90'></a><a href='#L90'>90</a> +<a name='L91'></a><a href='#L91'>91</a> +<a name='L92'></a><a href='#L92'>92</a> +<a name='L93'></a><a href='#L93'>93</a> +<a name='L94'></a><a href='#L94'>94</a> +<a name='L95'></a><a href='#L95'>95</a> +<a name='L96'></a><a href='#L96'>96</a> +<a name='L97'></a><a href='#L97'>97</a> +<a name='L98'></a><a href='#L98'>98</a> +<a name='L99'></a><a href='#L99'>99</a> +<a name='L100'></a><a href='#L100'>100</a> +<a name='L101'></a><a href='#L101'>101</a> +<a name='L102'></a><a href='#L102'>102</a> +<a name='L103'></a><a href='#L103'>103</a> +<a name='L104'></a><a href='#L104'>104</a> +<a name='L105'></a><a href='#L105'>105</a> +<a name='L106'></a><a href='#L106'>106</a> +<a name='L107'></a><a href='#L107'>107</a> +<a name='L108'></a><a href='#L108'>108</a> +<a name='L109'></a><a href='#L109'>109</a> +<a name='L110'></a><a href='#L110'>110</a> +<a name='L111'></a><a href='#L111'>111</a> +<a name='L112'></a><a href='#L112'>112</a> +<a name='L113'></a><a href='#L113'>113</a> +<a name='L114'></a><a href='#L114'>114</a> +<a name='L115'></a><a href='#L115'>115</a> +<a name='L116'></a><a href='#L116'>116</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -151,16 +189,18 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">33x</span> +<span class="cline-any cline-yes">33x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">21x</span> -<span class="cline-any cline-yes">21x</span> +<span class="cline-any cline-yes">33x</span> +<span class="cline-any cline-yes">16x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">21x</span> +<span class="cline-any cline-yes">33x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">21x</span> +<span class="cline-any cline-yes">33x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">1x</span> @@ -196,7 +236,28 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">21x</span> +<span class="cline-any cline-yes">33x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">33x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -227,108 +288,143 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { useState } from 'react'; +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { useState, useEffect } from 'react'; import type { Item } from '../types'; import './FeedItem.css'; +import { apiFetch } from '../utils'; + interface FeedItemProps { - item: Item; + item: Item; } export default function FeedItem({ item: initialItem }: FeedItemProps) { - const [item, setItem] = useState(initialItem); - const [loading, setLoading] = useState(false); + const [item, setItem] = useState(initialItem); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setItem(initialItem); + }, [initialItem]); + const toggleStar = () => { + updateItem({ ...item, starred: !item.starred }); + }; - const toggleStar = () => { - updateItem({ ...item, starred: !item.starred }); - }; + const updateItem = (newItem: Item) => { + setLoading(true); + // Optimistic update + const previousItem = item; + setItem(newItem); - 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) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) { +<span class="cstat-no" title="statement not covered" > throw new Error('Failed to update item');</span> + } + 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(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > console.error('Error updating item:', err);</span> + // Revert on error +<span class="cstat-no" title="statement not covered" > setItem(previousItem);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; - fetch(`/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) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) { -<span class="cstat-no" title="statement not covered" > throw new Error('Failed to update item');</span> - } - 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(<span class="fstat-no" title="function not covered" >(e</span>rr) => { -<span class="cstat-no" title="statement not covered" > console.error('Error updating item:', err);</span> - // Revert on error -<span class="cstat-no" title="statement not covered" > setItem(previousItem);</span> -<span class="cstat-no" title="statement not covered" > setLoading(false);</span> - }); - }; + const loadFullContent = (e: React.MouseEvent) => { + e.stopPropagation(); + setLoading(true); + apiFetch(`/api/item/${item._id}`) + .then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch full content');</span> + return res.json(); + }) + .then((data) => { + setItem({ ...item, ...data }); + setLoading(false); + }) + .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > console.error('Error fetching full content:', err);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; - return ( - <li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}> - <div className="item-header"> - <button - onClick={(e) => { - e.stopPropagation(); - toggleStar(); - }} - className={`star-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`} - title={item.starred ? "Unstar" : "Star"} - > - {item.starred ? '★' : '☆'} - </button> - <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title"> - {item.title || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}</span> - </a> - </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' }}> - </div> - </div> - {item.description && ( - <div className="item-description" dangerouslySetInnerHTML={{ __html: item.description }} /> - )} - </li> - ); + 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 || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}</span> + </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> + ); } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.css.html b/frontend/coverage/src/components/FeedItems.css.html index 0b1c77d..7a971c6 100644 --- a/frontend/coverage/src/components/FeedItems.css.html +++ b/frontend/coverage/src/components/FeedItems.css.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedItems.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedItems.css - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItems.css</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -81,23 +85,7 @@ <a name='L20'></a><a href='#L20'>20</a> <a name='L21'></a><a href='#L21'>21</a> <a name='L22'></a><a href='#L22'>22</a> -<a name='L23'></a><a href='#L23'>23</a> -<a name='L24'></a><a href='#L24'>24</a> -<a name='L25'></a><a href='#L25'>25</a> -<a name='L26'></a><a href='#L26'>26</a> -<a name='L27'></a><a href='#L27'>27</a> -<a name='L28'></a><a href='#L28'>28</a> -<a name='L29'></a><a href='#L29'>29</a> -<a name='L30'></a><a href='#L30'>30</a> -<a name='L31'></a><a href='#L31'>31</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> +<a name='L23'></a><a href='#L23'>23</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -120,53 +108,44 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">.feed-items { - padding: 1rem; + padding: 1rem 0; + /* Removing horizontal padding to avoid double-padding with FeedItem */ } .feed-items h2 { - margin-top: 0; - border-bottom: 2px solid #eee; - padding-bottom: 0.5rem; + margin-top: 0; + border-bottom: 2px solid var(--border-color); + padding-bottom: 0.5rem; } .item-list { - list-style: none; - padding: 0; -} - -.selected-item-container { - border-left: 4px solid #007bff; - background-color: #f8f9fa; - padding-left: 0.5rem; - margin-left: -0.5rem; - /* Compensate for padding/border to keep alignment */ - transition: background-color 0.2s; + list-style: none; + padding: 0; } .loading-more { - padding: 2rem; - text-align: center; - color: #888; - font-size: 0.9rem; - min-height: 50px; + padding: 2rem; + text-align: center; + color: #888; + font-size: 0.9rem; + min-height: 50px; }</pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.tsx.html b/frontend/coverage/src/components/FeedItems.tsx.html index e57acf9..f6b7493 100644 --- a/frontend/coverage/src/components/FeedItems.tsx.html +++ b/frontend/coverage/src/components/FeedItems.tsx.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedItems.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedItems.tsx - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">89.34% </span> - <span class="quiet">Statements</span> - <span class="fraction">109/122</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">77.21% </span> - <span class="quiet">Branches</span> - <span class="fraction">61/79</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">86.2% </span> - <span class="quiet">Functions</span> - <span class="fraction">25/29</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">89.09% </span> - <span class="quiet">Lines</span> - <span class="fraction">98/110</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItems.tsx</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">88.97% </span> + <span class="quiet">Statements</span> + <span class='fraction'>113/127</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">75.3% </span> + <span class="quiet">Branches</span> + <span class='fraction'>61/81</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">86.2% </span> + <span class="quiet">Functions</span> + <span class='fraction'>25/29</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">88.69% </span> + <span class="quiet">Lines</span> + <span class='fraction'>102/115</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line high"></div> - <pre><table class="coverage"> + </div> + <div class='status-line high'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -282,7 +286,18 @@ <a name='L221'></a><a href='#L221'>221</a> <a name='L222'></a><a href='#L222'>222</a> <a name='L223'></a><a href='#L223'>223</a> -<a name='L224'></a><a href='#L224'>224</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L224'></a><a href='#L224'>224</a> +<a name='L225'></a><a href='#L225'>225</a> +<a name='L226'></a><a href='#L226'>226</a> +<a name='L227'></a><a href='#L227'>227</a> +<a name='L228'></a><a href='#L228'>228</a> +<a name='L229'></a><a href='#L229'>229</a> +<a name='L230'></a><a href='#L230'>230</a> +<a name='L231'></a><a href='#L231'>231</a> +<a name='L232'></a><a href='#L232'>232</a> +<a name='L233'></a><a href='#L233'>233</a> +<a name='L234'></a><a href='#L234'>234</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -298,6 +313,7 @@ <span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">27x</span> +<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">8x</span> @@ -323,6 +339,11 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">8x</span> +<span class="cline-any cline-yes">8x</span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">8x</span> <span class="cline-any cline-no"> </span> <span class="cline-any cline-yes">8x</span> <span class="cline-any cline-no"> </span> @@ -330,6 +351,8 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">8x</span> +<span class="cline-any cline-yes">8x</span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">8x</span> @@ -363,77 +386,79 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">7x</span> +<span class="cline-any cline-yes">7x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">27x</span> -<span class="cline-any cline-yes">23x</span> -<span class="cline-any cline-yes">3x</span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">3x</span> -<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span> -<span class="cline-any cline-yes">2x</span> -<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">3x</span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">1x</span> -<span class="cline-any cline-no"> </span> -<span class="cline-any cline-no"> </span> -<span class="cline-any cline-no"> </span> -<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">1x</span> -<span class="cline-any cline-yes">1x</span> -<span class="cline-any cline-yes">1x</span> -<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">23x</span> -<span class="cline-any cline-yes">23x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">27x</span> +<span class="cline-any cline-yes">23x</span> +<span class="cline-any cline-yes">3x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">3x</span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-yes">2x</span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">2x</span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">3x</span> +<span class="cline-any cline-yes">2x</span> +<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">2x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">23x</span> +<span class="cline-any cline-yes">23x</span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-no"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">27x</span> @@ -477,7 +502,6 @@ <span class="cline-any cline-yes">27x</span> <span class="cline-any cline-yes">14x</span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">13x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -510,242 +534,251 @@ 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 { 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 [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(''); + const fetchItems = (maxId?: string) => { + if (maxId) { + setLoadingMore(true); + } else { + setLoading(true); + setItems([]); + } + setError(''); - let url = '/api/stream'; - const params = new URLSearchParams(); + let url = '/api/stream'; + const params = new URLSearchParams(); - if (feedId) { - params.append('feed_id', feedId); - } else if (tagName) { - params.append('tag', tagName); - } + if (feedId) { + params.append('feed_id', feedId); + } else if (tagName) { + params.append('tag', tagName); + } - if (maxId) { - params.append('max_id', maxId); - } + if (maxId) { + params.append('max_id', maxId); + } + + // Apply filters + const searchQuery = searchParams.get('q'); + <span class="missing-if-branch" title="if path not taken" >I</span>if (searchQuery) { +<span class="cstat-no" title="statement not covered" > params.append('q', searchQuery);</span> + } + + <span class="missing-if-branch" title="if path not taken" >I</span>if (filterFn === 'all') { +<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span> + <span class="missing-if-branch" title="if path not taken" >I</span>} else if (filterFn === 'starred') { +<span class="cstat-no" title="statement not covered" > params.append('starred', 'true');</span> +<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span> + } else { + // default to unread + <span class="missing-if-branch" title="else path not taken" >E</span>if (!searchQuery) { + params.append('read_filter', 'unread'); + } + } + + const queryString = params.toString(); + <span class="missing-if-branch" title="else path not taken" >E</span>if (queryString) { + url += `?${queryString}`; + } - // Apply filters - <span class="missing-if-branch" title="if path not taken" >I</span>if (filterFn === 'all') { -<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span> - <span class="missing-if-branch" title="if path not taken" >I</span>} else if (filterFn === 'starred') { -<span class="cstat-no" title="statement not covered" > params.append('starred', 'true');</span> -<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span> + apiFetch(url) + .then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) { +<span class="cstat-no" title="statement not covered" > throw new Error('Failed to fetch items');</span> + } + return res.json(); + }) + .then((data) => { + if (maxId) { + setItems((prev) => [...prev, ...data]); } else { - // default to unread - params.append('read_filter', 'unread'); + setItems(data); } + setHasMore(data.length > 0); + setLoading(false); + setLoadingMore(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + setLoadingMore(false); + }); + }; - const queryString = params.toString(); - <span class="missing-if-branch" title="else path not taken" >E</span>if (queryString) { - url += `?${queryString}`; - } + useEffect(() => { + fetchItems(); + setSelectedIndex(-1); + }, [feedId, tagName, filterFn, searchParams]); - fetch(url) - .then((res) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) { -<span class="cstat-no" title="statement not covered" > throw new Error('Failed to fetch items');</span> - } - 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(); - }, [feedId, tagName, filterFn]); + const scrollToItem = (index: number) => { + const element = document.getElementById(`item-${index}`); + <span class="missing-if-branch" title="else path not taken" >E</span>if (element) { + element.scrollIntoView({ behavior: 'auto', block: 'start' }); + } + }; - const [selectedIndex, setSelectedIndex] = useState(-1); + const markAsRead = (item: Item) => { + const updatedItem = { ...item, read: true }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (items.length === 0) <span class="cstat-no" title="statement not covered" >return;</span> + apiFetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: true, starred: item.starred }), + }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => <span class="cstat-no" title="statement not covered" >console.error('Failed to mark read', err))</span>; + }; - if (e.key === 'j') { - setSelectedIndex((prev) => { - const nextIndex = Math.min(prev + 1, items.length - 1); - <span class="missing-if-branch" title="else path not taken" >E</span>if (nextIndex !== prev) { - const item = items[nextIndex]; - if (!item.read) { - markAsRead(item); - } - scrollToItem(nextIndex); - } - return nextIndex; - }); - <span class="missing-if-branch" title="if path not taken" >I</span>} else if (e.key === 'k') { -<span class="cstat-no" title="statement not covered" > setSelectedIndex(<span class="fstat-no" title="function not covered" >(p</span>rev) => {</span> - const nextIndex = <span class="cstat-no" title="statement not covered" >Math.max(prev - 1, 0);</span> -<span class="cstat-no" title="statement not covered" > if (nextIndex !== prev) {</span> -<span class="cstat-no" title="statement not covered" > scrollToItem(nextIndex);</span> - } -<span class="cstat-no" title="statement not covered" > return nextIndex;</span> - }); - <span class="missing-if-branch" title="else path not taken" >E</span>} else if (e.key === 's') { - setSelectedIndex((currentIndex) => { - <span class="missing-if-branch" title="else path not taken" >E</span>if (currentIndex >= 0 && currentIndex < items.length) { - toggleStar(items[currentIndex]); - } - return currentIndex; - }); - } - }; + const toggleStar = (item: Item) => { + const updatedItem = { ...item, starred: !item.starred }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [items]); + apiFetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: item.read, starred: !item.starred }), + }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => <span class="cstat-no" title="statement not covered" >console.error('Failed to toggle star', err))</span>; + }; - const scrollToItem = (index: number) => { - const element = document.getElementById(`item-${index}`); - <span class="missing-if-branch" title="else path not taken" >E</span>if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }; + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (items.length === 0) <span class="cstat-no" title="statement not covered" >return;</span> - const markAsRead = (item: Item) => { - const updatedItem = { ...item, read: true }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); - - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: true, starred: item.starred }), - }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => <span class="cstat-no" title="statement not covered" >console.error('Failed to mark read', err))</span>; + if (e.key === 'j') { + setSelectedIndex((prev) => { + const nextIndex = Math.min(prev + 1, items.length - 1); + <span class="missing-if-branch" title="else path not taken" >E</span>if (nextIndex !== prev) { + const item = items[nextIndex]; + if (!item.read) { + markAsRead(item); + } + scrollToItem(nextIndex); + } + return nextIndex; + }); + <span class="missing-if-branch" title="if path not taken" >I</span>} else if (e.key === 'k') { +<span class="cstat-no" title="statement not covered" > setSelectedIndex(<span class="fstat-no" title="function not covered" >(p</span>rev) => {</span> + const nextIndex = <span class="cstat-no" title="statement not covered" >Math.max(prev - 1, 0);</span> +<span class="cstat-no" title="statement not covered" > if (nextIndex !== prev) {</span> +<span class="cstat-no" title="statement not covered" > scrollToItem(nextIndex);</span> + } +<span class="cstat-no" title="statement not covered" > return nextIndex;</span> + }); + <span class="missing-if-branch" title="else path not taken" >E</span>} else if (e.key === 's') { + setSelectedIndex((currentIndex) => { + <span class="missing-if-branch" title="else path not taken" >E</span>if (currentIndex >= 0 && currentIndex < items.length) { + toggleStar(items[currentIndex]); + } + return currentIndex; + }); + } }; - const toggleStar = (item: Item) => { - const updatedItem = { ...item, starred: !item.starred }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [items]); - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: item.read, starred: !item.starred }), - }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => <span class="cstat-no" title="statement not covered" >console.error('Failed to toggle star', err))</span>; - }; - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - // Infinity scroll sentinel - if (entry.target.id === 'load-more-sentinel') { - <span class="missing-if-branch" title="else path not taken" >E</span>if (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { - fetchItems(String(items[items.length - 1]._id)); - } - return; - } - // If item is not intersecting and is above the viewport, it's been scrolled past - <span class="missing-if-branch" title="else path not taken" >E</span>if (!entry.isIntersecting && entry.boundingClientRect.top < 0) { - const index = Number(entry.target.getAttribute('data-index')); - <span class="missing-if-branch" title="else path not taken" >E</span>if (!isNaN(index) && index >= 0 && index < items.length) { - const item = items[index]; - <span class="missing-if-branch" title="else path not taken" >E</span>if (!item.read) { - markAsRead(item); - } - } - } - }); - }, - { root: null, threshold: 0 } - ); + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + // Infinity scroll sentinel + if (entry.target.id === 'load-more-sentinel') { + <span class="missing-if-branch" title="else path not taken" >E</span>if (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { + fetchItems(String(items[items.length - 1]._id)); + } + return; + } - items.forEach((_, index) => { - const el = document.getElementById(`item-${index}`); - <span class="missing-if-branch" title="else path not taken" >E</span>if (el) observer.observe(el); + // If item is not intersecting and is above the viewport, it's been scrolled past + <span class="missing-if-branch" title="else path not taken" >E</span>if (!entry.isIntersecting && entry.boundingClientRect.top < 0) { + const index = Number(entry.target.getAttribute('data-index')); + <span class="missing-if-branch" title="else path not taken" >E</span>if (!isNaN(index) && index >= 0 && index < items.length) { + const item = items[index]; + <span class="missing-if-branch" title="else path not taken" >E</span>if (!item.read) { + markAsRead(item); + } + } + } }); + }, + { root: null, threshold: 0 } + ); - const sentinel = document.getElementById('load-more-sentinel'); - if (sentinel) observer.observe(sentinel); + items.forEach((_, index) => { + const el = document.getElementById(`item-${index}`); + <span class="missing-if-branch" title="else path not taken" >E</span>if (el) observer.observe(el); + }); - return () => observer.disconnect(); - }, [items, loadingMore, hasMore]); + const sentinel = document.getElementById('load-more-sentinel'); + if (sentinel) observer.observe(sentinel); - if (loading) return <div className="feed-items-loading">Loading items...</div>; - if (error) return <div className="feed-items-error">Error: {error}</div>; + return () => observer.disconnect(); + }, [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 ? ( -<span class="branch-0 cbranch-no" title="branch not covered" > <p>No items found.</p></span> - ) : ( - <ul className="item-list"> - {items.map((item, index) => ( - <div - id={`item-${index}`} - key={item._id} - data-index={index} - className={index === selectedIndex ? 'selected-item-container' : ''} - onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etSelectedIndex(index)}</span> - > - <FeedItem item={item} /> - </div> - ))} - {hasMore && ( - <div id="load-more-sentinel" className="loading-more"> - {loadingMore ? 'Loading more...' : ''} - </div> - )} - </ul> - )} - </div> - ); + return ( + <div className="feed-items"> + {items.length === 0 ? ( +<span class="branch-0 cbranch-no" title="branch not covered" > <p>No items found.</p></span> + ) : ( + <ul className="item-list"> + {items.map((item, index) => ( + <div + id={`item-${index}`} + key={item._id} + data-index={index} + data-selected={index === selectedIndex} + onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etSelectedIndex(index)}</span> + > + <FeedItem item={item} /> + </div> + ))} + {hasMore && ( + <div id="load-more-sentinel" className="loading-more"> + {loadingMore ? 'Loading more...' : ''} + </div> + )} + </ul> + )} + </div> + ); } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.css.html b/frontend/coverage/src/components/FeedList.css.html index fe60b9d..2b93e18 100644 --- a/frontend/coverage/src/components/FeedList.css.html +++ b/frontend/coverage/src/components/FeedList.css.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedList.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedList.css - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedList.css</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -156,7 +160,159 @@ <a name='L95'></a><a href='#L95'>95</a> <a name='L96'></a><a href='#L96'>96</a> <a name='L97'></a><a href='#L97'>97</a> -<a name='L98'></a><a href='#L98'>98</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L98'></a><a href='#L98'>98</a> +<a name='L99'></a><a href='#L99'>99</a> +<a name='L100'></a><a href='#L100'>100</a> +<a name='L101'></a><a href='#L101'>101</a> +<a name='L102'></a><a href='#L102'>102</a> +<a name='L103'></a><a href='#L103'>103</a> +<a name='L104'></a><a href='#L104'>104</a> +<a name='L105'></a><a href='#L105'>105</a> +<a name='L106'></a><a href='#L106'>106</a> +<a name='L107'></a><a href='#L107'>107</a> +<a name='L108'></a><a href='#L108'>108</a> +<a name='L109'></a><a href='#L109'>109</a> +<a name='L110'></a><a href='#L110'>110</a> +<a name='L111'></a><a href='#L111'>111</a> +<a name='L112'></a><a href='#L112'>112</a> +<a name='L113'></a><a href='#L113'>113</a> +<a name='L114'></a><a href='#L114'>114</a> +<a name='L115'></a><a href='#L115'>115</a> +<a name='L116'></a><a href='#L116'>116</a> +<a name='L117'></a><a href='#L117'>117</a> +<a name='L118'></a><a href='#L118'>118</a> +<a name='L119'></a><a href='#L119'>119</a> +<a name='L120'></a><a href='#L120'>120</a> +<a name='L121'></a><a href='#L121'>121</a> +<a name='L122'></a><a href='#L122'>122</a> +<a name='L123'></a><a href='#L123'>123</a> +<a name='L124'></a><a href='#L124'>124</a> +<a name='L125'></a><a href='#L125'>125</a> +<a name='L126'></a><a href='#L126'>126</a> +<a name='L127'></a><a href='#L127'>127</a> +<a name='L128'></a><a href='#L128'>128</a> +<a name='L129'></a><a href='#L129'>129</a> +<a name='L130'></a><a href='#L130'>130</a> +<a name='L131'></a><a href='#L131'>131</a> +<a name='L132'></a><a href='#L132'>132</a> +<a name='L133'></a><a href='#L133'>133</a> +<a name='L134'></a><a href='#L134'>134</a> +<a name='L135'></a><a href='#L135'>135</a> +<a name='L136'></a><a href='#L136'>136</a> +<a name='L137'></a><a href='#L137'>137</a> +<a name='L138'></a><a href='#L138'>138</a> +<a name='L139'></a><a href='#L139'>139</a> +<a name='L140'></a><a href='#L140'>140</a> +<a name='L141'></a><a href='#L141'>141</a> +<a name='L142'></a><a href='#L142'>142</a> +<a name='L143'></a><a href='#L143'>143</a> +<a name='L144'></a><a href='#L144'>144</a> +<a name='L145'></a><a href='#L145'>145</a> +<a name='L146'></a><a href='#L146'>146</a> +<a name='L147'></a><a href='#L147'>147</a> +<a name='L148'></a><a href='#L148'>148</a> +<a name='L149'></a><a href='#L149'>149</a> +<a name='L150'></a><a href='#L150'>150</a> +<a name='L151'></a><a href='#L151'>151</a> +<a name='L152'></a><a href='#L152'>152</a> +<a name='L153'></a><a href='#L153'>153</a> +<a name='L154'></a><a href='#L154'>154</a> +<a name='L155'></a><a href='#L155'>155</a> +<a name='L156'></a><a href='#L156'>156</a> +<a name='L157'></a><a href='#L157'>157</a> +<a name='L158'></a><a href='#L158'>158</a> +<a name='L159'></a><a href='#L159'>159</a> +<a name='L160'></a><a href='#L160'>160</a> +<a name='L161'></a><a href='#L161'>161</a> +<a name='L162'></a><a href='#L162'>162</a> +<a name='L163'></a><a href='#L163'>163</a> +<a name='L164'></a><a href='#L164'>164</a> +<a name='L165'></a><a href='#L165'>165</a> +<a name='L166'></a><a href='#L166'>166</a> +<a name='L167'></a><a href='#L167'>167</a> +<a name='L168'></a><a href='#L168'>168</a> +<a name='L169'></a><a href='#L169'>169</a> +<a name='L170'></a><a href='#L170'>170</a> +<a name='L171'></a><a href='#L171'>171</a> +<a name='L172'></a><a href='#L172'>172</a> +<a name='L173'></a><a href='#L173'>173</a> +<a name='L174'></a><a href='#L174'>174</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -254,120 +410,195 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">.feed-list { - /* Removed card styling */ - padding: 0; - background: transparent; + padding: 1rem; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: #777; + /* specific v1 color */ + font-size: 0.8rem; } -.feed-list h2 { - font-size: 1.2rem; - margin-bottom: 0.5rem; - border-bottom: 1px solid #999; - padding-bottom: 0.25rem; - text-transform: uppercase; - letter-spacing: 1px; +.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); + /* Usually dark/white depending on theme, v1 was white on blue? No, white on fixed header? No, v1 logo class says color: white. But sidebar is #ccc. */ + /* In v1 logo was fixed top left (blue header bar?). In v2 sidebar is #ccc. + Let's use theme text color but maybe bolder? */ } -.feed-list-items, -.tag-list-items, -.filter-list { - list-style: none; - padding: 0; - margin: 0; +/* Override logo color if necessary for themes */ +.theme-light .feed-list h1.logo { + color: #333; } -.sidebar-feed-item { - padding: 0.25rem 0; - border-bottom: none; - /* Clean look */ - display: flex; - justify-content: space-between; - align-items: center; +.theme-dark .feed-list h1.logo { + color: #eee; } -.feed-title { - color: #333; - text-decoration: none; - font-size: 0.9rem; +.search-section { + margin-bottom: 1rem; } -.feed-title:hover { - text-decoration: underline; - color: #000; +.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 */ } -.feed-category { - display: none; - /* Hide category in sidebar list to save space/match legacy simplicity if needed */ +.section-header { + font-size: 1rem; + /* v1 h4 size? */ + font-weight: bold; + margin: 1rem 0 0.25rem 0; + cursor: pointer; + user-select: none; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: #333; + /* Darker than list items */ + text-transform: lowercase; + font-variant: small-caps; +} + +.filter-list, +.tag-list-items, +.feed-list-items, +.nav-list { + list-style: none; + padding: 0; + margin: 0; } -.tag-section { - margin-top: 2rem; +.filter-list li, +.nav-list li { + margin-bottom: 0.1rem; } +.filter-list a, +.nav-list a, .tag-link, -.filter-list li a { - color: #333; - text-decoration: none; - font-size: 0.9rem; - display: block; - padding: 0.1rem 0; +.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, -.filter-list li a:hover { - text-decoration: underline; - background: transparent; - /* No hover bg */ - color: #000; +.feed-title:hover, +.logout-link:hover { + text-decoration: underline; + color: var(--link-color, blue); } -.filter-section { - margin-bottom: 2rem; +.filter-list a.active, +.tag-link.active, +.feed-title.active { + font-weight: bold; + color: #000; + /* Active state black */ } -.filter-list { - display: block; - /* Stack vertically */ +.tag-item, +.sidebar-feed-item { + margin-bottom: 0; } -.filter-list { - display: block; +.feed-category { + display: none; } -.filter-list li a { - text-decoration: none; - color: #333; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: bold; - font-variant: small-caps; - text-transform: lowercase; - font-size: 1.1rem; - display: block; - margin-bottom: 0.5rem; +.nav-section { + margin-top: 2rem; + border-top: 1px solid var(--border-color, #999); + padding-top: 0.5rem; } -.filter-list li a:hover { - color: blue; - background-color: transparent; +.logout-link { + text-align: left; + width: 100%; + color: #777; + /* Make logout less prominent */ +} + +.logout-link:hover { + color: var(--link-color, blue); +} + +.theme-section { + margin-top: 1rem; +} + +.theme-selector { + display: flex; + gap: 0.5rem; +} + +.theme-selector button { + background: transparent; + border: 1px solid var(--border-color, #999); + cursor: pointer; + padding: 0.1rem 0.3rem; + font-size: 0.9rem; + border-radius: 0; +} + +.theme-selector button.active { + background: var(--border-color, #999); + color: white; +} + +/* Scrollbar styling for webkit */ +.dashboard-sidebar::-webkit-scrollbar { + width: 4px; +} + +.dashboard-sidebar::-webkit-scrollbar-thumb { + background-color: var(--border-color, #999); }</pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.tsx.html b/frontend/coverage/src/components/FeedList.tsx.html index ba7d81f..acb2ede 100644 --- a/frontend/coverage/src/components/FeedList.tsx.html +++ b/frontend/coverage/src/components/FeedList.tsx.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/FeedList.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> FeedList.tsx - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">91.66% </span> - <span class="quiet">Statements</span> - <span class="fraction">22/24</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">82.35% </span> - <span class="quiet">Branches</span> - <span class="fraction">14/17</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">100% </span> - <span class="quiet">Functions</span> - <span class="fraction">8/8</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">100% </span> - <span class="quiet">Lines</span> - <span class="fraction">20/20</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedList.tsx</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">79.54% </span> + <span class="quiet">Statements</span> + <span class='fraction'>35/44</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">64.86% </span> + <span class="quiet">Branches</span> + <span class='fraction'>24/37</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">64.7% </span> + <span class="quiet">Functions</span> + <span class='fraction'>11/17</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">82.05% </span> + <span class="quiet">Lines</span> + <span class='fraction'>32/39</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line high"></div> - <pre><table class="coverage"> + </div> + <div class='status-line medium'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -139,18 +143,153 @@ <a name='L78'></a><a href='#L78'>78</a> <a name='L79'></a><a href='#L79'>79</a> <a name='L80'></a><a href='#L80'>80</a> -<a name='L81'></a><a href='#L81'>81</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">11x</span> -<span class="cline-any cline-yes">11x</span> -<span class="cline-any cline-yes">11x</span> -<span class="cline-any cline-yes">11x</span> -<span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">11x</span> +<a name='L81'></a><a href='#L81'>81</a> +<a name='L82'></a><a href='#L82'>82</a> +<a name='L83'></a><a href='#L83'>83</a> +<a name='L84'></a><a href='#L84'>84</a> +<a name='L85'></a><a href='#L85'>85</a> +<a name='L86'></a><a href='#L86'>86</a> +<a name='L87'></a><a href='#L87'>87</a> +<a name='L88'></a><a href='#L88'>88</a> +<a name='L89'></a><a href='#L89'>89</a> +<a name='L90'></a><a href='#L90'>90</a> +<a name='L91'></a><a href='#L91'>91</a> +<a name='L92'></a><a href='#L92'>92</a> +<a name='L93'></a><a href='#L93'>93</a> +<a name='L94'></a><a href='#L94'>94</a> +<a name='L95'></a><a href='#L95'>95</a> +<a name='L96'></a><a href='#L96'>96</a> +<a name='L97'></a><a href='#L97'>97</a> +<a name='L98'></a><a href='#L98'>98</a> +<a name='L99'></a><a href='#L99'>99</a> +<a name='L100'></a><a href='#L100'>100</a> +<a name='L101'></a><a href='#L101'>101</a> +<a name='L102'></a><a href='#L102'>102</a> +<a name='L103'></a><a href='#L103'>103</a> +<a name='L104'></a><a href='#L104'>104</a> +<a name='L105'></a><a href='#L105'>105</a> +<a name='L106'></a><a href='#L106'>106</a> +<a name='L107'></a><a href='#L107'>107</a> +<a name='L108'></a><a href='#L108'>108</a> +<a name='L109'></a><a href='#L109'>109</a> +<a name='L110'></a><a href='#L110'>110</a> +<a name='L111'></a><a href='#L111'>111</a> +<a name='L112'></a><a href='#L112'>112</a> +<a name='L113'></a><a href='#L113'>113</a> +<a name='L114'></a><a href='#L114'>114</a> +<a name='L115'></a><a href='#L115'>115</a> +<a name='L116'></a><a href='#L116'>116</a> +<a name='L117'></a><a href='#L117'>117</a> +<a name='L118'></a><a href='#L118'>118</a> +<a name='L119'></a><a href='#L119'>119</a> +<a name='L120'></a><a href='#L120'>120</a> +<a name='L121'></a><a href='#L121'>121</a> +<a name='L122'></a><a href='#L122'>122</a> +<a name='L123'></a><a href='#L123'>123</a> +<a name='L124'></a><a href='#L124'>124</a> +<a name='L125'></a><a href='#L125'>125</a> +<a name='L126'></a><a href='#L126'>126</a> +<a name='L127'></a><a href='#L127'>127</a> +<a name='L128'></a><a href='#L128'>128</a> +<a name='L129'></a><a href='#L129'>129</a> +<a name='L130'></a><a href='#L130'>130</a> +<a name='L131'></a><a href='#L131'>131</a> +<a name='L132'></a><a href='#L132'>132</a> +<a name='L133'></a><a href='#L133'>133</a> +<a name='L134'></a><a href='#L134'>134</a> +<a name='L135'></a><a href='#L135'>135</a> +<a name='L136'></a><a href='#L136'>136</a> +<a name='L137'></a><a href='#L137'>137</a> +<a name='L138'></a><a href='#L138'>138</a> +<a name='L139'></a><a href='#L139'>139</a> +<a name='L140'></a><a href='#L140'>140</a> +<a name='L141'></a><a href='#L141'>141</a> +<a name='L142'></a><a href='#L142'>142</a> +<a name='L143'></a><a href='#L143'>143</a> +<a name='L144'></a><a href='#L144'>144</a> +<a name='L145'></a><a href='#L145'>145</a> +<a name='L146'></a><a href='#L146'>146</a> +<a name='L147'></a><a href='#L147'>147</a> +<a name='L148'></a><a href='#L148'>148</a> +<a name='L149'></a><a href='#L149'>149</a> +<a name='L150'></a><a href='#L150'>150</a> +<a name='L151'></a><a href='#L151'>151</a> +<a name='L152'></a><a href='#L152'>152</a> +<a name='L153'></a><a href='#L153'>153</a> +<a name='L154'></a><a href='#L154'>154</a> +<a name='L155'></a><a href='#L155'>155</a> +<a name='L156'></a><a href='#L156'>156</a> +<a name='L157'></a><a href='#L157'>157</a> +<a name='L158'></a><a href='#L158'>158</a> +<a name='L159'></a><a href='#L159'>159</a> +<a name='L160'></a><a href='#L160'>160</a> +<a name='L161'></a><a href='#L161'>161</a> +<a name='L162'></a><a href='#L162'>162</a> +<a name='L163'></a><a href='#L163'>163</a> +<a name='L164'></a><a href='#L164'>164</a> +<a name='L165'></a><a href='#L165'>165</a> +<a name='L166'></a><a href='#L166'>166</a> +<a name='L167'></a><a href='#L167'>167</a> +<a name='L168'></a><a href='#L168'>168</a> +<a name='L169'></a><a href='#L169'>169</a> +<a name='L170'></a><a href='#L170'>170</a> +<a name='L171'></a><a href='#L171'>171</a> +<a name='L172'></a><a href='#L172'>172</a> +<a name='L173'></a><a href='#L173'>173</a> +<a name='L174'></a><a href='#L174'>174</a> +<a name='L175'></a><a href='#L175'>175</a> +<a name='L176'></a><a href='#L176'>176</a> +<a name='L177'></a><a href='#L177'>177</a> +<a name='L178'></a><a href='#L178'>178</a> +<a name='L179'></a><a href='#L179'>179</a> +<a name='L180'></a><a href='#L180'>180</a> +<a name='L181'></a><a href='#L181'>181</a> +<a name='L182'></a><a href='#L182'>182</a> +<a name='L183'></a><a href='#L183'>183</a> +<a name='L184'></a><a href='#L184'>184</a> +<a name='L185'></a><a href='#L185'>185</a> +<a name='L186'></a><a href='#L186'>186</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">2x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">13x</span> <span class="cline-any cline-yes">6x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">4x</span> @@ -172,8 +311,56 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">11x</span> -<span class="cline-any cline-yes">5x</span> +<span class="cline-any cline-yes">13x</span> +<span class="cline-any cline-yes">7x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">6x</span> +<span class="cline-any cline-yes">1x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">6x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">4x</span> <span class="cline-any cline-neutral"> </span> @@ -191,7 +378,20 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">3x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">2x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -207,7 +407,6 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> -<span class="cline-any cline-yes">3x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -216,107 +415,226 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'react-router-dom'; import type { Feed, Category } from '../types'; import './FeedList.css'; +import { apiFetch } from '../utils'; + +export default function FeedList({ + theme, + setTheme, + setSidebarVisible, +}: { + theme: string; + setTheme: (t: string) => void; + setSidebarVisible: (visible: boolean) => void; +}) { + 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 [searchQuery, setSearchQuery] = useState(''); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const location = useLocation(); + const { feedId, tagName } = useParams(); + + const currentFilter = + searchParams.get('filter') || + (location.pathname === '/' && !feedId && !tagName ? 'unread' : <span class="branch-1 cbranch-no" title="branch not covered" >'');</span> + + const handleSearch = <span class="fstat-no" title="function not covered" >(e</span>: React.FormEvent) => { +<span class="cstat-no" title="statement not covered" > e.preventDefault();</span> +<span class="cstat-no" title="statement not covered" > if (searchQuery.trim()) {</span> +<span class="cstat-no" title="statement not covered" > navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`);</span> + } + }; + + const toggleFeeds = () => { + setFeedsExpanded(!feedsExpanded); + }; + + useEffect(() => { + Promise.all([ + apiFetch('/api/feed/').then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span> + return res.json(); + }), + apiFetch('/api/tag').then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch tags');</span> + return res.json(); + }), + ]) + .then(([feedsData, tagsData]) => { + setFeeds(feedsData); + setTags(tagsData); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); -export default function FeedList() { - const [feeds, setFeeds] = useState<Feed[]>([]); - const [tags, setTags] = useState<Category[]>([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); + if (loading) return <div className="feed-list-loading">Loading feeds...</div>; + if (error) return <div className="feed-list-error">Error: {error}</div>; - useEffect(() => { - Promise.all([ - fetch('/api/feed/').then(res => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span> - return res.json(); - }), - fetch('/api/tag').then(res => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch tags');</span> - return res.json(); - }) - ]) - .then(([feedsData, tagsData]) => { - setFeeds(feedsData); - setTags(tagsData); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }, []); + const handleLogout = () => { + apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/login')); + }; - if (loading) return <div className="feed-list-loading">Loading feeds...</div>; - if (error) return <div className="feed-list-error">Error: {error}</div>; + return ( + <div className="feed-list"> + <h1 className="logo" onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etSidebarVisible(false)}></span> + 🐱 + </h1> - return ( - <div className="feed-list"> - <div className="filter-section"> - <ul className="filter-list"> - <li><Link to="/?filter=unread">Unread</Link></li> - <li><Link to="/?filter=all">All</Link></li> - <li><Link to="/?filter=starred">Starred</Link></li> - </ul> - </div> - <div className="feed-section"> - <h2>Feeds</h2> - {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"> - {feed.title || <span class="branch-1 cbranch-no" title="branch not covered" >feed.url}</span> - </Link> - {feed.category && <span className="feed-category">{feed.category}</span>} - </li> - ))} - </ul> - )} - </div> + <div className="search-section"> + <form onSubmit={handleSearch} className="search-form"> + <input + type="search" + placeholder="search..." + value={searchQuery} + onChange={<span class="fstat-no" title="function not covered" >(e</span>) => <span class="cstat-no" title="statement not covered" >setSearchQuery(e.target.value)}</span> + className="search-input" + /> + </form> + </div> - {tags && tags.length > 0 && ( - <div className="tag-section"> - <h2>Tags</h2> - <ul className="tag-list-items"> - {tags.map((tag) => ( - <li key={tag.title} className="tag-item"> - <Link to={`/tag/${encodeURIComponent(tag.title)}`} className="tag-link"> - {tag.title} - </Link> - </li> - ))} - </ul> - </div> - )} + <div className="filter-section"> + <ul className="filter-list"> + <li className="unread_filter"> + <Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : <span class="branch-1 cbranch-no" title="branch not covered" >''}></span> + unread + </Link> + </li> + <li className="all_filter"> + <Link to="/?filter=all" className={currentFilter === 'all' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}> + all + </Link> + </li> + <li className="starred_filter"> + <Link to="/?filter=starred" className={currentFilter === 'starred' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}> + starred + </Link> + </li> + </ul> + </div> + + <div className="tag-section"> + <h4 onClick={<span class="fstat-no" title="function not covered" >() => {</span> }} className="section-header"> + Tags + </h4> + <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 ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}`} + > + {tag.title} + </Link> + </li> + ))} + </ul> + </div> + + <div className="feed-section"> + <h4 onClick={toggleFeeds} className="section-header"> + 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) ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'}`} + > + {feed.title || <span class="branch-1 cbranch-no" title="branch not covered" >feed.url}</span> + </Link> + </li> + ))} + </ul> + ))} + </div> + + <div className="nav-section"> + <ul className="nav-list"> + <li> + <Link to="/settings" className="nav-link"> + 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={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etTheme('light')}</span> + className={theme === 'light' ? 'active' : <span class="branch-1 cbranch-no" title="branch not covered" >''}</span> + title="Light Theme" + > + ☀️ + </button> + <button + onClick={<span class="fstat-no" title="function not covered" >() => <span class="cstat-no" title="statement not covered" >s</span>etTheme('dark')}</span> + className={theme === 'dark' ? <span class="branch-0 cbranch-no" title="branch not covered" >'active' : '</span>'} + title="Dark Theme" + > + 🌙 + </button> </div> - ); + </div> + </div> + ); } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/Login.css.html b/frontend/coverage/src/components/Login.css.html index bb3654e..031618b 100644 --- a/frontend/coverage/src/components/Login.css.html +++ b/frontend/coverage/src/components/Login.css.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/Login.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> Login.css - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Login.css</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -233,7 +237,7 @@ text-align: center; } -button[type="submit"] { +button[type='submit'] { width: 100%; padding: 0.75rem; background-color: #007bff; @@ -245,27 +249,26 @@ button[type="submit"] { transition: background-color 0.2s; } -button[type="submit"]:hover { +button[type='submit']:hover { background-color: #0056b3; } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/Login.tsx.html b/frontend/coverage/src/components/Login.tsx.html index f29e3cb..43fb613 100644 --- a/frontend/coverage/src/components/Login.tsx.html +++ b/frontend/coverage/src/components/Login.tsx.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/Login.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> Login.tsx - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">100% </span> - <span class="quiet">Statements</span> - <span class="fraction">17/17</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">83.33% </span> - <span class="quiet">Branches</span> - <span class="fraction">5/6</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">100% </span> - <span class="quiet">Functions</span> - <span class="fraction">3/3</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">100% </span> - <span class="quiet">Lines</span> - <span class="fraction">17/17</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Login.tsx</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">100% </span> + <span class="quiet">Statements</span> + <span class='fraction'>17/17</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">83.33% </span> + <span class="quiet">Branches</span> + <span class='fraction'>5/6</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">100% </span> + <span class="quiet">Functions</span> + <span class='fraction'>3/3</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">100% </span> + <span class="quiet">Lines</span> + <span class='fraction'>17/17</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line high"></div> - <pre><table class="coverage"> + </div> + <div class='status-line high'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -113,7 +117,11 @@ <a name='L52'></a><a href='#L52'>52</a> <a name='L53'></a><a href='#L53'>53</a> <a name='L54'></a><a href='#L54'>54</a> -<a name='L55'></a><a href='#L55'>55</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L55'></a><a href='#L55'>55</a> +<a name='L56'></a><a href='#L56'>56</a> +<a name='L57'></a><a href='#L57'>57</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -171,74 +179,75 @@ import { useNavigate } from 'react-router-dom'; import './Login.css'; +import { apiFetch } from '../utils'; + export default function Login() { - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const navigate = useNavigate(); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const navigate = useNavigate(); - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - setError(''); + 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('password', password); + try { + // Use URLSearchParams to send as form-urlencoded, matching backend expectation + const params = new URLSearchParams(); + params.append('password', password); - const res = await fetch('/api/login', { - method: 'POST', - body: params, - }); + const res = await apiFetch('/api/login', { + method: 'POST', + body: params, + }); - if (res.ok) { - navigate('/'); - } else { - const data = await res.json(); - setError(data.message || <span class="branch-1 cbranch-no" title="branch not covered" >'Login failed')</span>; - } - } catch (err) { - setError('Network error'); - } - }; + if (res.ok) { + navigate('/'); + } else { + const data = await res.json(); + setError(data.message || <span class="branch-1 cbranch-no" title="branch not covered" >'Login failed')</span>; + } + } 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="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> + return ( + <div className="login-container"> + <form onSubmit={handleSubmit} className="login-form"> + <h1>neko rss mode</h1> + <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> + ); } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.css.html b/frontend/coverage/src/components/Settings.css.html index 6a1155e..28a1915 100644 --- a/frontend/coverage/src/components/Settings.css.html +++ b/frontend/coverage/src/components/Settings.css.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/Settings.css</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> Settings.css - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Statements</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Branches</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Functions</span> - <span class="fraction">0/0</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">0% </span> - <span class="quiet">Lines</span> - <span class="fraction">0/0</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Settings.css</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Statements</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Branches</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Functions</span> + <span class='fraction'>0/0</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">0% </span> + <span class="quiet">Lines</span> + <span class='fraction'>0/0</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line low"></div> - <pre><table class="coverage"> + </div> + <div class='status-line low'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -141,7 +145,163 @@ <a name='L80'></a><a href='#L80'>80</a> <a name='L81'></a><a href='#L81'>81</a> <a name='L82'></a><a href='#L82'>82</a> -<a name='L83'></a><a href='#L83'>83</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L83'></a><a href='#L83'>83</a> +<a name='L84'></a><a href='#L84'>84</a> +<a name='L85'></a><a href='#L85'>85</a> +<a name='L86'></a><a href='#L86'>86</a> +<a name='L87'></a><a href='#L87'>87</a> +<a name='L88'></a><a href='#L88'>88</a> +<a name='L89'></a><a href='#L89'>89</a> +<a name='L90'></a><a href='#L90'>90</a> +<a name='L91'></a><a href='#L91'>91</a> +<a name='L92'></a><a href='#L92'>92</a> +<a name='L93'></a><a href='#L93'>93</a> +<a name='L94'></a><a href='#L94'>94</a> +<a name='L95'></a><a href='#L95'>95</a> +<a name='L96'></a><a href='#L96'>96</a> +<a name='L97'></a><a href='#L97'>97</a> +<a name='L98'></a><a href='#L98'>98</a> +<a name='L99'></a><a href='#L99'>99</a> +<a name='L100'></a><a href='#L100'>100</a> +<a name='L101'></a><a href='#L101'>101</a> +<a name='L102'></a><a href='#L102'>102</a> +<a name='L103'></a><a href='#L103'>103</a> +<a name='L104'></a><a href='#L104'>104</a> +<a name='L105'></a><a href='#L105'>105</a> +<a name='L106'></a><a href='#L106'>106</a> +<a name='L107'></a><a href='#L107'>107</a> +<a name='L108'></a><a href='#L108'>108</a> +<a name='L109'></a><a href='#L109'>109</a> +<a name='L110'></a><a href='#L110'>110</a> +<a name='L111'></a><a href='#L111'>111</a> +<a name='L112'></a><a href='#L112'>112</a> +<a name='L113'></a><a href='#L113'>113</a> +<a name='L114'></a><a href='#L114'>114</a> +<a name='L115'></a><a href='#L115'>115</a> +<a name='L116'></a><a href='#L116'>116</a> +<a name='L117'></a><a href='#L117'>117</a> +<a name='L118'></a><a href='#L118'>118</a> +<a name='L119'></a><a href='#L119'>119</a> +<a name='L120'></a><a href='#L120'>120</a> +<a name='L121'></a><a href='#L121'>121</a> +<a name='L122'></a><a href='#L122'>122</a> +<a name='L123'></a><a href='#L123'>123</a> +<a name='L124'></a><a href='#L124'>124</a> +<a name='L125'></a><a href='#L125'>125</a> +<a name='L126'></a><a href='#L126'>126</a> +<a name='L127'></a><a href='#L127'>127</a> +<a name='L128'></a><a href='#L128'>128</a> +<a name='L129'></a><a href='#L129'>129</a> +<a name='L130'></a><a href='#L130'>130</a> +<a name='L131'></a><a href='#L131'>131</a> +<a name='L132'></a><a href='#L132'>132</a> +<a name='L133'></a><a href='#L133'>133</a> +<a name='L134'></a><a href='#L134'>134</a> +<a name='L135'></a><a href='#L135'>135</a> +<a name='L136'></a><a href='#L136'>136</a> +<a name='L137'></a><a href='#L137'>137</a> +<a name='L138'></a><a href='#L138'>138</a> +<a name='L139'></a><a href='#L139'>139</a> +<a name='L140'></a><a href='#L140'>140</a> +<a name='L141'></a><a href='#L141'>141</a> +<a name='L142'></a><a href='#L142'>142</a> +<a name='L143'></a><a href='#L143'>143</a> +<a name='L144'></a><a href='#L144'>144</a> +<a name='L145'></a><a href='#L145'>145</a> +<a name='L146'></a><a href='#L146'>146</a> +<a name='L147'></a><a href='#L147'>147</a> +<a name='L148'></a><a href='#L148'>148</a> +<a name='L149'></a><a href='#L149'>149</a> +<a name='L150'></a><a href='#L150'>150</a> +<a name='L151'></a><a href='#L151'>151</a> +<a name='L152'></a><a href='#L152'>152</a> +<a name='L153'></a><a href='#L153'>153</a> +<a name='L154'></a><a href='#L154'>154</a> +<a name='L155'></a><a href='#L155'>155</a> +<a name='L156'></a><a href='#L156'>156</a> +<a name='L157'></a><a href='#L157'>157</a> +<a name='L158'></a><a href='#L158'>158</a> +<a name='L159'></a><a href='#L159'>159</a> +<a name='L160'></a><a href='#L160'>160</a> +<a name='L161'></a><a href='#L161'>161</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -224,105 +384,182 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">.settings-page { - padding: 2rem; - max-width: 800px; - margin: 0 auto; + padding: 2rem; + max-width: 800px; + margin: 0 auto; } .add-feed-section { - background: #f9f9f9; - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 2rem; - border: 1px solid #eee; + background: var(--sidebar-bg); + padding: 1.5rem; + border-radius: 8px; + margin-bottom: 2rem; + border: 1px solid var(--border-color); } .add-feed-form { - display: flex; - gap: 1rem; + display: flex; + gap: 1rem; } .feed-input { - flex: 1; - padding: 0.5rem; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 1rem; + flex: 1; + padding: 0.5rem; + border: 1px solid var(--border-color); + background: var(--bg-color); + color: var(--text-color); + border-radius: 4px; + font-size: 1rem; } .error-message { - color: #d32f2f; - margin-top: 1rem; + color: #d32f2f; + margin-top: 1rem; } .settings-feed-list { - list-style: none; - padding: 0; - border: 1px solid #eee; - border-radius: 8px; + list-style: none; + padding: 0; + border: 1px solid var(--border-color); + border-radius: 8px; } .settings-feed-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid var(--border-color); } .settings-feed-item:last-child { - border-bottom: none; + border-bottom: none; } .feed-info { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .feed-title { - font-weight: bold; - font-size: 1.1rem; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: bold; + font-size: 1.1rem; } .feed-url { - color: #666; - font-size: 0.9rem; + color: var(--text-color); + opacity: 0.6; + font-size: 0.9rem; } .delete-btn { - background: #ff5252; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; + background: #ff5252; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; } .delete-btn:hover { - background: #ff1744; + background: #ff1744; } .delete-btn:disabled { - background: #ffcdd2; - cursor: not-allowed; + background: #ffcdd2; + cursor: not-allowed; +} + +.import-export-section { + display: flex; + gap: 2rem; + margin-bottom: 2rem; +} + +@media (max-width: 600px) { + .import-export-section { + flex-direction: column; + } +} + +.import-section, +.export-section { + flex: 1; + background: var(--sidebar-bg); + padding: 1.5rem; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.import-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.file-input { + font-size: 0.9rem; + max-width: 100%; +} + +.export-buttons { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.export-btn { + display: inline-block; + padding: 0.5rem 1rem; + background: var(--bg-color); + color: var(--link-color); + text-decoration: none; + border: 1px solid var(--border-color); + border-radius: 4px; + font-weight: bold; + text-align: center; + min-width: 70px; +} + +.export-btn:hover { + background: var(--sidebar-bg); +} + +button { + cursor: pointer; + padding: 0.5rem 1rem; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-color); + color: var(--text-color); + font-weight: bold; +} + +button:hover:not(:disabled) { + background: var(--sidebar-bg); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; }</pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.tsx.html b/frontend/coverage/src/components/Settings.tsx.html index df6d027..3d8d219 100644 --- a/frontend/coverage/src/components/Settings.tsx.html +++ b/frontend/coverage/src/components/Settings.tsx.html @@ -1,64 +1,68 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components/Settings.tsx</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> - <h1> - <a href="../../index.html">All files</a> / - <a href="index.html">src/components</a> Settings.tsx - </h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">75.55% </span> - <span class="quiet">Statements</span> - <span class="fraction">34/45</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">56.25% </span> - <span class="quiet">Branches</span> - <span class="fraction">9/16</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">82.35% </span> - <span class="quiet">Functions</span> - <span class="fraction">14/17</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">84.61% </span> - <span class="quiet">Lines</span> - <span class="fraction">33/39</span> - </div> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> + <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> Settings.tsx</h1> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">56.25% </span> + <span class="quiet">Statements</span> + <span class='fraction'>36/64</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">41.66% </span> + <span class="quiet">Branches</span> + <span class='fraction'>10/24</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">63.63% </span> + <span class="quiet">Functions</span> + <span class='fraction'>14/22</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">62.5% </span> + <span class="quiet">Lines</span> + <span class='fraction'>35/56</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line medium"></div> - <pre><table class="coverage"> + </div> + <div class='status-line medium'></div> + <pre><table class="coverage"> <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> <a name='L2'></a><a href='#L2'>2</a> <a name='L3'></a><a href='#L3'>3</a> @@ -180,7 +184,67 @@ <a name='L119'></a><a href='#L119'>119</a> <a name='L120'></a><a href='#L120'>120</a> <a name='L121'></a><a href='#L121'>121</a> -<a name='L122'></a><a href='#L122'>122</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<a name='L122'></a><a href='#L122'>122</a> +<a name='L123'></a><a href='#L123'>123</a> +<a name='L124'></a><a href='#L124'>124</a> +<a name='L125'></a><a href='#L125'>125</a> +<a name='L126'></a><a href='#L126'>126</a> +<a name='L127'></a><a href='#L127'>127</a> +<a name='L128'></a><a href='#L128'>128</a> +<a name='L129'></a><a href='#L129'>129</a> +<a name='L130'></a><a href='#L130'>130</a> +<a name='L131'></a><a href='#L131'>131</a> +<a name='L132'></a><a href='#L132'>132</a> +<a name='L133'></a><a href='#L133'>133</a> +<a name='L134'></a><a href='#L134'>134</a> +<a name='L135'></a><a href='#L135'>135</a> +<a name='L136'></a><a href='#L136'>136</a> +<a name='L137'></a><a href='#L137'>137</a> +<a name='L138'></a><a href='#L138'>138</a> +<a name='L139'></a><a href='#L139'>139</a> +<a name='L140'></a><a href='#L140'>140</a> +<a name='L141'></a><a href='#L141'>141</a> +<a name='L142'></a><a href='#L142'>142</a> +<a name='L143'></a><a href='#L143'>143</a> +<a name='L144'></a><a href='#L144'>144</a> +<a name='L145'></a><a href='#L145'>145</a> +<a name='L146'></a><a href='#L146'>146</a> +<a name='L147'></a><a href='#L147'>147</a> +<a name='L148'></a><a href='#L148'>148</a> +<a name='L149'></a><a href='#L149'>149</a> +<a name='L150'></a><a href='#L150'>150</a> +<a name='L151'></a><a href='#L151'>151</a> +<a name='L152'></a><a href='#L152'>152</a> +<a name='L153'></a><a href='#L153'>153</a> +<a name='L154'></a><a href='#L154'>154</a> +<a name='L155'></a><a href='#L155'>155</a> +<a name='L156'></a><a href='#L156'>156</a> +<a name='L157'></a><a href='#L157'>157</a> +<a name='L158'></a><a href='#L158'>158</a> +<a name='L159'></a><a href='#L159'>159</a> +<a name='L160'></a><a href='#L160'>160</a> +<a name='L161'></a><a href='#L161'>161</a> +<a name='L162'></a><a href='#L162'>162</a> +<a name='L163'></a><a href='#L163'>163</a> +<a name='L164'></a><a href='#L164'>164</a> +<a name='L165'></a><a href='#L165'>165</a> +<a name='L166'></a><a href='#L166'>166</a> +<a name='L167'></a><a href='#L167'>167</a> +<a name='L168'></a><a href='#L168'>168</a> +<a name='L169'></a><a href='#L169'>169</a> +<a name='L170'></a><a href='#L170'>170</a> +<a name='L171'></a><a href='#L171'>171</a> +<a name='L172'></a><a href='#L172'>172</a> +<a name='L173'></a><a href='#L173'>173</a> +<a name='L174'></a><a href='#L174'>174</a> +<a name='L175'></a><a href='#L175'>175</a> +<a name='L176'></a><a href='#L176'>176</a> +<a name='L177'></a><a href='#L177'>177</a> +<a name='L178'></a><a href='#L178'>178</a> +<a name='L179'></a><a href='#L179'>179</a> +<a name='L180'></a><a href='#L180'>180</a> +<a name='L181'></a><a href='#L181'>181</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -191,8 +255,6 @@ <span class="cline-any cline-yes">14x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">14x</span> -<span class="cline-any cline-yes">3x</span> -<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">14x</span> <span class="cline-any cline-yes">4x</span> @@ -212,6 +274,10 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">14x</span> +<span class="cline-any cline-yes">3x</span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">14x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-yes">1x</span> <span class="cline-any cline-neutral"> </span> @@ -254,6 +320,34 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">14x</span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-yes">14x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -281,6 +375,34 @@ <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-no"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> +<span class="cline-any cline-neutral"> </span> <span class="cline-any cline-yes">5x</span> <span class="cline-any cline-neutral"> </span> <span class="cline-any cline-neutral"> </span> @@ -304,142 +426,200 @@ <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import React, { useEffect, useState } from 'react'; import type { Feed } from '../types'; import './Settings.css'; +import { apiFetch } from '../utils'; export default function Settings() { - const [feeds, setFeeds] = useState<Feed[]>([]); - const [newFeedUrl, setNewFeedUrl] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); + const [feeds, setFeeds] = useState<Feed[]>([]); + const [newFeedUrl, setNewFeedUrl] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState<string | null>(null); - useEffect(() => { + const [importFile, setImportFile] = useState<File | null>(null); + + const fetchFeeds = () => { + setLoading(true); + apiFetch('/api/feed/') + .then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span> + return res.json(); + }) + .then((data) => { + setFeeds(data); + setLoading(false); + }) + .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > setError(err.message);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; + + useEffect(() => { + fetchFeeds(); + }, []); + + const handleAddFeed = (e: React.FormEvent) => { + e.preventDefault(); + <span class="missing-if-branch" title="if path not taken" >I</span>if (!newFeedUrl) <span class="cstat-no" title="statement not covered" >return;</span> + + setLoading(true); + apiFetch('/api/feed/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: newFeedUrl }), + }) + .then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to add feed');</span> + return res.json(); + }) + .then(() => { + setNewFeedUrl(''); fetchFeeds(); - }, []); + }) + .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > setError(err.message);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; + + const handleDeleteFeed = (id: number) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!globalThis.confirm('Are you sure you want to delete this feed?')) <span class="cstat-no" title="statement not covered" >return;</span> - const fetchFeeds = () => { - setLoading(true); - fetch('/api/feed/') - .then((res) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to fetch feeds');</span> - return res.json(); - }) - .then((data) => { - setFeeds(data); - setLoading(false); - }) - .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { -<span class="cstat-no" title="statement not covered" > setError(err.message);</span> -<span class="cstat-no" title="statement not covered" > setLoading(false);</span> - }); - }; + setLoading(true); + apiFetch(`/api/feed/${id}`, { + method: 'DELETE', + }) + .then((res) => { + <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to delete feed');</span> + setFeeds(feeds.filter((f) => f._id !== id)); + setLoading(false); + }) + .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > setError(err.message);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; - const handleAddFeed = (e: React.FormEvent) => { - e.preventDefault(); - <span class="missing-if-branch" title="if path not taken" >I</span>if (!newFeedUrl) <span class="cstat-no" title="statement not covered" >return;</span> + const handleImport = <span class="fstat-no" title="function not covered" >(e</span>: React.FormEvent) => { +<span class="cstat-no" title="statement not covered" > e.preventDefault();</span> +<span class="cstat-no" title="statement not covered" > if (!importFile) <span class="cstat-no" title="statement not covered" >return;</span></span> - setLoading(true); - fetch('/api/feed/', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url: newFeedUrl }), - }) - .then((res) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to add feed');</span> - return res.json(); - }) - .then(() => { - setNewFeedUrl(''); - fetchFeeds(); // Refresh list (or we could append if server returns full feed object) - }) - .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { -<span class="cstat-no" title="statement not covered" > setError(err.message);</span> -<span class="cstat-no" title="statement not covered" > setLoading(false);</span> - }); - }; +<span class="cstat-no" title="statement not covered" > setLoading(true);</span> + const formData = <span class="cstat-no" title="statement not covered" >new FormData();</span> +<span class="cstat-no" title="statement not covered" > formData.append('file', importFile);</span> +<span class="cstat-no" title="statement not covered" > formData.append('format', 'opml');</span> - const handleDeleteFeed = (id: number) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!globalThis.confirm('Are you sure you want to delete this feed?')) <span class="cstat-no" title="statement not covered" >return;</span> +<span class="cstat-no" title="statement not covered" > apiFetch('/api/import', {</span> + method: 'POST', + body: formData, + }) + .then(<span class="fstat-no" title="function not covered" >(r</span>es) => { +<span class="cstat-no" title="statement not covered" > if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to import feeds');</span></span> +<span class="cstat-no" title="statement not covered" > return res.json();</span> + }) + .then(<span class="fstat-no" title="function not covered" >() => {</span> +<span class="cstat-no" title="statement not covered" > setImportFile(null);</span> +<span class="cstat-no" title="statement not covered" > fetchFeeds();</span> +<span class="cstat-no" title="statement not covered" > alert('Import successful!');</span> + }) + .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { +<span class="cstat-no" title="statement not covered" > setError(err.message);</span> +<span class="cstat-no" title="statement not covered" > setLoading(false);</span> + }); + }; - setLoading(true); - fetch(`/api/feed/${id}`, { - method: 'DELETE', - }) - .then((res) => { - <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) <span class="cstat-no" title="statement not covered" >throw new Error('Failed to delete feed');</span> - setFeeds(feeds.filter((f) => f._id !== id)); - setLoading(false); - }) - .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) => { -<span class="cstat-no" title="statement not covered" > setError(err.message);</span> -<span class="cstat-no" title="statement not covered" > setLoading(false);</span> - }); - }; + return ( + <div className="settings-page"> + <h2>Settings</h2> - return ( - <div className="settings-page"> - <h2>Settings</h2> + <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="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> - {error && <span class="branch-1 cbranch-no" title="branch not covered" ><p className="error-message">{error}</p>}</span> - </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" + onChange={<span class="fstat-no" title="function not covered" >(e</span>) => <span class="cstat-no" title="statement not covered" >setImportFile(e.target.files?.[0] || null)}</span> + className="file-input" + disabled={loading} + /> + <button type="submit" disabled={!importFile || <span class="branch-1 cbranch-no" title="branch not covered" >loading}></span> + Import + </button> + </form> + </div> - <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 || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}<</span>/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 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> + + {error && <span class="branch-1 cbranch-no" title="branch not covered" ><p className="error-message">{error}</p>}</span> + + <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 || <span class="branch-1 cbranch-no" title="branch not covered" >'(No Title)'}<</span>/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> + ); } </pre></td></tr></table></pre> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/components/index.html b/frontend/coverage/src/components/index.html index 7e1a0b7..66ca900 100644 --- a/frontend/coverage/src/components/index.html +++ b/frontend/coverage/src/components/index.html @@ -1,303 +1,251 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src/components</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../../prettify.css" /> <link rel="stylesheet" href="../../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> <h1><a href="../../index.html">All files</a> src/components</h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">86.78% </span> - <span class="quiet">Statements</span> - <span class="fraction">197/227</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">77.2% </span> - <span class="quiet">Branches</span> - <span class="fraction">105/136</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">87.5% </span> - <span class="quiet">Functions</span> - <span class="fraction">56/64</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">89.26% </span> - <span class="quiet">Lines</span> - <span class="fraction">183/205</span> - </div> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">79.57% </span> + <span class="quiet">Statements</span> + <span class='fraction'>226/284</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">70.17% </span> + <span class="quiet">Branches</span> + <span class='fraction'>120/171</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">75.9% </span> + <span class="quiet">Functions</span> + <span class='fraction'>63/83</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">81.78% </span> + <span class="quiet">Lines</span> + <span class='fraction'>211/258</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line high"></div> - <div class="pad1"> - <table class="coverage-summary"> - <thead> - <tr> - <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> - <th - data-col="pic" - data-type="number" - data-fmt="html" - data-html="true" - class="pic" - ></th> - <th data-col="statements" data-type="number" data-fmt="pct" class="pct"> - Statements - </th> - <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> - <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> - <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> - <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> - </tr> - </thead> - <tbody> - <tr> - <td class="file empty" data-value="FeedItem.css"> - <a href="FeedItem.css.html">FeedItem.css</a> - </td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> + </div> + <div class='status-line medium'></div> + <div class="pad1"> +<table class="coverage-summary"> +<thead> +<tr> + <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> + <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> + <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> + <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> + <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> + <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> + <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> +</tr> +</thead> +<tbody><tr> + <td class="file empty" data-value="FeedItem.css"><a href="FeedItem.css.html">FeedItem.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> - <tr> - <td class="file medium" data-value="FeedItem.tsx"> - <a href="FeedItem.tsx.html">FeedItem.tsx</a> - </td> - <td data-value="78.94" class="pic medium"> - <div class="chart"> - <div class="cover-fill" style="width: 78%"></div> - <div class="cover-empty" style="width: 22%"></div> - </div> - </td> - <td data-value="78.94" class="pct medium">78.94%</td> - <td data-value="19" class="abs medium">15/19</td> - <td data-value="88.88" class="pct high">88.88%</td> - <td data-value="18" class="abs high">16/18</td> - <td data-value="85.71" class="pct high">85.71%</td> - <td data-value="7" class="abs high">6/7</td> - <td data-value="78.94" class="pct medium">78.94%</td> - <td data-value="19" class="abs medium">15/19</td> - </tr> +<tr> + <td class="file medium" data-value="FeedItem.tsx"><a href="FeedItem.tsx.html">FeedItem.tsx</a></td> + <td data-value="78.12" class="pic medium"> + <div class="chart"><div class="cover-fill" style="width: 78%"></div><div class="cover-empty" style="width: 22%"></div></div> + </td> + <td data-value="78.12" class="pct medium">78.12%</td> + <td data-value="32" class="abs medium">25/32</td> + <td data-value="86.95" class="pct high">86.95%</td> + <td data-value="23" class="abs high">20/23</td> + <td data-value="83.33" class="pct high">83.33%</td> + <td data-value="12" class="abs high">10/12</td> + <td data-value="80.64" class="pct high">80.64%</td> + <td data-value="31" class="abs high">25/31</td> + </tr> - <tr> - <td class="file empty" data-value="FeedItems.css"> - <a href="FeedItems.css.html">FeedItems.css</a> - </td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> +<tr> + <td class="file empty" data-value="FeedItems.css"><a href="FeedItems.css.html">FeedItems.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> - <tr> - <td class="file high" data-value="FeedItems.tsx"> - <a href="FeedItems.tsx.html">FeedItems.tsx</a> - </td> - <td data-value="89.34" class="pic high"> - <div class="chart"> - <div class="cover-fill" style="width: 89%"></div> - <div class="cover-empty" style="width: 11%"></div> - </div> - </td> - <td data-value="89.34" class="pct high">89.34%</td> - <td data-value="122" class="abs high">109/122</td> - <td data-value="77.21" class="pct medium">77.21%</td> - <td data-value="79" class="abs medium">61/79</td> - <td data-value="86.2" class="pct high">86.2%</td> - <td data-value="29" class="abs high">25/29</td> - <td data-value="89.09" class="pct high">89.09%</td> - <td data-value="110" class="abs high">98/110</td> - </tr> +<tr> + <td class="file high" data-value="FeedItems.tsx"><a href="FeedItems.tsx.html">FeedItems.tsx</a></td> + <td data-value="88.97" class="pic high"> + <div class="chart"><div class="cover-fill" style="width: 88%"></div><div class="cover-empty" style="width: 12%"></div></div> + </td> + <td data-value="88.97" class="pct high">88.97%</td> + <td data-value="127" class="abs high">113/127</td> + <td data-value="75.3" class="pct medium">75.3%</td> + <td data-value="81" class="abs medium">61/81</td> + <td data-value="86.2" class="pct high">86.2%</td> + <td data-value="29" class="abs high">25/29</td> + <td data-value="88.69" class="pct high">88.69%</td> + <td data-value="115" class="abs high">102/115</td> + </tr> - <tr> - <td class="file empty" data-value="FeedList.css"> - <a href="FeedList.css.html">FeedList.css</a> - </td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> +<tr> + <td class="file empty" data-value="FeedList.css"><a href="FeedList.css.html">FeedList.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> - <tr> - <td class="file high" data-value="FeedList.tsx"> - <a href="FeedList.tsx.html">FeedList.tsx</a> - </td> - <td data-value="91.66" class="pic high"> - <div class="chart"> - <div class="cover-fill" style="width: 91%"></div> - <div class="cover-empty" style="width: 9%"></div> - </div> - </td> - <td data-value="91.66" class="pct high">91.66%</td> - <td data-value="24" class="abs high">22/24</td> - <td data-value="82.35" class="pct high">82.35%</td> - <td data-value="17" class="abs high">14/17</td> - <td data-value="100" class="pct high">100%</td> - <td data-value="8" class="abs high">8/8</td> - <td data-value="100" class="pct high">100%</td> - <td data-value="20" class="abs high">20/20</td> - </tr> +<tr> + <td class="file medium" data-value="FeedList.tsx"><a href="FeedList.tsx.html">FeedList.tsx</a></td> + <td data-value="79.54" class="pic medium"> + <div class="chart"><div class="cover-fill" style="width: 79%"></div><div class="cover-empty" style="width: 21%"></div></div> + </td> + <td data-value="79.54" class="pct medium">79.54%</td> + <td data-value="44" class="abs medium">35/44</td> + <td data-value="64.86" class="pct medium">64.86%</td> + <td data-value="37" class="abs medium">24/37</td> + <td data-value="64.7" class="pct medium">64.7%</td> + <td data-value="17" class="abs medium">11/17</td> + <td data-value="82.05" class="pct high">82.05%</td> + <td data-value="39" class="abs high">32/39</td> + </tr> - <tr> - <td class="file empty" data-value="Login.css"> - <a href="Login.css.html">Login.css</a> - </td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> +<tr> + <td class="file empty" data-value="Login.css"><a href="Login.css.html">Login.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> - <tr> - <td class="file high" data-value="Login.tsx"> - <a href="Login.tsx.html">Login.tsx</a> - </td> - <td data-value="100" class="pic high"> - <div class="chart"> - <div class="cover-fill cover-full" style="width: 100%"></div> - <div class="cover-empty" style="width: 0%"></div> - </div> - </td> - <td data-value="100" class="pct high">100%</td> - <td data-value="17" class="abs high">17/17</td> - <td data-value="83.33" class="pct high">83.33%</td> - <td data-value="6" class="abs high">5/6</td> - <td data-value="100" class="pct high">100%</td> - <td data-value="3" class="abs high">3/3</td> - <td data-value="100" class="pct high">100%</td> - <td data-value="17" class="abs high">17/17</td> - </tr> +<tr> + <td class="file high" data-value="Login.tsx"><a href="Login.tsx.html">Login.tsx</a></td> + <td data-value="100" class="pic high"> + <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div> + </td> + <td data-value="100" class="pct high">100%</td> + <td data-value="17" class="abs high">17/17</td> + <td data-value="83.33" class="pct high">83.33%</td> + <td data-value="6" class="abs high">5/6</td> + <td data-value="100" class="pct high">100%</td> + <td data-value="3" class="abs high">3/3</td> + <td data-value="100" class="pct high">100%</td> + <td data-value="17" class="abs high">17/17</td> + </tr> - <tr> - <td class="file empty" data-value="Settings.css"> - <a href="Settings.css.html">Settings.css</a> - </td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> +<tr> + <td class="file empty" data-value="Settings.css"><a href="Settings.css.html">Settings.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> - <tr> - <td class="file medium" data-value="Settings.tsx"> - <a href="Settings.tsx.html">Settings.tsx</a> - </td> - <td data-value="75.55" class="pic medium"> - <div class="chart"> - <div class="cover-fill" style="width: 75%"></div> - <div class="cover-empty" style="width: 25%"></div> - </div> - </td> - <td data-value="75.55" class="pct medium">75.55%</td> - <td data-value="45" class="abs medium">34/45</td> - <td data-value="56.25" class="pct medium">56.25%</td> - <td data-value="16" class="abs medium">9/16</td> - <td data-value="82.35" class="pct high">82.35%</td> - <td data-value="17" class="abs high">14/17</td> - <td data-value="84.61" class="pct high">84.61%</td> - <td data-value="39" class="abs high">33/39</td> - </tr> - </tbody> - </table> - </div> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z - </div> - <script src="../../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../../sorter.js"></script> - <script src="../../block-navigation.js"></script> - </body> +<tr> + <td class="file medium" data-value="Settings.tsx"><a href="Settings.tsx.html">Settings.tsx</a></td> + <td data-value="56.25" class="pic medium"> + <div class="chart"><div class="cover-fill" style="width: 56%"></div><div class="cover-empty" style="width: 44%"></div></div> + </td> + <td data-value="56.25" class="pct medium">56.25%</td> + <td data-value="64" class="abs medium">36/64</td> + <td data-value="41.66" class="pct low">41.66%</td> + <td data-value="24" class="abs low">10/24</td> + <td data-value="63.63" class="pct medium">63.63%</td> + <td data-value="22" class="abs medium">14/22</td> + <td data-value="62.5" class="pct medium">62.5%</td> + <td data-value="56" class="abs medium">35/56</td> + </tr> + +</tbody> +</table> +</div> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../../sorter.js"></script> + <script src="../../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html index 14e9f51..19a8dfd 100644 --- a/frontend/coverage/src/index.html +++ b/frontend/coverage/src/index.html @@ -1,139 +1,146 @@ + <!doctype html> <html lang="en"> - <head> + +<head> <title>Code coverage report for src</title> <meta charset="utf-8" /> <link rel="stylesheet" href="../prettify.css" /> <link rel="stylesheet" href="../base.css" /> <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <style type="text/css"> - .coverage-summary .sorter { - background-image: url(../sort-arrow-sprite.png); - } + <style type='text/css'> + .coverage-summary .sorter { + background-image: url(../sort-arrow-sprite.png); + } </style> - </head> - - <body> - <div class="wrapper"> - <div class="pad1"> +</head> + +<body> +<div class='wrapper'> + <div class='pad1'> <h1><a href="../index.html">All files</a> src</h1> - <div class="clearfix"> - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Statements</span> - <span class="fraction">15/19</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">66.66% </span> - <span class="quiet">Branches</span> - <span class="fraction">4/6</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">77.77% </span> - <span class="quiet">Functions</span> - <span class="fraction">7/9</span> - </div> - - <div class="fl pad1y space-right2"> - <span class="strong">78.94% </span> - <span class="quiet">Lines</span> - <span class="fraction">15/19</span> - </div> + <div class='clearfix'> + + <div class='fl pad1y space-right2'> + <span class="strong">76.47% </span> + <span class="quiet">Statements</span> + <span class='fraction'>26/34</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">69.23% </span> + <span class="quiet">Branches</span> + <span class='fraction'>18/26</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">70% </span> + <span class="quiet">Functions</span> + <span class='fraction'>7/10</span> + </div> + + + <div class='fl pad1y space-right2'> + <span class="strong">78.78% </span> + <span class="quiet">Lines</span> + <span class='fraction'>26/33</span> + </div> + + </div> <p class="quiet"> - Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, - <em>p</em> or <em>k</em> for the previous block. + Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. </p> <template id="filterTemplate"> - <div class="quiet"> - Filter: - <input type="search" id="fileSearch" /> - </div> + <div class="quiet"> + Filter: + <input type="search" id="fileSearch"> + </div> </template> - </div> - <div class="status-line medium"></div> - <div class="pad1"> - <table class="coverage-summary"> - <thead> - <tr> - <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> - <th - data-col="pic" - data-type="number" - data-fmt="html" - data-html="true" - class="pic" - ></th> - <th data-col="statements" data-type="number" data-fmt="pct" class="pct"> - Statements - </th> - <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> - <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> - <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> - <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> - <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> - </tr> - </thead> - <tbody> - <tr> - <td class="file empty" data-value="App.css"><a href="App.css.html">App.css</a></td> - <td data-value="0" class="pic empty"> - <div class="chart"> - <div class="cover-fill" style="width: 0%"></div> - <div class="cover-empty" style="width: 100%"></div> - </div> - </td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - <td data-value="0" class="pct empty">0%</td> - <td data-value="0" class="abs empty">0/0</td> - </tr> - - <tr> - <td class="file medium" data-value="App.tsx"><a href="App.tsx.html">App.tsx</a></td> - <td data-value="78.94" class="pic medium"> - <div class="chart"> - <div class="cover-fill" style="width: 78%"></div> - <div class="cover-empty" style="width: 22%"></div> - </div> - </td> - <td data-value="78.94" class="pct medium">78.94%</td> - <td data-value="19" class="abs medium">15/19</td> - <td data-value="66.66" class="pct medium">66.66%</td> - <td data-value="6" class="abs medium">4/6</td> - <td data-value="77.77" class="pct medium">77.77%</td> - <td data-value="9" class="abs medium">7/9</td> - <td data-value="78.94" class="pct medium">78.94%</td> - <td data-value="19" class="abs medium">15/19</td> - </tr> - </tbody> - </table> - </div> - <div class="push"></div> - <!-- for sticky footer --> - </div> - <!-- /wrapper --> - <div class="footer quiet pad2 space-top1 center small"> - Code coverage generated by - <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> - at 2026-02-13T21:49:58.924Z </div> - <script src="../prettify.js"></script> - <script> - window.onload = function () { - prettyPrint(); - }; - </script> - <script src="../sorter.js"></script> - <script src="../block-navigation.js"></script> - </body> + <div class='status-line medium'></div> + <div class="pad1"> +<table class="coverage-summary"> +<thead> +<tr> + <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> + <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> + <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> + <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> + <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> + <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> + <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> + <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> +</tr> +</thead> +<tbody><tr> + <td class="file empty" data-value="App.css"><a href="App.css.html">App.css</a></td> + <td data-value="0" class="pic empty"> + <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div> + </td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + <td data-value="0" class="pct empty">0%</td> + <td data-value="0" class="abs empty">0/0</td> + </tr> + +<tr> + <td class="file medium" data-value="App.tsx"><a href="App.tsx.html">App.tsx</a></td> + <td data-value="72.72" class="pic medium"> + <div class="chart"><div class="cover-fill" style="width: 72%"></div><div class="cover-empty" style="width: 28%"></div></div> + </td> + <td data-value="72.72" class="pct medium">72.72%</td> + <td data-value="22" class="abs medium">16/22</td> + <td data-value="62.5" class="pct medium">62.5%</td> + <td data-value="16" class="abs medium">10/16</td> + <td data-value="62.5" class="pct medium">62.5%</td> + <td data-value="8" class="abs medium">5/8</td> + <td data-value="72.72" class="pct medium">72.72%</td> + <td data-value="22" class="abs medium">16/22</td> + </tr> + +<tr> + <td class="file high" data-value="utils.ts"><a href="utils.ts.html">utils.ts</a></td> + <td data-value="83.33" class="pic high"> + <div class="chart"><div class="cover-fill" style="width: 83%"></div><div class="cover-empty" style="width: 17%"></div></div> + </td> + <td data-value="83.33" class="pct high">83.33%</td> + <td data-value="12" class="abs high">10/12</td> + <td data-value="80" class="pct high">80%</td> + <td data-value="10" class="abs high">8/10</td> + <td data-value="100" class="pct high">100%</td> + <td data-value="2" class="abs high">2/2</td> + <td data-value="90.9" class="pct high">90.9%</td> + <td data-value="11" class="abs high">10/11</td> + </tr> + +</tbody> +</table> +</div> + <div class='push'></div><!-- for sticky footer --> + </div><!-- /wrapper --> + <div class='footer quiet pad2 space-top1 center small'> + Code coverage generated by + <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> + at 2026-02-14T18:02:09.004Z + </div> + <script src="../prettify.js"></script> + <script> + window.onload = function () { + prettyPrint(); + }; + </script> + <script src="../sorter.js"></script> + <script src="../block-navigation.js"></script> + </body> </html> +
\ No newline at end of file |
