[READ-ONLY] Mirror of https://github.com/flo-bit/bluesky-website-generator. bluesky-website-generator.vercel.app
0

Configure Feed

Select the types of activity you want to include in your feed.

bluesky-website-generator / src / routes / +page.svelte
3.7 kB 148 lines
1<script lang="ts"> 2 import { 3 Badge, 4 Button, 5 Heading, 6 Input, 7 Modal, 8 Subheading, 9 Tabs, 10 ThemeToggle, 11 toast, 12 Toaster 13 } from '@fuxui/base'; 14 import { BlueskyLogin, blueskyPostToPostData, UserProfile, type PostData } from '@fuxui/social'; 15 import { editingState } from './state.svelte.js'; 16 import Editor from '$lib/Editor.svelte'; 17 import Posts from '$lib/Posts.svelte'; 18 import Links from '$lib/Links.svelte'; 19 import { onMount } from 'svelte'; 20 import { AtpBaseClient } from '@atproto/api'; 21 import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs.js'; 22 import Navbar from '$lib/Navbar.svelte'; 23 24 let { data } = $props(); 25 let form = $state<HTMLFormElement | null>(null); 26 27 onMount(async () => { 28 editingState.links = JSON.parse(localStorage.getItem('links') ?? '[]'); 29 30 if (!data.user) return; 31 32 const posts = await getPostsOfUser({ actor: data.user.did }); 33 blueskyPosts = posts.feed.map((post) => { 34 return blueskyPostToPostData(post.post); 35 }); 36 }); 37 38 export async function getPostsOfUser({ 39 actor, 40 cursor, 41 limit = 20 42 }: { 43 actor: string; 44 cursor?: string; 45 limit?: number; 46 }) { 47 let agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 48 const perLimit = limit > 100 ? 100 : limit; 49 const posts: FeedViewPost[] = []; 50 do { 51 const { data } = await agent.app.bsky.feed.getAuthorFeed({ 52 actor, 53 cursor, 54 limit: perLimit, 55 filter: 'posts_no_replies' 56 }); 57 posts.push(...data.feed); 58 cursor = data.cursor; 59 } while (cursor && posts.length < limit); 60 return { feed: posts, cursor }; 61 } 62 63 let blueskyPosts: PostData[] = $state([]); 64</script> 65 66<div class="mx-auto max-w-2xl py-16"> 67 {#if !data.user} 68 <Heading class="mt-16 mb-8">Login with Bluesky to create your website</Heading> 69 <BlueskyLogin 70 login={async (handle) => { 71 // create and submit form with handle 72 73 if (!form) { 74 console.error('Form not found'); 75 return false; 76 } 77 78 // set handle 79 const input = form.querySelector('input[name="handle"]') as HTMLInputElement; 80 if (!input) { 81 return false; 82 } 83 input.value = handle; 84 form.submit(); 85 86 return true; 87 }} 88 /> 89 90 <form method="POST" action="/?/login" class="hidden" bind:this={form}> 91 <input type="hidden" name="handle" value="handle" /> 92 </form> 93 {:else} 94 <UserProfile class="" profile={{ ...data.user, description: undefined }} /> 95 96 <div class="-mt-8"> 97 <Tabs 98 active={editingState.active} 99 items={[ 100 { 101 name: 'about', 102 onclick: () => (editingState.active = 'about') 103 }, 104 { 105 name: 'links', 106 onclick: () => (editingState.active = 'links') 107 }, 108 { 109 name: 'feed', 110 onclick: () => (editingState.active = 'feed') 111 } 112 ]} 113 /> 114 115 <div class="px-4"> 116 {#if editingState.active === 'about'} 117 <div 118 class="focus-within:outline-accent-400 hover:not-focus-within:bg-base-200/40 dark:hover:not-focus-within:bg-base-900/20 group focus-within:bg-accent-500/5 relative mt-4 rounded-2xl px-2 py-0.5 focus-within:outline" 119 > 120 <Editor /> 121 <Badge 122 variant="secondary" 123 class="absolute top-2 right-2 opacity-60 group-focus-within:hidden" 124 > 125 Select to edit 126 </Badge> 127 </div> 128 {:else if editingState.active === 'links'} 129 <div> 130 <Links links={editingState.links} /> 131 </div> 132 {:else if editingState.active === 'feed'} 133 <Posts posts={blueskyPosts} /> 134 {/if} 135 </div> 136 137 <form method="POST" action="/?/logout" class="fixed right-2 bottom-2"> 138 <Button variant="secondary" type="submit">Logout</Button> 139 </form> 140 </div> 141 {/if} 142</div> 143 144<Toaster /> 145 146{#if data.user} 147 <Navbar handle={data.user.handle} domain={data.project} /> 148{/if}