diff options
Diffstat (limited to 'frontend-vanilla/src')
| -rw-r--r-- | frontend-vanilla/src/components/FeedItem.ts | 11 | ||||
| -rw-r--r-- | frontend-vanilla/src/main.test.ts | 21 | ||||
| -rw-r--r-- | frontend-vanilla/src/main.ts | 19 |
3 files changed, 37 insertions, 14 deletions
diff --git a/frontend-vanilla/src/components/FeedItem.ts b/frontend-vanilla/src/components/FeedItem.ts index e58aac8..8f607af 100644 --- a/frontend-vanilla/src/components/FeedItem.ts +++ b/frontend-vanilla/src/components/FeedItem.ts @@ -1,5 +1,9 @@ import type { Item } from '../types'; +// NOTE: The "text" scrape button (data-action="scrape") was removed from the +// template. The backend endpoint and scrapeItem() handler in main.ts are still +// intact if we want to bring it back with proper styling later. + export function createFeedItem(item: Item): string { const date = new Date(item.publish_date).toLocaleDateString(); return ` @@ -17,13 +21,6 @@ export function createFeedItem(item: Item): string { ${date} ${item.feed_title ? ` - ${item.feed_title}` : ''} </a> - <div class="item-actions" style="display: inline-block; float: right;"> - ${!item.full_content ? ` - <button class="scrape-btn" title="Load Full Content" data-action="scrape"> - text - </button> - ` : ''} - </div> </div> ${(item.full_content || item.description) ? ` <div class="item-description"> diff --git a/frontend-vanilla/src/main.test.ts b/frontend-vanilla/src/main.test.ts index 2715b5c..2d74ea5 100644 --- a/frontend-vanilla/src/main.test.ts +++ b/frontend-vanilla/src/main.test.ts @@ -188,6 +188,27 @@ describe('main application logic', () => { })); }); + it('should handle star toggle when _id is a string (API returns string)', async () => { + renderLayout(); + // The Go API returns _id as a string due to json:",string" tag. + // fetchItems normalizes this, but verify the flow works end-to-end. + const mockItem = { _id: "5" as any, title: 'String ID Item', starred: true, publish_date: '2023-01-01' } as any; + store.setItems([mockItem]); + renderItems(); + + vi.mocked(apiFetch).mockResolvedValue({ ok: true } as Response); + + const starBtn = document.querySelector('[data-action="toggle-star"]') as HTMLElement; + starBtn.click(); + + // parseInt in the click handler produces number 5, but store has string "5". + // This should still work because toggleStar finds the item. + expect(apiFetch).toHaveBeenCalledWith(expect.stringContaining('/api/item/5'), expect.objectContaining({ + method: 'PUT', + body: expect.stringContaining('"starred":false') + })); + }); + it('should handle theme change in settings', () => { renderLayout(); renderSettings(); diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts index b59d185..5d67515 100644 --- a/frontend-vanilla/src/main.ts +++ b/frontend-vanilla/src/main.ts @@ -258,7 +258,7 @@ export function attachLayoutListeners() { const id = parseInt(itemRow.getAttribute('data-id')!); activeItemId = id; - const item = store.items.find(i => i._id === id); + const item = store.items.find(i => Number(i._id) === Number(id)); if (item && !item.read) { updateItem(id, { read: true }); } @@ -361,7 +361,7 @@ function checkReadItems(scrollRoot?: HTMLElement) { const id = parseInt(idAttr); // Safety check: skip if store already says it's read (though unread class implies not) - const item = store.items.find(i => i._id === id); + const item = store.items.find(i => Number(i._id) === Number(id)); if (item?.read) continue; const rect = el.getBoundingClientRect(); @@ -674,14 +674,14 @@ export async function updateFeed(id: number, updates: Partial<Feed>): Promise<bo // --- Data Actions --- export async function toggleStar(id: number) { - const item = store.items.find(i => i._id === id); + const item = store.items.find(i => Number(i._id) === Number(id)); if (item) { updateItem(id, { starred: !item.starred }); } } export async function scrapeItem(id: number) { - const item = store.items.find(i => i._id === id); + const item = store.items.find(i => Number(i._id) === Number(id)); if (!item) return; try { @@ -775,6 +775,11 @@ export async function fetchItems(feedId?: string, tagName?: string, append: bool const res = await apiFetch(`/api/stream?${params.toString()}`); if (res.ok) { const items = await res.json(); + // The Go API returns _id as a string (json:",string" tag). Coerce to + // number so DOM parseInt comparisons using === work everywhere. + for (const item of items) { + item._id = Number(item._id); + } store.setHasMore(items.length > 0); store.setItems(items, append); } @@ -840,13 +845,13 @@ window.addEventListener('keydown', (e) => { break; case 'r': if (activeItemId) { - const item = store.items.find(i => i._id === activeItemId); + const item = store.items.find(i => Number(i._id) === Number(activeItemId)); if (item) updateItem(item._id, { read: !item.read }); } break; case 's': if (activeItemId) { - const item = store.items.find(i => i._id === activeItemId); + const item = store.items.find(i => Number(i._id) === Number(activeItemId)); if (item) updateItem(item._id, { starred: !item.starred }); } break; @@ -859,7 +864,7 @@ window.addEventListener('keydown', (e) => { function navigateItems(direction: number) { if (store.items.length === 0) return; - const currentIndex = store.items.findIndex(i => i._id === activeItemId); + const currentIndex = store.items.findIndex(i => Number(i._id) === Number(activeItemId)); let nextIndex; if (currentIndex === -1) { |
