diff --git a/feeds/service.go b/feeds/service.go index 8a2db61..1c91193 100644 --- a/feeds/service.go +++ b/feeds/service.go @@ -224,11 +224,11 @@ func (s *Service) MarkEntryAsRead(id string) error { // MarkAllEntriesAsRead marks all entries as read, optionally filtered by feed ID func (s *Service) MarkAllEntriesAsRead(feedID string) error { query := s.db.Model(&Entry{}).Where("read_at IS NULL") - + if feedID != "" { query = query.Where("feed_id = ?", feedID) } - + return query.Update("read_at", time.Now()).Error } diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 8725d8e..86097e8 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -19,7 +19,7 @@ }); </script> -<nav class="navbar is-primary" role="navigation" aria-label="main navigation"> +<nav class="navbar is-primary" aria-label="main navigation"> <div class="container"> <div class="navbar-brand"> <a class="navbar-item" href="/"> @@ -28,11 +28,12 @@ <a role="button" + href="#top" class="navbar-burger" class:is-active={isActive} aria-label="menu" aria-expanded="false" - on:click|stopPropagation={toggleMenu} + onclick={toggleMenu} > <span aria-hidden="true"></span> <span aria-hidden="true"></span> diff --git a/frontend/src/lib/feeds.ts b/frontend/src/lib/feeds.ts index 74f18fb..63d6052 100644 --- a/frontend/src/lib/feeds.ts +++ b/frontend/src/lib/feeds.ts @@ -172,7 +172,7 @@ function createEntriesStore() { if (!response.ok) throw new Error('Failed to load entries'); const data = await response.json(); - const entries = data.map((entry: any) => ({ + const entries = data.map((entry: FeedEntry) => ({ ...entry, published: entry.published ? new Date(entry.published) : null, updated: entry.updated ? new Date(entry.updated) : null, diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index eff4330..ad17b4a 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -20,9 +20,20 @@ description: item.content, timestamp: new Date(item.createdAt), actions: [ + { + icon: 'eye', + label: 'View', + href: `/notes/${item.id}` + }, + { + icon: 'edit', + label: 'Edit', + href: `/notes/${item.id}?edit=true` + }, { icon: 'trash', label: 'Delete', + isDangerous: true, onClick: async () => { await fetch(`/api/notes/${item.id}`, { method: 'DELETE' }); notes = notes.filter((n) => n.id !== item.id); diff --git a/frontend/src/routes/feeds/[id]/+page.svelte b/frontend/src/routes/feeds/[id]/+page.svelte index 9a51656..76df8e1 100644 --- a/frontend/src/routes/feeds/[id]/+page.svelte +++ b/frontend/src/routes/feeds/[id]/+page.svelte @@ -171,7 +171,9 @@ <ul> <li><a href="/feeds">Feeds</a></li> <li><a href="/feeds/list">Manage Feeds</a></li> - <li class="is-active"><a href="#" aria-current="page">{feed?.title || 'Feed'}</a></li> + <li class="is-active"> + <a href="#top" aria-current="page">{feed?.title || 'Feed'}</a> + </li> </ul> </nav> </div> diff --git a/frontend/src/routes/feeds/entries/[id]/+page.svelte b/frontend/src/routes/feeds/entries/[id]/+page.svelte index ee31a8b..2c72bd6 100644 --- a/frontend/src/routes/feeds/entries/[id]/+page.svelte +++ b/frontend/src/routes/feeds/entries/[id]/+page.svelte @@ -3,6 +3,7 @@ import { entries, feeds } from '$lib/feeds'; import type { FeedEntry, Feed } from '$lib/types'; import { page } from '$app/stores'; + import { goto } from '$app/navigation'; let entry: FeedEntry | null = null; let feed: Feed | null = null; @@ -29,7 +30,9 @@ // Load feed info const feedsList = await feeds.load(); - feed = feedsList.find((f) => f.id === entry.feedId) || null; + // We've already checked that entry is not null + const currentEntry = entry; + feed = feedsList.find((f) => f.id === currentEntry.feedId) || null; } } catch (e) { error = e instanceof Error ? e.message : 'Failed to load entry'; @@ -53,6 +56,20 @@ isLoadingFullContent = false; } } + + function createNote() { + if (!entry) return; + + const title = `Highlights from ${entry.title}`; + const content = `[Original Feed Entry](/feeds/entries/${entry.id})\n\n${entry.fullContent || entry.summary || ''}`; + + const params = new URLSearchParams({ + title, + content + }); + + goto(`/notes/new?${params.toString()}`); + } </script> <div class="container mt-4"> @@ -62,7 +79,7 @@ {#if feed} <li><a href="/feeds/{feed.id}">{feed.title}</a></li> {/if} - <li class="is-active"><a href="#" aria-current="page">{entry?.title || 'Entry'}</a></li> + <li class="is-active"><a href="#top" aria-current="page">{entry?.title || 'Entry'}</a></li> </ul> </nav> @@ -136,11 +153,20 @@ </button> </div> {/if} + <div class="level-item"> + <button class="button is-small is-info" on:click={createNote}> + <span class="icon"> + <i class="fas fa-sticky-note"></i> + </span> + <span>Create Note</span> + </button> + </div> </div> </div> {#if entry.summary && !entry.fullContent} <div class="content mt-4"> + <!-- eslint-disable-next-line svelte/no-at-html-tags --> {@html entry.summary} </div> <div class="notification is-info is-light"> @@ -148,6 +174,7 @@ </div> {:else if entry.fullContent} <div class="content mt-4"> + <!-- eslint-disable-next-line svelte/no-at-html-tags --> {@html entry.fullContent} </div> {:else} diff --git a/frontend/src/routes/notes/new/+page.svelte b/frontend/src/routes/notes/new/+page.svelte index 5625c1a..82e21f9 100644 --- a/frontend/src/routes/notes/new/+page.svelte +++ b/frontend/src/routes/notes/new/+page.svelte @@ -3,10 +3,18 @@ import { notes } from '$lib'; import { goto } from '$app/navigation'; import Navigation from '$lib/components/Navigation.svelte'; - let { data } = $props(); + + interface PageData { + props: { + prefilledTitle: string; + prefilledContent?: string; + }; + } + + let { data } = $props<{ data: PageData }>(); let title = $state(data.props.prefilledTitle); - let content = $state(''); + let content = $state(data.props.prefilledContent || ''); async function handleSave() { if (!title || !content) return; diff --git a/frontend/src/routes/notes/new/+page.ts b/frontend/src/routes/notes/new/+page.ts index 2b0b889..2597b31 100644 --- a/frontend/src/routes/notes/new/+page.ts +++ b/frontend/src/routes/notes/new/+page.ts @@ -2,10 +2,13 @@ import type { PageLoad } from './$types'; export const load: PageLoad = async ({ url }) => { const title = url.searchParams.get('title') || ''; + const content = url.searchParams.get('content') || ''; + return { status: 'success', props: { - prefilledTitle: title + prefilledTitle: title, + prefilledContent: content } }; }; diff --git a/frontend/src/routes/readlist/[id]/+page.svelte b/frontend/src/routes/readlist/[id]/+page.svelte index 48249d0..f9b6d70 100644 --- a/frontend/src/routes/readlist/[id]/+page.svelte +++ b/frontend/src/routes/readlist/[id]/+page.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import { readlist } from '$lib/readlist'; import type { ReadLaterItem } from '$lib/types'; + import { goto } from '$app/navigation'; export let data: { id: string }; let id = data.id; @@ -32,6 +33,20 @@ error = e instanceof Error ? e.message : 'Failed to load item'; } }); + + function createNote() { + if (!item) return; + + const title = `Highlights from ${item.title}`; + const content = `[Original Link](/readlist/${item.id})\n\n${item.content || ''}`; + + const params = new URLSearchParams({ + title, + content + }); + + goto(`/notes/new?${params.toString()}`); + } </script> <div class="container"> @@ -78,6 +93,14 @@ </div> </div> <div class="level-right"> + <div class="level-item"> + <button class="button is-info" onclick={() => createNote()}> + <span class="icon"> + <i class="fas fa-sticky-note"></i> + </span> + <span>Create Note</span> + </button> + </div> <div class="level-item"> <button class="button is-danger" onclick={() => item && readlist.delete(item.id)}> <span class="icon">