Bookmarks on public/semi activities, surfaced on /home
Logged-in users can star a public or semi activity to save it for
later. Bookmarked rows float to the top of the user's dashboard in a
dedicated "Bokmerker" section. The same row still appears in its
visibility section below — bookmarking doesn't remove anything; it
just adds a fast lane.
Schema:
- bookmarks(user_id, activity_id, created_at) with composite PK
- Both FKs CASCADE so user deletion or activity deletion sweeps
bookmarks automatically
Wire/types: ActivityPublic/Semi/Private all gain `viewer_bookmarked:
boolean` for type uniformity. Private rows always carry false (the
owner already has direct access; bookmarking own private items would
be redundant), and the bookmark endpoints reject visibility='private'
with cannot_bookmark_private. Anonymous viewers (public-list endpoint)
get false too.
Server:
- viewerBookmarked() helper next to heartsFor() — same shape
- serialize() includes the field
- POST/DELETE /api/activities/:id/bookmark, idempotent via
INSERT OR IGNORE / DELETE; mirrors the heart endpoints
Frontend:
- ActivityRow gets an "☆ Bokmerk" / "★ Bokmerket" toggle next to
the heart button. Uses the same optimistic local-override pattern
so the UI feels instant.
- Home renders a "Bokmerker" section at the top when bookmarked
rows exist. publicOnly mode (the "/" landing) skips it — the
field is always false there.
26 tests still pass; typecheck clean.
This commit is contained in:
parent
3215917b7a
commit
f0ce5e9680
7 changed files with 127 additions and 8 deletions
|
|
@ -195,6 +195,8 @@ export interface ActivityPublic {
|
|||
heart_count: number;
|
||||
/** True when the authenticated viewer has hearted this activity. */
|
||||
viewer_hearted: boolean;
|
||||
/** True when the authenticated viewer has bookmarked this activity. */
|
||||
viewer_bookmarked: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
@ -215,6 +217,7 @@ export interface ActivitySemi {
|
|||
scheduled_at: number | null;
|
||||
heart_count: number;
|
||||
viewer_hearted: boolean;
|
||||
viewer_bookmarked: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
@ -225,11 +228,12 @@ export interface ActivityPrivate {
|
|||
owner_id: string; // always you — server only returns your private rows
|
||||
ciphertext: string; // base64
|
||||
nonce: string; // base64
|
||||
// Always 0 / false for private rows — hearts don't apply (nobody else sees
|
||||
// them). Kept in the type so the client doesn't need a discriminator check
|
||||
// before reading the field.
|
||||
// Always 0 / false for private rows — hearts and bookmarks don't apply
|
||||
// (nobody else sees them; the owner already has direct access). Kept in
|
||||
// the type so the client doesn't need a discriminator check before reading.
|
||||
heart_count: number;
|
||||
viewer_hearted: boolean;
|
||||
viewer_bookmarked: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue