aboutsummaryrefslogtreecommitdiffstats
path: root/frontend-vanilla/src/main.ts
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-16 07:38:51 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-16 07:44:50 -0800
commite7516c4124d033332922db91a3dc0e8dc417c2aa (patch)
tree91842fb5ae12815563752b8eb2c541ed0227e705 /frontend-vanilla/src/main.ts
parent7196992916b42fe6180c0d05331716c52fac79b0 (diff)
downloadneko-e7516c4124d033332922db91a3dc0e8dc417c2aa.tar.gz
neko-e7516c4124d033332922db91a3dc0e8dc417c2aa.tar.bz2
neko-e7516c4124d033332922db91a3dc0e8dc417c2aa.zip
Fix scroll mark as read in V3 UI
- Close NK-k2fh32: scroll mark as read broken in V3 UI - Add IntersectionObserver to item list in renderItems - Add test case ensuring apiFetch is called when item intersects
Diffstat (limited to 'frontend-vanilla/src/main.ts')
-rw-r--r--frontend-vanilla/src/main.ts26
1 files changed, 25 insertions, 1 deletions
diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts
index 93bee63..b167a18 100644
--- a/frontend-vanilla/src/main.ts
+++ b/frontend-vanilla/src/main.ts
@@ -18,6 +18,7 @@ let activeItemId: number | null = null;
// Cache elements (initialized in renderLayout)
let appEl: HTMLDivElement | null = null;
+let itemObserver: IntersectionObserver | null = null;
// Initial Layout (v2-style 2-pane)
export function renderLayout() {
@@ -216,6 +217,11 @@ export function renderFilters() {
export function renderItems() {
const { items, loading } = store;
+
+ if (itemObserver) {
+ itemObserver.disconnect();
+ itemObserver = null;
+ }
const contentArea = document.getElementById('content-area');
if (!contentArea || router.getCurrentRoute().path === '/settings') return;
@@ -246,6 +252,25 @@ export function renderItems() {
}, { threshold: 0.1 });
observer.observe(sentinel);
}
+
+ // Setup item observer for marking read
+ itemObserver = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ const target = entry.target as HTMLElement;
+ const id = parseInt(target.getAttribute('data-id') || '0');
+ if (id) {
+ const item = store.items.find(i => i._id === id);
+ if (item && !item.read) {
+ updateItem(id, { read: true });
+ itemObserver?.unobserve(target);
+ }
+ }
+ }
+ });
+ }, { threshold: 0.5 });
+
+ contentArea.querySelectorAll('.feed-item').forEach(el => itemObserver!.observe(el));
}
export function renderSettings() {
@@ -623,7 +648,6 @@ window.app = {
};
// Start
-// Start
export async function init() {
const authRes = await apiFetch('/api/auth');
if (!authRes || authRes.status === 401) {