Read-it-later social network

update styles, implement explore home page

Changed files
+73 -6
src
lib
routes
+3 -3
src/lib/components/BookmarkCard.svelte
··· 11 11 let { isOwner, bookmark, onTagClick, onTagDeleteClick }: BookmarkCardProps = $props(); 12 12 </script> 13 13 14 - <article class="flex flex-col gap-4 border border-dashed hover:border-solid px-4 py-3 w-fit"> 15 - <a href={bookmark.subject} class="hover:cursor-pointer text-sm">{bookmark.subject}</a> 14 + <article class="flex flex-col gap-4 border border-dashed hover:border-solid hover:shadow-lg px-4 py-3 w-fit"> 15 + <a href={bookmark.subject} class="hover:cursor-pointer text-xl visited:text-violet-600">{bookmark.subject}</a> 16 16 {#if bookmark.tags && bookmark.tags.length > 0} 17 17 <div class="flex gap-5"> 18 18 {#each bookmark.tags as tag} ··· 27 27 {/if} 28 28 <button 29 29 onclick={() => onTagClick(tag)} 30 - class="bg-gray-200 w-fit px-2 py-1 hover:cursor-pointer" 30 + class="bg-gray-200 w-fit px-2 py-1 hover:cursor-pointer font-comico text-sm" 31 31 > 32 32 {tag} 33 33 </button>
-2
src/lib/utils.ts
··· 1 - import { atclient } from "./atproto"; 2 - 3 1 // --- UTILITIES --- 4 2 export type CommonSliceFields = { 5 3 indexedAt: string;
+59 -1
src/routes/+page.svelte
··· 1 + <script lang="ts"> 2 + import BookmarkCard from "$lib/components/BookmarkCard.svelte"; 3 + import { getAllBookmarks } from "./api/bookmarks/data.remote"; 4 + 5 + let { data } = $props(); 6 + let cursor = $state(""); 7 + const userBookmarksQuery = $derived(getAllBookmarks({ cursor })); 8 + 9 + let query = $state(""); 10 + let filterTags = $state<string[]>([]); 11 + 12 + function onTagClick(tag: string) { 13 + const index = filterTags.findIndex((t) => t === tag); 14 + if (index >= 0) { filterTags.splice(index, 1); } 15 + else { filterTags.push(tag); 16 + } 17 + } 18 + 19 + function onTagDeleteClick(tag: string) { 20 + console.log("DELETE", tag); 21 + } 22 + </script> 23 + 1 24 <h1 class="text-3xl font-bold font-comico">explore</h1> 2 - <p>coming soon...</p> 25 + 26 + {#if userBookmarksQuery.loading} 27 + <p>Loading...</p> 28 + {:else if userBookmarksQuery.error} 29 + <p>Error</p> 30 + {:else} 31 + {@const { cursor: returnedCursor, bookmarks } = userBookmarksQuery.current || { cursor: "", bookmarks: []}} 32 + 33 + <div class="sticky top-0 flex flex-col gap-4 pt-4 bg-white z-50"> 34 + <menu class="flex justify-between font-comico"> 35 + <div class="flex gap-4"> 36 + <label class="flex items-center gap-2"> 37 + Search term: 38 + <input type="text" bind:value={query} class="font-neco border px-2 py-1" placeholder="recipe" /> 39 + </label> 40 + 41 + <label class="flex items-center gap-2"> 42 + Tags: 43 + {#each filterTags as filtered} 44 + <button onclick={() => onTagClick(filtered)}>{filtered}</button> 45 + {/each} 46 + </label> 47 + <button onclick={() => userBookmarksQuery.refresh()}>Refresh</button> 48 + </div> 49 + </menu> 50 + <hr /> 51 + </div> 52 + 53 + <div class="flex flex-wrap gap-4"> 54 + {#each bookmarks as bookmark} 55 + {#if bookmark.subject.includes(query) && (bookmark.tags?.some(t => filterTags.length > 0 ? filterTags.includes(t) : true))} 56 + <BookmarkCard isOwner={false} {bookmark} {onTagClick} {onTagDeleteClick} /> 57 + {/if} 58 + {/each} 59 + </div> 60 + {/if}
+11
src/routes/api/bookmarks/data.remote.ts
··· 23 23 24 24 return { cursor: data.cursor, bookmarks: data.records.map((r) => r.value )}; 25 25 }); 26 + 27 + 28 + const GetAllBookmarksValidator = v.object({ 29 + cursor: v.optional(v.string()) 30 + }); 31 + 32 + export const getAllBookmarks = query(GetAllBookmarksValidator, async ({ cursor }) => { 33 + const data = await LexiconBookmarkSlicesAPI.getList({ cursor }); 34 + 35 + return { cursor: data.cursor, bookmarks: data.records.map((r) => r.value )}; 36 + });