From 0b08b2fb49827399ff0652bc20d5f71e2066025c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 19:13:22 +0000 Subject: v3 ui: require 100% visibility before marking items as read Change IntersectionObserver threshold from 0.5 to 1.0 so items are only marked as read when fully scrolled into view, reducing accidental mark-as-read during fast scrolling. Also fix unused Category import in perf test and add thicket config.json to repository so future agents can use thicket CLI commands. Closes NK-s2g59a https://claude.ai/code/session_019Z4VJxzY7tcAuNkPAkvry9 --- .thicket/.gitignore | 1 + .thicket/config.json | 3 + .thicket/tickets.jsonl | 8 +- frontend-vanilla/src/main.ts | 2 +- frontend-vanilla/src/perf/store.perf.test.ts | 2 +- web/dist/v3/assets/index-CoLpO-VY.js | 143 --------------------------- web/dist/v3/assets/index-M5xszonw.js | 143 +++++++++++++++++++++++++++ web/dist/v3/index.html | 2 +- 8 files changed, 157 insertions(+), 147 deletions(-) create mode 100644 .thicket/config.json delete mode 100644 web/dist/v3/assets/index-CoLpO-VY.js create mode 100644 web/dist/v3/assets/index-M5xszonw.js diff --git a/.thicket/.gitignore b/.thicket/.gitignore index be8efad..80b0aa1 100644 --- a/.thicket/.gitignore +++ b/.thicket/.gitignore @@ -1 +1,2 @@ cache.db +!config.json diff --git a/.thicket/config.json b/.thicket/config.json new file mode 100644 index 0000000..8f8cf46 --- /dev/null +++ b/.thicket/config.json @@ -0,0 +1,3 @@ +{ + "project_code": "NK" +} \ No newline at end of file diff --git a/.thicket/tickets.jsonl b/.thicket/tickets.jsonl index 738ce47..046e885 100644 --- a/.thicket/tickets.jsonl +++ b/.thicket/tickets.jsonl @@ -36,6 +36,7 @@ {"id":"NK-8rhpp3","title":"v2 frontend: when selected, don't change style of feed items","description":"Just leave them the same when j/k \"selects\" an item. No blue side thing, no change in background, it's distracting. Just scroll it to the right place.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T00:39:50.01934312Z","updated":"2026-02-14T01:02:54.204739756Z"} {"id":"NK-8s75ec","title":"page size and performance","description":"Do some analysis of page size (css/html/javascript) on the legacy version vs. new version and give me a report. We want it to be small and fast! If the new version is much worse file some tickets to investigate further.","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T20:16:13.898081788Z","updated":"2026-02-13T21:50:12.004391671Z"} {"id":"NK-8u3uo3","title":"Add configurable username support","description":"Currently login accepts a username field but ignores it. We should allow configuring a username (defaulting to 'neko') and validate it during login.","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-15T05:16:55.257993616Z","updated":"2026-02-15T05:16:55.257993616Z"} +{"id":"NK-967mx5","title":"Add configurable scroll-to-read delay/debounce","description":"Currently items are marked as read immediately when fully visible. Consider adding a short delay (e.g. 500ms) so fast scrolling doesn't mark items as read. This would be a UX enhancement building on NK-s2g59a.","type":"feature","status":"open","priority":3,"labels":null,"assignee":"","created":"2026-02-16T19:09:49.041731485Z","updated":"2026-02-16T19:09:49.041731485Z"} {"id":"NK-9hx0y7","title":"Implement Frontend Login","description":"Create login page and auth logic in the new React frontend. Port functionality from legacy login.html.","type":"","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T05:44:01.546395342Z","updated":"2026-02-13T05:50:33.877452063Z"} {"id":"NK-9pgjph","title":"v2 ui - font size 18px","description":"Compare your font sizes with the legacy version -- I think they're a little too small (16 vs 18 baseline)","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T03:21:48.453217898Z","updated":"2026-02-14T03:24:25.316927886Z"} {"id":"NK-9vquj9","title":"v3 login","description":"Login still doesn't work right -- i'm assuming the CSRF stuff is still wrong","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-16T16:24:06.315635122Z","updated":"2026-02-16T16:50:26.432014927Z"} @@ -89,6 +90,7 @@ {"id":"NK-hy162w","title":"URLs in UI are api/feed output, not loadable HTML","description":"After clicking in the sidebar, you get to a URL like http://localhost:9001/feed/38?filter=all but if you hit \"reload\" in the browser that retuns a blob of JSON to the browser! Oops. Maybe just don't change the user visible URL at all. If we do change the URLs, maybe just use #/feed/38/filter=all or something similar that is just client side for the JS.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-15T22:00:10.335296177Z","updated":"2026-02-15T22:22:41.252479327Z"} {"id":"NK-hyej38","title":"[ui] when a left menu item is \"active\" make it bold","description":"The \"default\" is UNREAD - this should be in the \"bold\" state when you're seeing that. When you filter out to \"ALL\" that should instead be bold. Same with the individual feeds if one is selected. And Starred.","type":"epic","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T00:47:25.74838134Z","updated":"2026-02-14T01:25:07.267016355Z"} {"id":"NK-iklxn4","title":"infinite scroll on ipad/mobile","description":"On a mobile device, the infinite scrooll didn't seem to be working properly and triggering as I scrolled to the bottom. Are the right triggers set up for mobile browsers as well as desktop -- this was on an ipad.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-15T16:12:01.013697894Z","updated":"2026-02-15T16:35:49.542636756Z"} +{"id":"NK-iux6qx","title":"Add thicket config.json to repository","description":"The .thicket/config.json file was missing from the repository, causing thicket add to fail with 'not initialized' error. Ensure config.json is tracked in git so future agents can use thicket commands.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-16T19:09:59.044156033Z","updated":"2026-02-16T19:10:08.535167617Z"} {"id":"NK-iw9l7h","title":"Improve scraper heuristics","description":"The scraper currently uses a simple fallback between CleanedText and TopNode. It could be improved to better handle different article layouts.","type":"feature","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-14T01:04:11.588135487Z","updated":"2026-02-14T01:04:11.588135487Z"} {"id":"NK-j9fv6r","title":"v3: sidebar behavior","description":"1. Remember sidebar open/close status via cookie\n2. On tablet+mobile, default to closed.","type":"task","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-16T16:00:26.254957438Z","updated":"2026-02-16T16:00:26.254957438Z"} {"id":"NK-jhludy","title":"600px width by default, closer to left panel","description":"On desktop the feed items are too narrow (~500px) compared to legacy version which is ~600px.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T20:14:48.84900972Z","updated":"2026-02-13T20:50:22.207833479Z"} @@ -135,7 +137,7 @@ {"id":"NK-ric1zs","title":"Migrate frontend to /api/ endpoints","description":"The backend now provides a clean REST API at /api/. Update the frontend UI to use these new endpoints instead of the legacy backward-compatibility routes (/stream/, /feed/, etc.). This will allow for cleaner separation and better utilization of proper REST patterns.","type":"cleanup","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T04:26:55.864725765Z","updated":"2026-02-13T04:26:55.864725765Z"} {"id":"NK-rn4nzp","title":"font themes","description":"in the v2 ui, let's offer a few different font stacks the user can switch through. primarily this should just change font-face, maybe size, but don't worry about colors or anything right now.\n\nthe current default (helvetica neue, palatino)\na fancy all serif stack\na no-nonsense modern san-serif stack\na terminal inspired fixed width stack","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T17:10:02.185477382Z","updated":"2026-02-14T18:12:30.253145852Z"} {"id":"NK-rohuiq","title":"titles changing on read state and hover","description":"Titles are changing on read state from blue to grey. They should just stay blue all the time.\n\nTitles are getting underlined on hover. They should have no underline regardless of hover state.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T03:36:26.36373162Z","updated":"2026-02-14T03:37:50.73870586Z"} -{"id":"NK-s2g59a","title":"v3 ui: change scroll mark-as-read criteria","description":"Currently items are marked as read immediately when 50% visible. This might be annoying if users are scrolling fast. We change it 100% viewable, ie the bottom has scrolled into view.","type":"bug","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-16T15:37:40.617999342Z","updated":"2026-02-16T15:37:40.617999342Z"} +{"id":"NK-s2g59a","title":"v3 ui: change scroll mark-as-read criteria","description":"Currently items are marked as read immediately when 50% visible. This might be annoying if users are scrolling fast. We change it 100% viewable, ie the bottom has scrolled into view.","type":"","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-16T15:37:40.617999342Z","updated":"2026-02-16T19:06:44.785323839Z"} {"id":"NK-s8nytj","title":"v3: close settings","description":"when settings page is shown, clicking settings again should close it and go back to the \"unread items\" state\n\nsimilarly, clicking \"unread\" or \"all\" or \"starred\" should close settings and take you to those.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-16T15:40:59.195773828Z","updated":"2026-02-16T16:17:20.080233097Z"} {"id":"NK-sdxq5p","title":"update documentation based on all the recent changes","description":"update README.md based on what's been changed given all the CLs and tickets recently","type":"task","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-15T00:07:09.08704631Z","updated":"2026-02-15T00:24:50.622207852Z"} {"id":"NK-shpyxh","title":"add search to new ui","description":"","type":"epic","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T19:29:44.251257089Z","updated":"2026-02-14T01:02:58.547025683Z"} @@ -155,6 +157,7 @@ {"id":"NK-uywybr","title":"https://computer.rip/rss.xml fails to importa","description":"running neko -a https://computer.rip/rss.xml gave an error. debug it and add test case to catch.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T20:12:28.54350403Z","updated":"2026-02-14T01:03:02.755247954Z"} {"id":"NK-v9e7r3","title":"consistency in sidebar","description":"With the new sidebar styling, SETTINGS and LOGOUT and the light/dark look really different than the rest. Let's make them more consistent from a style perspective.","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T23:55:51.554786606Z","updated":"2026-02-15T00:22:33.826814528Z"} {"id":"NK-wibjlg","title":"update README.md","description":"Ensure the build, configuration, etc are up too date.\nNote the git change when we started to vibe-code this in the history (with dates etc.)","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T20:18:08.790048498Z","updated":"2026-02-13T22:36:07.717448961Z"} +{"id":"NK-wj8m4v","title":"Fix pre-existing safehttp test failure","description":"TestSafeClient in internal/safehttp fails: private IP check returns nil error instead of blocking. The test expects an error for 10.0.0.1 but gets nil.","type":"bug","status":"open","priority":2,"labels":null,"assignee":"","created":"2026-02-16T19:09:38.775620083Z","updated":"2026-02-16T19:09:38.775620083Z"} {"id":"NK-wjats7","title":"v3 ui: takes 3 presses of 'j' to move to next item","description":"Is there some weird timer before it's scrolling it -- after that it's fine. Or a delay to setup the listener or something","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-16T16:20:01.7097349Z","updated":"2026-02-16T18:53:59.531388427Z"} {"id":"NK-wjnczv","title":"Vanilla JS: Test Infrastructure \u0026 Coverage","description":"Setup testing framework (likely vitest or simple runner) for vanilla JS. Refactor code for testability. Aim for 80% coverage on vanilla/app.js logic.","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T05:13:23.292982698Z","updated":"2026-02-14T05:34:53.241988263Z"} {"id":"NK-x924bu","title":"test coverage","description":"assume the code works properly (it mostly does)\nget to 90% test coverage on the go code","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T01:52:01.042476226Z","updated":"2026-02-13T03:54:21.526519915Z"} @@ -178,12 +181,15 @@ {"id":"NK-d7tr9po","from_ticket_id":"NK-8d1uzw","to_ticket_id":"NK-3g1ouf","type":"created_from","created":"2026-02-15T02:24:06.423302632Z"} {"id":"NK-d7zgu6w","from_ticket_id":"NK-fm15vq","to_ticket_id":"NK-ymf1jb","type":"created_from","created":"2026-02-13T20:27:36.795511524Z"} {"id":"NK-d86tgcs","from_ticket_id":"NK-ed1iah","to_ticket_id":"NK-1phdpf","type":"created_from","created":"2026-02-13T04:26:55.917754798Z"} +{"id":"NK-day1wdg","from_ticket_id":"NK-wj8m4v","to_ticket_id":"NK-s2g59a","type":"created_from","created":"2026-02-16T19:09:38.800821074Z"} {"id":"NK-db0jm5a","from_ticket_id":"NK-gxvegm","to_ticket_id":"NK-s8nytj","type":"created_from","created":"2026-02-16T16:17:21.472303923Z"} {"id":"NK-dda9zfr","from_ticket_id":"NK-lrew5z","to_ticket_id":"NK-mwf9q2","type":"created_from","created":"2026-02-13T18:04:57.273164732Z"} +{"id":"NK-ddpnjqu","from_ticket_id":"NK-967mx5","to_ticket_id":"NK-s2g59a","type":"created_from","created":"2026-02-16T19:09:49.063644515Z"} {"id":"NK-de65jjz","from_ticket_id":"NK-p0nfoi","to_ticket_id":"NK-r8rs7m","type":"created_from","created":"2026-02-15T16:49:31.043201298Z"} {"id":"NK-dew7hvb","from_ticket_id":"NK-tw0nga","to_ticket_id":"NK-59kbij","type":"created_from","created":"2026-02-13T15:01:33.825547908Z"} {"id":"NK-dfercff","from_ticket_id":"NK-d2be57","to_ticket_id":"NK-hy162w","type":"created_from","created":"2026-02-15T22:23:06.888593551Z"} {"id":"NK-dffwhjf","from_ticket_id":"NK-2t5ijy","to_ticket_id":"NK-lrew5z","type":"created_from","created":"2026-02-13T18:11:47.471931543Z"} +{"id":"NK-dfnadg4","from_ticket_id":"NK-iux6qx","to_ticket_id":"NK-s2g59a","type":"created_from","created":"2026-02-16T19:09:59.064561009Z"} {"id":"NK-dfyyk6k","from_ticket_id":"NK-hj6f9p","to_ticket_id":"NK-kra45a","type":"created_from","created":"2026-02-15T01:04:54.417714174Z"} {"id":"NK-dgbrb79","from_ticket_id":"NK-9hx0y7","to_ticket_id":"NK-t0nmbj","type":"created_from","created":"2026-02-13T05:44:01.556027956Z"} {"id":"NK-dgfppki","from_ticket_id":"NK-gqkh96","to_ticket_id":"NK-x924bu","type":"created_from","created":"2026-02-13T03:54:30.303602703Z"} diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts index 7b55c48..7d75e14 100644 --- a/frontend-vanilla/src/main.ts +++ b/frontend-vanilla/src/main.ts @@ -291,7 +291,7 @@ export function renderItems() { } } }); - }, { threshold: 0.5 }); + }, { threshold: 1.0 }); contentArea.querySelectorAll('.feed-item').forEach(el => itemObserver!.observe(el)); } diff --git a/frontend-vanilla/src/perf/store.perf.test.ts b/frontend-vanilla/src/perf/store.perf.test.ts index 734e132..382cbb4 100644 --- a/frontend-vanilla/src/perf/store.perf.test.ts +++ b/frontend-vanilla/src/perf/store.perf.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { Store } from '../store'; -import type { Item, Feed, Category } from '../types'; +import type { Item, Feed } from '../types'; function makeItem(id: number): Item { return { diff --git a/web/dist/v3/assets/index-CoLpO-VY.js b/web/dist/v3/assets/index-CoLpO-VY.js deleted file mode 100644 index 2d26b99..0000000 --- a/web/dist/v3/assets/index-CoLpO-VY.js +++ /dev/null @@ -1,143 +0,0 @@ -(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))n(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&n(d)}).observe(document,{childList:!0,subtree:!0});function a(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(s){if(s.ep)return;s.ep=!0;const r=a(s);fetch(s.href,r)}})();function R(t){const a=`; ${document.cookie}`.split(`; ${t}=`);if(a.length===2)return a.pop()?.split(";").shift()}async function g(t,e){const a=e?.method?.toUpperCase()||"GET",n=["POST","PUT","DELETE"].includes(a),s=new Headers(e?.headers||{});if(n){const r=R("csrf_token");r&&s.set("X-CSRF-Token",r)}return fetch(t,{...e,headers:s,credentials:"include"})}class B extends EventTarget{feeds=[];tags=[];items=[];activeFeedId=null;activeTagName=null;filter="unread";searchQuery="";loading=!1;hasMore=!0;theme=localStorage.getItem("neko-theme")||"light";fontTheme=localStorage.getItem("neko-font-theme")||"default";sidebarVisible=window.innerWidth>768;setFeeds(e){this.feeds=e,this.emit("feeds-updated")}setTags(e){this.tags=e,this.emit("tags-updated")}setItems(e,a=!1){a?this.items=[...this.items,...e]:this.items=e,this.emit("items-updated")}setActiveFeed(e){this.activeFeedId=e,this.activeTagName=null,this.emit("active-feed-updated")}setActiveTag(e){this.activeTagName=e,this.activeFeedId=null,this.emit("active-tag-updated")}setFilter(e){this.filter!==e&&(this.filter=e,this.emit("filter-updated"))}setSearchQuery(e){this.searchQuery!==e&&(this.searchQuery=e,this.emit("search-updated"))}setLoading(e){this.loading=e,this.emit("loading-state-changed")}setHasMore(e){this.hasMore=e}setTheme(e){this.theme=e,localStorage.setItem("neko-theme",e),this.emit("theme-updated")}setFontTheme(e){this.fontTheme=e,localStorage.setItem("neko-font-theme",e),this.emit("theme-updated")}setSidebarVisible(e){this.sidebarVisible=e,this.emit("sidebar-toggle")}toggleSidebar(){this.setSidebarVisible(!this.sidebarVisible)}emit(e,a){this.dispatchEvent(new CustomEvent(e,{detail:a}))}on(e,a){this.addEventListener(e,a)}}const i=new B;class C extends EventTarget{constructor(){super(),window.addEventListener("popstate",()=>this.handleRouteChange())}handleRouteChange(){this.dispatchEvent(new CustomEvent("route-changed",{detail:this.getCurrentRoute()}))}getCurrentRoute(){const e=new URL(window.location.href),n=e.pathname.replace(/^\/v3\//,"").split("/").filter(Boolean);let s="/";const r={};return n[0]==="feed"&&n[1]?(s="/feed",r.feedId=n[1]):n[0]==="tag"&&n[1]?(s="/tag",r.tagName=decodeURIComponent(n[1])):n[0]==="settings"&&(s="/settings"),{path:s,params:r,query:e.searchParams}}navigate(e,a){let n=`/v3${e}`;if(a){const s=new URLSearchParams(a);n+=`?${s.toString()}`}window.history.pushState({},"",n),this.handleRouteChange()}updateQuery(e){const a=new URL(window.location.href);for(const[n,s]of Object.entries(e))s?a.searchParams.set(n,s):a.searchParams.delete(n);window.history.pushState({},"",a.toString()),this.handleRouteChange()}}const o=new C;function P(t,e=!1){const a=new Date(t.publish_date).toLocaleDateString();return` -
  • - - - ${t.full_content||t.description?` -
    - ${t.full_content||t.description} -
    - `:""} -
  • - `}let c=null,p=null,b=null;function O(){p=document.querySelector("#app"),p&&(p.className=`theme-${i.theme} font-${i.fontTheme}`,p.innerHTML=` -
    - - - -
    -
    -
    -
    - `,M())}function M(){document.getElementById("search-input")?.addEventListener("input",s=>{const r=s.target.value;o.updateQuery({q:r})}),document.getElementById("logo-link")?.addEventListener("click",()=>o.navigate("/")),document.getElementById("logout-button")?.addEventListener("click",s=>{s.preventDefault(),V()}),document.getElementById("sidebar-toggle-btn")?.addEventListener("click",()=>{i.toggleSidebar()}),document.getElementById("sidebar-backdrop")?.addEventListener("click",()=>{i.setSidebarVisible(!1)}),window.addEventListener("resize",()=>{window.innerWidth>768&&!i.sidebarVisible&&i.setSidebarVisible(!0)}),document.querySelectorAll(".sidebar-section.collapsible h3").forEach(s=>{s.addEventListener("click",()=>{s.parentElement?.classList.toggle("collapsed")})}),document.getElementById("sidebar")?.addEventListener("click",s=>{const r=s.target,d=r.closest("a");if(!d){r.classList.contains("logo")&&(s.preventDefault(),o.navigate("/",{}));return}const u=d.getAttribute("data-nav"),f=Object.fromEntries(o.getCurrentRoute().query.entries());if(u==="filter"){s.preventDefault();const l=d.getAttribute("data-value");o.getCurrentRoute().path==="/settings"?o.navigate("/",{...f,filter:l}):o.updateQuery({filter:l})}else if(u==="tag"){s.preventDefault();const l=d.getAttribute("data-value");o.navigate(`/tag/${encodeURIComponent(l)}`,f)}else if(u==="feed"){s.preventDefault();const l=d.getAttribute("data-value");i.activeFeedId===parseInt(l)?o.navigate("/",f):o.navigate(`/feed/${l}`,f)}else u==="settings"&&(s.preventDefault(),o.getCurrentRoute().path==="/settings"?o.navigate("/",f):o.navigate("/settings",f));window.innerWidth<=768&&i.setSidebarVisible(!1)}),document.getElementById("content-area")?.addEventListener("click",s=>{const r=s.target,d=r.closest('[data-action="toggle-star"]');if(d){const m=d.closest("[data-id]");if(m){const v=parseInt(m.getAttribute("data-id"));Q(v)}return}const u=r.closest('[data-action="scrape"]');if(u){const m=u.closest("[data-id]");if(m){const v=parseInt(m.getAttribute("data-id"));j(v)}return}const f=r.closest('[data-action="open"]'),l=r.closest(".feed-item");if(l&&!f){const m=parseInt(l.getAttribute("data-id"));c=m,document.querySelectorAll(".feed-item").forEach(w=>{const A=parseInt(w.getAttribute("data-id")||"0");w.classList.toggle("selected",A===c)});const v=i.items.find(w=>w._id===m);v&&!v.read&&h(m,{read:!0})}})}function $(){const{feeds:t,activeFeedId:e}=i,a=document.getElementById("feed-list");a&&(a.innerHTML=t.map(n=>` -
  • - - ${n.title||n.url} - -
  • - `).join(""))}function k(){const{tags:t,activeTagName:e}=i,a=document.getElementById("tag-list");a&&(a.innerHTML=t.map(n=>` -
  • - - ${n.title} - -
  • - `).join(""))}function F(){const{filter:t}=i,e=document.getElementById("filter-list");e&&e.querySelectorAll("li").forEach(a=>{a.classList.toggle("active",a.getAttribute("data-filter")===t)})}function L(){const{items:t,loading:e}=i;b&&(b.disconnect(),b=null);const a=document.getElementById("content-area");if(!a||o.getCurrentRoute().path==="/settings")return;if(e&&t.length===0){a.innerHTML='

    Loading items...

    ';return}if(t.length===0){a.innerHTML='

    No items found.

    ';return}a.innerHTML=` - - ${i.hasMore?'
    Loading more...
    ':""} - `;const n=document.getElementById("load-more-sentinel");n&&new IntersectionObserver(r=>{r[0].isIntersecting&&!i.loading&&i.hasMore&&U()},{threshold:.1}).observe(n),b=new IntersectionObserver(s=>{s.forEach(r=>{if(r.isIntersecting){const d=r.target,u=parseInt(d.getAttribute("data-id")||"0");if(u){const f=i.items.find(l=>l._id===u);f&&!f.read&&(h(u,{read:!0}),b?.unobserve(d))}}})},{threshold:.5}),a.querySelectorAll(".feed-item").forEach(s=>b.observe(s))}function I(){const t=document.getElementById("content-area");t&&(t.innerHTML=` -
    -

    Settings

    - -
    -

    Add Feed

    -
    - - -
    -
    - -
    -

    Appearance

    -
    - -
    - - -
    -
    -
    - - -
    -
    - -
    -

    Manage Feeds

    -
      - ${i.feeds.map(e=>` -
    • -
      -
      ${e.title||e.url}
      -
      ${e.url}
      -
      -
      - - - -
      -
    • - `).join("")} -
    -
    - -
    -

    Data Management

    -
    - -
    - - -
    -
    -
    -
    - `,document.getElementById("theme-options")?.addEventListener("click",e=>{const a=e.target.closest("button");if(a){const n=a.getAttribute("data-theme");i.setTheme(n),I()}}),document.getElementById("font-selector")?.addEventListener("change",e=>{i.setFontTheme(e.target.value)}),document.getElementById("add-feed-btn")?.addEventListener("click",async()=>{const e=document.getElementById("new-feed-url"),a=e.value.trim();a&&(await x(a)?(e.value="",alert("Feed added successfully!"),y()):alert("Failed to add feed."))}),document.getElementById("export-opml-btn")?.addEventListener("click",()=>{window.location.href="/api/export/opml"}),document.getElementById("import-opml-file")?.addEventListener("change",async e=>{const a=e.target.files?.[0];a&&(await q(a)?(alert("OPML imported successfully! Crawling started."),y()):alert("Failed to import OPML."))}),document.querySelectorAll(".delete-feed-btn").forEach(e=>{e.addEventListener("click",async a=>{const n=parseInt(a.target.getAttribute("data-id"));confirm("Are you sure you want to delete this feed?")&&(await N(n),y(),await y(),I())})}),document.querySelectorAll(".update-feed-tag-btn").forEach(e=>{e.addEventListener("click",async a=>{const n=parseInt(a.target.getAttribute("data-id")),r=document.querySelector(`.feed-tag-input[data-id="${n}"]`).value.trim();await D(n,{category:r}),await y(),await _(),I(),alert("Feed updated")})}))}async function x(t){try{return(await g("/api/feed",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:t})})).ok}catch(e){return console.error("Failed to add feed",e),!1}}async function q(t){try{const e=new FormData;e.append("file",t),e.append("format","opml");const a=document.cookie.split("; ").find(s=>s.startsWith("csrf_token="))?.split("=")[1];return(await fetch("/api/import",{method:"POST",headers:{"X-CSRF-Token":a||""},body:e})).ok}catch(e){return console.error("Failed to import OPML",e),!1}}async function N(t){try{return(await g(`/api/feed/${t}`,{method:"DELETE"})).ok}catch(e){return console.error("Failed to delete feed",e),!1}}async function D(t,e){try{return(await g("/api/feed",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,_id:t})})).ok}catch(a){return console.error("Failed to update feed",a),!1}}async function Q(t){const e=i.items.find(a=>a._id===t);e&&h(t,{starred:!e.starred})}async function j(t){if(i.items.find(a=>a._id===t))try{const a=await g(`/api/item/${t}/content`);if(a.ok){const n=await a.json();n.full_content&&h(t,{full_content:n.full_content})}}catch(a){console.error("Failed to fetch full content",a)}}async function h(t,e){try{if((await g(`/api/item/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok){const n=i.items.find(s=>s._id===t);if(n){Object.assign(n,e);const s=document.querySelector(`.feed-item[data-id="${t}"]`);if(s){if(e.read!==void 0&&s.classList.toggle("read",e.read),e.starred!==void 0){const r=s.querySelector(".star-btn");r&&(r.classList.toggle("is-starred",e.starred),r.classList.toggle("is-unstarred",!e.starred),r.setAttribute("title",e.starred?"Unstar":"Star"))}e.full_content&&L()}}}}catch(a){console.error("Failed to update item",a)}}async function y(){const t=await g("/api/feed/");if(t.ok){const e=await t.json();i.setFeeds(e)}}async function _(){const t=await g("/api/tag");if(t.ok){const e=await t.json();i.setTags(e)}}async function E(t,e,a=!1){i.setLoading(!0);try{const n=new URLSearchParams;t&&n.append("feed_id",t),e&&n.append("tag",e),i.searchQuery&&n.append("q",i.searchQuery),(i.filter==="starred"||i.filter==="all")&&n.append("read_filter","all"),i.filter==="starred"&&n.append("starred","true"),a&&i.items.length>0&&n.append("max_id",String(i.items[i.items.length-1]._id));const s=await g(`/api/stream?${n.toString()}`);if(s.ok){const r=await s.json();i.setHasMore(r.length>=50),i.setItems(r,a)}}finally{i.setLoading(!1)}}async function U(){const t=o.getCurrentRoute();E(t.params.feedId,t.params.tagName,!0)}async function V(){await g("/api/logout",{method:"POST"}),window.location.href="/login/"}function S(){const t=o.getCurrentRoute(),e=t.query.get("filter");i.setFilter(e||"unread");const a=t.query.get("q");if(a!==null&&i.setSearchQuery(a),t.path==="/settings"){I();return}if(t.path==="/feed"&&t.params.feedId){const n=parseInt(t.params.feedId);i.setActiveFeed(n),E(t.params.feedId),document.getElementById("section-feeds")?.classList.remove("collapsed")}else t.path==="/tag"&&t.params.tagName?(i.setActiveTag(t.params.tagName),E(void 0,t.params.tagName),document.getElementById("section-tags")?.classList.remove("collapsed")):(i.setActiveFeed(null),i.setActiveTag(null),E())}window.addEventListener("keydown",t=>{if(!["INPUT","TEXTAREA"].includes(t.target.tagName))switch(t.key){case"j":T(1);break;case"k":T(-1);break;case"r":if(c){const e=i.items.find(a=>a._id===c);e&&h(e._id,{read:!e.read})}break;case"s":if(c){const e=i.items.find(a=>a._id===c);e&&h(e._id,{starred:!e.starred})}break;case"/":t.preventDefault(),document.getElementById("search-input")?.focus();break}});function T(t){if(i.items.length===0)return;const e=i.items.findIndex(n=>n._id===c);let a;if(e===-1?a=t>0?0:i.items.length-1:a=e+t,a>=0&&a{const r=parseInt(s.getAttribute("data-id")||"0");s.classList.toggle("selected",r===c)});const n=document.querySelector(`.feed-item[data-id="${c}"]`);n&&n.scrollIntoView({block:"start",behavior:"smooth"}),i.items[a].read||h(c,{read:!0})}}i.on("feeds-updated",$);i.on("tags-updated",k);i.on("active-feed-updated",$);i.on("active-tag-updated",k);i.on("filter-updated",F);i.on("search-updated",()=>{const t=document.getElementById("search-input");t&&t.value!==i.searchQuery&&(t.value=i.searchQuery),S()});i.on("theme-updated",()=>{p||(p=document.querySelector("#app")),p&&(p.className=`theme-${i.theme} font-${i.fontTheme}`)});i.on("sidebar-toggle",()=>{const t=document.querySelector(".layout");t&&(i.sidebarVisible?(t.classList.remove("sidebar-hidden"),t.classList.add("sidebar-visible")):(t.classList.remove("sidebar-visible"),t.classList.add("sidebar-hidden")))});i.on("items-updated",L);i.on("loading-state-changed",L);o.addEventListener("route-changed",S);window.app={navigate:t=>o.navigate(t)};async function H(){const t=await g("/api/auth");if(!t||t.status===401){window.location.href="/login/";return}O(),F();try{await Promise.all([y(),_()])}catch(e){console.error("Initial fetch failed",e)}S()}typeof window<"u"&&!window.__VITEST__&&H(); diff --git a/web/dist/v3/assets/index-M5xszonw.js b/web/dist/v3/assets/index-M5xszonw.js new file mode 100644 index 0000000..8348b8a --- /dev/null +++ b/web/dist/v3/assets/index-M5xszonw.js @@ -0,0 +1,143 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))n(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&n(d)}).observe(document,{childList:!0,subtree:!0});function a(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(s){if(s.ep)return;s.ep=!0;const r=a(s);fetch(s.href,r)}})();function R(t){const a=`; ${document.cookie}`.split(`; ${t}=`);if(a.length===2)return a.pop()?.split(";").shift()}async function g(t,e){const a=e?.method?.toUpperCase()||"GET",n=["POST","PUT","DELETE"].includes(a),s=new Headers(e?.headers||{});if(n){const r=R("csrf_token");r&&s.set("X-CSRF-Token",r)}return fetch(t,{...e,headers:s,credentials:"include"})}class B extends EventTarget{feeds=[];tags=[];items=[];activeFeedId=null;activeTagName=null;filter="unread";searchQuery="";loading=!1;hasMore=!0;theme=localStorage.getItem("neko-theme")||"light";fontTheme=localStorage.getItem("neko-font-theme")||"default";sidebarVisible=window.innerWidth>768;setFeeds(e){this.feeds=e,this.emit("feeds-updated")}setTags(e){this.tags=e,this.emit("tags-updated")}setItems(e,a=!1){a?this.items=[...this.items,...e]:this.items=e,this.emit("items-updated")}setActiveFeed(e){this.activeFeedId=e,this.activeTagName=null,this.emit("active-feed-updated")}setActiveTag(e){this.activeTagName=e,this.activeFeedId=null,this.emit("active-tag-updated")}setFilter(e){this.filter!==e&&(this.filter=e,this.emit("filter-updated"))}setSearchQuery(e){this.searchQuery!==e&&(this.searchQuery=e,this.emit("search-updated"))}setLoading(e){this.loading=e,this.emit("loading-state-changed")}setHasMore(e){this.hasMore=e}setTheme(e){this.theme=e,localStorage.setItem("neko-theme",e),this.emit("theme-updated")}setFontTheme(e){this.fontTheme=e,localStorage.setItem("neko-font-theme",e),this.emit("theme-updated")}setSidebarVisible(e){this.sidebarVisible=e,this.emit("sidebar-toggle")}toggleSidebar(){this.setSidebarVisible(!this.sidebarVisible)}emit(e,a){this.dispatchEvent(new CustomEvent(e,{detail:a}))}on(e,a){this.addEventListener(e,a)}}const i=new B;class C extends EventTarget{constructor(){super(),window.addEventListener("popstate",()=>this.handleRouteChange())}handleRouteChange(){this.dispatchEvent(new CustomEvent("route-changed",{detail:this.getCurrentRoute()}))}getCurrentRoute(){const e=new URL(window.location.href),n=e.pathname.replace(/^\/v3\//,"").split("/").filter(Boolean);let s="/";const r={};return n[0]==="feed"&&n[1]?(s="/feed",r.feedId=n[1]):n[0]==="tag"&&n[1]?(s="/tag",r.tagName=decodeURIComponent(n[1])):n[0]==="settings"&&(s="/settings"),{path:s,params:r,query:e.searchParams}}navigate(e,a){let n=`/v3${e}`;if(a){const s=new URLSearchParams(a);n+=`?${s.toString()}`}window.history.pushState({},"",n),this.handleRouteChange()}updateQuery(e){const a=new URL(window.location.href);for(const[n,s]of Object.entries(e))s?a.searchParams.set(n,s):a.searchParams.delete(n);window.history.pushState({},"",a.toString()),this.handleRouteChange()}}const o=new C;function P(t,e=!1){const a=new Date(t.publish_date).toLocaleDateString();return` +
  • + + + ${t.full_content||t.description?` +
    + ${t.full_content||t.description} +
    + `:""} +
  • + `}let c=null,p=null,b=null;function O(){p=document.querySelector("#app"),p&&(p.className=`theme-${i.theme} font-${i.fontTheme}`,p.innerHTML=` +
    + + + +
    +
    +
    +
    + `,M())}function M(){document.getElementById("search-input")?.addEventListener("input",s=>{const r=s.target.value;o.updateQuery({q:r})}),document.getElementById("logo-link")?.addEventListener("click",()=>o.navigate("/")),document.getElementById("logout-button")?.addEventListener("click",s=>{s.preventDefault(),V()}),document.getElementById("sidebar-toggle-btn")?.addEventListener("click",()=>{i.toggleSidebar()}),document.getElementById("sidebar-backdrop")?.addEventListener("click",()=>{i.setSidebarVisible(!1)}),window.addEventListener("resize",()=>{window.innerWidth>768&&!i.sidebarVisible&&i.setSidebarVisible(!0)}),document.querySelectorAll(".sidebar-section.collapsible h3").forEach(s=>{s.addEventListener("click",()=>{s.parentElement?.classList.toggle("collapsed")})}),document.getElementById("sidebar")?.addEventListener("click",s=>{const r=s.target,d=r.closest("a");if(!d){r.classList.contains("logo")&&(s.preventDefault(),o.navigate("/",{}));return}const u=d.getAttribute("data-nav"),f=Object.fromEntries(o.getCurrentRoute().query.entries());if(u==="filter"){s.preventDefault();const l=d.getAttribute("data-value");o.getCurrentRoute().path==="/settings"?o.navigate("/",{...f,filter:l}):o.updateQuery({filter:l})}else if(u==="tag"){s.preventDefault();const l=d.getAttribute("data-value");o.navigate(`/tag/${encodeURIComponent(l)}`,f)}else if(u==="feed"){s.preventDefault();const l=d.getAttribute("data-value");i.activeFeedId===parseInt(l)?o.navigate("/",f):o.navigate(`/feed/${l}`,f)}else u==="settings"&&(s.preventDefault(),o.getCurrentRoute().path==="/settings"?o.navigate("/",f):o.navigate("/settings",f));window.innerWidth<=768&&i.setSidebarVisible(!1)}),document.getElementById("content-area")?.addEventListener("click",s=>{const r=s.target,d=r.closest('[data-action="toggle-star"]');if(d){const m=d.closest("[data-id]");if(m){const v=parseInt(m.getAttribute("data-id"));Q(v)}return}const u=r.closest('[data-action="scrape"]');if(u){const m=u.closest("[data-id]");if(m){const v=parseInt(m.getAttribute("data-id"));j(v)}return}const f=r.closest('[data-action="open"]'),l=r.closest(".feed-item");if(l&&!f){const m=parseInt(l.getAttribute("data-id"));c=m,document.querySelectorAll(".feed-item").forEach(w=>{const A=parseInt(w.getAttribute("data-id")||"0");w.classList.toggle("selected",A===c)});const v=i.items.find(w=>w._id===m);v&&!v.read&&h(m,{read:!0})}})}function $(){const{feeds:t,activeFeedId:e}=i,a=document.getElementById("feed-list");a&&(a.innerHTML=t.map(n=>` +
  • + + ${n.title||n.url} + +
  • + `).join(""))}function k(){const{tags:t,activeTagName:e}=i,a=document.getElementById("tag-list");a&&(a.innerHTML=t.map(n=>` +
  • + + ${n.title} + +
  • + `).join(""))}function F(){const{filter:t}=i,e=document.getElementById("filter-list");e&&e.querySelectorAll("li").forEach(a=>{a.classList.toggle("active",a.getAttribute("data-filter")===t)})}function L(){const{items:t,loading:e}=i;b&&(b.disconnect(),b=null);const a=document.getElementById("content-area");if(!a||o.getCurrentRoute().path==="/settings")return;if(e&&t.length===0){a.innerHTML='

    Loading items...

    ';return}if(t.length===0){a.innerHTML='

    No items found.

    ';return}a.innerHTML=` + + ${i.hasMore?'
    Loading more...
    ':""} + `;const n=document.getElementById("load-more-sentinel");n&&new IntersectionObserver(r=>{r[0].isIntersecting&&!i.loading&&i.hasMore&&U()},{threshold:.1}).observe(n),b=new IntersectionObserver(s=>{s.forEach(r=>{if(r.isIntersecting){const d=r.target,u=parseInt(d.getAttribute("data-id")||"0");if(u){const f=i.items.find(l=>l._id===u);f&&!f.read&&(h(u,{read:!0}),b?.unobserve(d))}}})},{threshold:1}),a.querySelectorAll(".feed-item").forEach(s=>b.observe(s))}function I(){const t=document.getElementById("content-area");t&&(t.innerHTML=` +
    +

    Settings

    + +
    +

    Add Feed

    +
    + + +
    +
    + +
    +

    Appearance

    +
    + +
    + + +
    +
    +
    + + +
    +
    + +
    +

    Manage Feeds

    +
      + ${i.feeds.map(e=>` +
    • +
      +
      ${e.title||e.url}
      +
      ${e.url}
      +
      +
      + + + +
      +
    • + `).join("")} +
    +
    + +
    +

    Data Management

    +
    + +
    + + +
    +
    +
    +
    + `,document.getElementById("theme-options")?.addEventListener("click",e=>{const a=e.target.closest("button");if(a){const n=a.getAttribute("data-theme");i.setTheme(n),I()}}),document.getElementById("font-selector")?.addEventListener("change",e=>{i.setFontTheme(e.target.value)}),document.getElementById("add-feed-btn")?.addEventListener("click",async()=>{const e=document.getElementById("new-feed-url"),a=e.value.trim();a&&(await x(a)?(e.value="",alert("Feed added successfully!"),y()):alert("Failed to add feed."))}),document.getElementById("export-opml-btn")?.addEventListener("click",()=>{window.location.href="/api/export/opml"}),document.getElementById("import-opml-file")?.addEventListener("change",async e=>{const a=e.target.files?.[0];a&&(await q(a)?(alert("OPML imported successfully! Crawling started."),y()):alert("Failed to import OPML."))}),document.querySelectorAll(".delete-feed-btn").forEach(e=>{e.addEventListener("click",async a=>{const n=parseInt(a.target.getAttribute("data-id"));confirm("Are you sure you want to delete this feed?")&&(await N(n),y(),await y(),I())})}),document.querySelectorAll(".update-feed-tag-btn").forEach(e=>{e.addEventListener("click",async a=>{const n=parseInt(a.target.getAttribute("data-id")),r=document.querySelector(`.feed-tag-input[data-id="${n}"]`).value.trim();await D(n,{category:r}),await y(),await _(),I(),alert("Feed updated")})}))}async function x(t){try{return(await g("/api/feed",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:t})})).ok}catch(e){return console.error("Failed to add feed",e),!1}}async function q(t){try{const e=new FormData;e.append("file",t),e.append("format","opml");const a=document.cookie.split("; ").find(s=>s.startsWith("csrf_token="))?.split("=")[1];return(await fetch("/api/import",{method:"POST",headers:{"X-CSRF-Token":a||""},body:e})).ok}catch(e){return console.error("Failed to import OPML",e),!1}}async function N(t){try{return(await g(`/api/feed/${t}`,{method:"DELETE"})).ok}catch(e){return console.error("Failed to delete feed",e),!1}}async function D(t,e){try{return(await g("/api/feed",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,_id:t})})).ok}catch(a){return console.error("Failed to update feed",a),!1}}async function Q(t){const e=i.items.find(a=>a._id===t);e&&h(t,{starred:!e.starred})}async function j(t){if(i.items.find(a=>a._id===t))try{const a=await g(`/api/item/${t}/content`);if(a.ok){const n=await a.json();n.full_content&&h(t,{full_content:n.full_content})}}catch(a){console.error("Failed to fetch full content",a)}}async function h(t,e){try{if((await g(`/api/item/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok){const n=i.items.find(s=>s._id===t);if(n){Object.assign(n,e);const s=document.querySelector(`.feed-item[data-id="${t}"]`);if(s){if(e.read!==void 0&&s.classList.toggle("read",e.read),e.starred!==void 0){const r=s.querySelector(".star-btn");r&&(r.classList.toggle("is-starred",e.starred),r.classList.toggle("is-unstarred",!e.starred),r.setAttribute("title",e.starred?"Unstar":"Star"))}e.full_content&&L()}}}}catch(a){console.error("Failed to update item",a)}}async function y(){const t=await g("/api/feed/");if(t.ok){const e=await t.json();i.setFeeds(e)}}async function _(){const t=await g("/api/tag");if(t.ok){const e=await t.json();i.setTags(e)}}async function E(t,e,a=!1){i.setLoading(!0);try{const n=new URLSearchParams;t&&n.append("feed_id",t),e&&n.append("tag",e),i.searchQuery&&n.append("q",i.searchQuery),(i.filter==="starred"||i.filter==="all")&&n.append("read_filter","all"),i.filter==="starred"&&n.append("starred","true"),a&&i.items.length>0&&n.append("max_id",String(i.items[i.items.length-1]._id));const s=await g(`/api/stream?${n.toString()}`);if(s.ok){const r=await s.json();i.setHasMore(r.length>=50),i.setItems(r,a)}}finally{i.setLoading(!1)}}async function U(){const t=o.getCurrentRoute();E(t.params.feedId,t.params.tagName,!0)}async function V(){await g("/api/logout",{method:"POST"}),window.location.href="/login/"}function S(){const t=o.getCurrentRoute(),e=t.query.get("filter");i.setFilter(e||"unread");const a=t.query.get("q");if(a!==null&&i.setSearchQuery(a),t.path==="/settings"){I();return}if(t.path==="/feed"&&t.params.feedId){const n=parseInt(t.params.feedId);i.setActiveFeed(n),E(t.params.feedId),document.getElementById("section-feeds")?.classList.remove("collapsed")}else t.path==="/tag"&&t.params.tagName?(i.setActiveTag(t.params.tagName),E(void 0,t.params.tagName),document.getElementById("section-tags")?.classList.remove("collapsed")):(i.setActiveFeed(null),i.setActiveTag(null),E())}window.addEventListener("keydown",t=>{if(!["INPUT","TEXTAREA"].includes(t.target.tagName))switch(t.key){case"j":T(1);break;case"k":T(-1);break;case"r":if(c){const e=i.items.find(a=>a._id===c);e&&h(e._id,{read:!e.read})}break;case"s":if(c){const e=i.items.find(a=>a._id===c);e&&h(e._id,{starred:!e.starred})}break;case"/":t.preventDefault(),document.getElementById("search-input")?.focus();break}});function T(t){if(i.items.length===0)return;const e=i.items.findIndex(n=>n._id===c);let a;if(e===-1?a=t>0?0:i.items.length-1:a=e+t,a>=0&&a{const r=parseInt(s.getAttribute("data-id")||"0");s.classList.toggle("selected",r===c)});const n=document.querySelector(`.feed-item[data-id="${c}"]`);n&&n.scrollIntoView({block:"start",behavior:"smooth"}),i.items[a].read||h(c,{read:!0})}}i.on("feeds-updated",$);i.on("tags-updated",k);i.on("active-feed-updated",$);i.on("active-tag-updated",k);i.on("filter-updated",F);i.on("search-updated",()=>{const t=document.getElementById("search-input");t&&t.value!==i.searchQuery&&(t.value=i.searchQuery),S()});i.on("theme-updated",()=>{p||(p=document.querySelector("#app")),p&&(p.className=`theme-${i.theme} font-${i.fontTheme}`)});i.on("sidebar-toggle",()=>{const t=document.querySelector(".layout");t&&(i.sidebarVisible?(t.classList.remove("sidebar-hidden"),t.classList.add("sidebar-visible")):(t.classList.remove("sidebar-visible"),t.classList.add("sidebar-hidden")))});i.on("items-updated",L);i.on("loading-state-changed",L);o.addEventListener("route-changed",S);window.app={navigate:t=>o.navigate(t)};async function H(){const t=await g("/api/auth");if(!t||t.status===401){window.location.href="/login/";return}O(),F();try{await Promise.all([y(),_()])}catch(e){console.error("Initial fetch failed",e)}S()}typeof window<"u"&&!window.__VITEST__&&H(); diff --git a/web/dist/v3/index.html b/web/dist/v3/index.html index 87dfe68..c48a396 100644 --- a/web/dist/v3/index.html +++ b/web/dist/v3/index.html @@ -5,7 +5,7 @@ neko - + -- cgit v1.2.3