Read-it-later social network

move publicationsQuery to /explore, show subscriptions in /home

authored by zeu.dev and committed by tangled.org 3981c57c ddbad17f

+260 -129
+10 -3
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import '../app.css'; 3 3 import { page } from '$app/state'; 4 - import { setContext } from 'svelte'; 4 + import { onMount, setContext } from 'svelte'; 5 5 import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query"; 6 6 import { SvelteQueryDevtools } from "@tanstack/svelte-query-devtools"; 7 + import { goto } from '$app/navigation'; 7 8 8 9 let { data, children } = $props(); 9 10 const { atclient, user } = data; ··· 31 32 } 32 33 } 33 34 }); 35 + 36 + onMount(() => { 37 + if (user) { 38 + goto("/home"); 39 + } 40 + }); 34 41 </script> 35 42 36 43 <QueryClientProvider client={queryClient}> ··· 41 48 42 49 <div class="flex gap-4 items-center flex-wrap"> 43 50 <nav class="flex gap-4 flex-wrap items-center px-3 py-1.5"> 44 - <a href="/" class="hover:text-shadow-sm" title="explore" aria-label="explore">🛰️ explore</a> 51 + <a href="/explore" class="hover:text-shadow-sm" title="explore" aria-label="explore">🛰️ explore</a> 45 52 {#if user} 46 - <p>{user.handle}</p> 53 + <a href="/home" class="hover:text-shadow-sm" title="explore" aria-label="explore">🏠 {user.handle}</a> 47 54 {/if} 48 55 </nav> 49 56 {#if user}
+1 -126
src/routes/+page.svelte
··· 1 - <script lang="ts"> 2 - import { Debounced } from "runed"; 3 - import type { PublicationNode } from '$lib/utils'; 4 - import { createInfiniteQuery } from '@tanstack/svelte-query'; 5 - import PublicationCard from '$lib/components/PublicationCard.svelte'; 6 - 7 - let { data } = $props(); 8 - let { atclient, user } = data; 9 - 10 - let page = $state(0); 11 - let searchTerm = $state(""); 12 - const debouncedSearchTerm = new Debounced(() => searchTerm, 500); 13 - 14 - const publicationsQuery = createInfiniteQuery(() => ({ 15 - queryKey: ["publications", debouncedSearchTerm.current || undefined], 16 - queryFn: async ({ pageParam }) => { 17 - const query = ` 18 - query GetPublications { 19 - siteStandardPublication(first: 20, after: "${pageParam}", ${debouncedSearchTerm.current && `where: { 20 - or: [{ 21 - name: { contains: "${debouncedSearchTerm.current}" } 22 - }, { 23 - actorHandle: { contains: "${debouncedSearchTerm.current}" } 24 - }] 25 - }`}) { 26 - edges {} 27 - pageInfo { 28 - hasNextPage 29 - endCursor 30 - } 31 - } 32 - } 33 - `; 34 - const data = await atclient.publicQuery(query); 35 - return data as { 36 - siteStandardPublication: { 37 - edges: { node: PublicationNode, cursor: string }[], 38 - pageInfo: { 39 - hasNextPage: boolean; 40 - endCursor: string; 41 - } 42 - } 43 - } 44 - }, 45 - initialPageParam: "", 46 - getNextPageParam: (lastPage) => lastPage.siteStandardPublication.pageInfo.endCursor, 47 - select: (data) => { 48 - const items = data.pages.map((page) => page.siteStandardPublication.edges).flat(); 49 - const nodes = items.map((i) => i.node); 50 - return nodes; 51 - } 52 - })); 53 - 54 - let currentPage = $derived(publicationsQuery.data?.slice(page*20, (page*20) + 20)); 55 - </script> 56 - 57 - <menu> 58 - <label> 59 - Search: 60 - <input bind:value={searchTerm} class="border" /> 61 - </label> 62 - <button 63 - onclick={() => { 64 - if (page > 0) { 65 - page--; 66 - } 67 - }} 68 - class="border" 69 - > 70 - Prev Page 71 - </button> 72 - <number>{page + 1}</number> 73 - {#if publicationsQuery.hasNextPage} 74 - <button 75 - onclick={() => { 76 - page++; 77 - if ((page * 20) + 20 > (publicationsQuery.data?.length || 0)) { 78 - publicationsQuery.fetchNextPage(); 79 - } 80 - }} 81 - class="border" 82 - > 83 - Next Page 84 - </button> 85 - {/if} 86 - </menu> 87 - 88 - {#if publicationsQuery.isFetching} 89 - <p>Fetching...</p> 90 - {:else if publicationsQuery.isError} 91 - <p>Error</p> 92 - {:else if publicationsQuery.isSuccess} 93 - {#if currentPage?.length === 0} 94 - There are no publications based onb the current filters 95 - {/if} 96 - {#each currentPage as publication (publication.uri)} 97 - <PublicationCard {publication} /> 98 - {/each} 99 - {/if} 100 - 101 - <menu> 102 - <button 103 - onclick={() => { 104 - if (page > 0) { 105 - page--; 106 - } 107 - }} 108 - class="border" 109 - > 110 - Prev Page 111 - </button> 112 - <number>{page + 1}</number> 113 - {#if publicationsQuery.hasNextPage} 114 - <button 115 - onclick={() => { 116 - page++; 117 - if ((page * 20) + 20 > (publicationsQuery.data?.length || 0)) { 118 - publicationsQuery.fetchNextPage(); 119 - } 120 - }} 121 - class="border" 122 - > 123 - Next Page 124 - </button> 125 - {/if} 126 - </menu> 1 + <h1>Index</h1>
+133
src/routes/explore/+page.svelte
··· 1 + <script lang="ts"> 2 + import { Debounced } from "runed"; 3 + import type { PublicationNode } from '$lib/utils'; 4 + import { createInfiniteQuery } from '@tanstack/svelte-query'; 5 + import PublicationCard from '$lib/components/PublicationCard.svelte'; 6 + 7 + let { data } = $props(); 8 + let { atclient, user } = data; 9 + 10 + let page = $state(0); 11 + let searchTerm = $state(""); 12 + const debouncedSearchTerm = new Debounced(() => searchTerm, 500); 13 + 14 + const publicationsQuery = createInfiniteQuery(() => ({ 15 + queryKey: ["publications", debouncedSearchTerm.current || undefined], 16 + queryFn: async ({ pageParam }) => { 17 + const query = ` 18 + query GetPublications { 19 + siteStandardPublication(first: 20, after: "${pageParam}", ${debouncedSearchTerm.current && `where: { 20 + or: [{ 21 + name: { contains: "${debouncedSearchTerm.current}" } 22 + }, { 23 + actorHandle: { contains: "${debouncedSearchTerm.current}" } 24 + }] 25 + }`}) { 26 + edges {} 27 + pageInfo { 28 + hasNextPage 29 + endCursor 30 + } 31 + } 32 + } 33 + `; 34 + const data = await atclient.publicQuery(query); 35 + return data as { 36 + siteStandardPublication: { 37 + edges: { node: PublicationNode, cursor: string }[], 38 + pageInfo: { 39 + hasNextPage: boolean; 40 + endCursor: string; 41 + } 42 + } 43 + } 44 + }, 45 + initialPageParam: "", 46 + getNextPageParam: (lastPage) => lastPage.siteStandardPublication.pageInfo.endCursor, 47 + select: (data) => { 48 + const items = data.pages.map((page) => page.siteStandardPublication.edges).flat(); 49 + const nodes = items.map((i) => i.node); 50 + return nodes; 51 + } 52 + })); 53 + 54 + let currentPage = $derived(publicationsQuery.data?.slice(page*20, (page*20) + 20)); 55 + </script> 56 + 57 + <menu class="flex w-full justify-between items-center"> 58 + <label> 59 + Search: 60 + <input bind:value={searchTerm} class="border px-3 py-2" /> 61 + </label> 62 + 63 + <div class=""> 64 + {#if page > 0} 65 + <button 66 + onclick={() => { 67 + if (page > 0) { 68 + page--; 69 + } 70 + }} 71 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 72 + > 73 + Previous 74 + </button> 75 + {/if} 76 + <number class="px-3">Page {page + 1}</number> 77 + {#if publicationsQuery.hasNextPage} 78 + <button 79 + onclick={() => { 80 + page++; 81 + if ((page * 20) + 20 > (publicationsQuery.data?.length || 0)) { 82 + publicationsQuery.fetchNextPage(); 83 + } 84 + }} 85 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 86 + > 87 + Next 88 + </button> 89 + {/if} 90 + </div> 91 + </menu> 92 + 93 + {#if publicationsQuery.isFetching} 94 + <p>Fetching...</p> 95 + {:else if publicationsQuery.isError} 96 + <p>Error</p> 97 + {:else if publicationsQuery.isSuccess} 98 + {#if currentPage?.length === 0} 99 + There are no publications based onb the current filters 100 + {/if} 101 + {#each currentPage as publication (publication.uri)} 102 + <PublicationCard {publication} /> 103 + {/each} 104 + {/if} 105 + 106 + <menu> 107 + {#if page > 0} 108 + <button 109 + onclick={() => { 110 + if (page > 0) { 111 + page--; 112 + } 113 + }} 114 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 115 + > 116 + Previous 117 + </button> 118 + {/if} 119 + <number class="px-3">Page {page + 1}</number> 120 + {#if publicationsQuery.hasNextPage} 121 + <button 122 + onclick={() => { 123 + page++; 124 + if ((page * 20) + 20 > (publicationsQuery.data?.length || 0)) { 125 + publicationsQuery.fetchNextPage(); 126 + } 127 + }} 128 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 129 + > 130 + Next 131 + </button> 132 + {/if} 133 + </menu>
+116
src/routes/home/+page.svelte
··· 1 + <script lang="ts"> 2 + import { goto } from "$app/navigation"; 3 + import { getContext, onMount } from "svelte"; 4 + import type { MiniDoc, PublicationNode } from "$lib/utils"; 5 + import { createInfiniteQuery } from "@tanstack/svelte-query"; 6 + import type { QuicksliceClient } from "quickslice-client-js"; 7 + import PublicationCard from "$lib/components/PublicationCard.svelte"; 8 + import { Debounced } from "runed"; 9 + 10 + const user = getContext("user") as MiniDoc; 11 + const atclient = getContext("atclient") as QuicksliceClient; 12 + 13 + let page = $state(0); 14 + let searchTerm = $state(""); 15 + let debouncedSearchTerm = new Debounced(() => searchTerm, 500); 16 + 17 + onMount(() => { 18 + if (!user) { 19 + goto("/") 20 + } 21 + }); 22 + 23 + const subscriptionsQuery = createInfiniteQuery(() => ({ 24 + queryKey: ["subscriptions", user.did, debouncedSearchTerm.current || undefined], 25 + queryFn: async () => { 26 + const query = ` 27 + query GetSubscriptionByUserDid { 28 + siteStandardGraphSubscription(first: 20, where: { 29 + did: { eq: "${user.did}" } 30 + }) { 31 + edges { 32 + node { 33 + publicationResolved {} 34 + } 35 + } 36 + pageInfo { 37 + hasNextPage 38 + endCursor 39 + } 40 + } 41 + } 42 + `; 43 + 44 + const data = await atclient.publicQuery(query); 45 + return data as { 46 + siteStandardGraphSubscription: { 47 + edges: { node: { publicationResolved: PublicationNode }}[], 48 + pageInfo: { 49 + hasNextPage: boolean; 50 + endCursor: string; 51 + } 52 + } 53 + }; 54 + }, 55 + initialPageParam: "", 56 + getNextPageParam: (lastPage) => lastPage.siteStandardGraphSubscription.pageInfo.endCursor, 57 + select: (data) => { 58 + const items = data.pages.map((page) => page.siteStandardGraphSubscription.edges).flat(); 59 + const nodes = items.map((i) => i.node.publicationResolved); 60 + return nodes; 61 + } 62 + })); 63 + 64 + let currentPage = $derived(subscriptionsQuery.data?.slice(page*20, (page*20) + 20)); 65 + </script> 66 + 67 + <h1 class="text-amber-400 text-3xl font-bold">My Subscriptions</h1> 68 + 69 + <menu class="flex w-full justify-between items-center"> 70 + <label> 71 + Search: 72 + <input bind:value={searchTerm} class="border px-3 py-2" /> 73 + </label> 74 + 75 + <div class=""> 76 + {#if page > 0} 77 + <button 78 + onclick={() => { 79 + if (page > 0) { 80 + page--; 81 + } 82 + }} 83 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 84 + > 85 + Previous 86 + </button> 87 + {/if} 88 + <number class="px-3">Page {page + 1}</number> 89 + {#if subscriptionsQuery.hasNextPage} 90 + <button 91 + onclick={() => { 92 + page++; 93 + if ((page * 20) + 20 > (subscriptionsQuery.data?.length || 0)) { 94 + subscriptionsQuery.fetchNextPage(); 95 + } 96 + }} 97 + class="bg-amber-400 text-black hover:cursor-pointer hover:bg-amber-500 hover:text-white px-4 py-2" 98 + > 99 + Next 100 + </button> 101 + {/if} 102 + </div> 103 + </menu> 104 + 105 + {#if subscriptionsQuery.isFetching} 106 + <p>Fetching...</p> 107 + {:else if subscriptionsQuery.isError} 108 + <p>Error</p> 109 + {:else if subscriptionsQuery.isSuccess} 110 + {#if currentPage?.length === 0} 111 + There are no subscriptions based onb the current filters 112 + {/if} 113 + {#each subscriptionsQuery.data as publication (publication.uri)} 114 + <PublicationCard {publication} /> 115 + {/each} 116 + {/if}