this repo has no description
16
fork

Configure Feed

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

Formatting, cleanup, misc style changes, more config options

astrra.space 2db2ca4a 3af16a98

verified
+120 -104
+26 -19
config.ts
··· 2 2 * Configuration module for the PDS Dashboard 3 3 */ 4 4 export class Config { 5 - /** 6 - * The base URL of the PDS (Personal Data Server) 7 - * @default "https://pds.witchcraft.systems" 8 - */ 9 - static readonly PDS_URL: string = "https://pds.witchcraft.systems"; 5 + /** 6 + * The base URL of the PDS (Personal Data Server) 7 + * @default "https://pds.witchcraft.systems" 8 + */ 9 + static readonly PDS_URL: string = "https://pds.witchcraft.systems"; 10 + 11 + /** 12 + * The base URL of the frontend service for linking to replies 13 + * @default "https://deer.social" 14 + */ 15 + static readonly FRONTEND_URL: string = "https://deer.social"; 10 16 11 - /** 12 - * The base URL of the frontend service for linking to replies 13 - * @default "https://deer.social" 14 - */ 15 - static readonly FRONTEND_URL: string = "https://deer.social"; 17 + /** 18 + * Maximum number of posts to show in the feed (across all users) 19 + * @default 100 20 + */ 21 + static readonly MAX_POSTS: number = 100; 16 22 17 - /** 18 - * Maximum number of posts to show in the feed (across all users) 19 - * @default 100 20 - */ 21 - static readonly MAX_POSTS: number = 100; 23 + /** 24 + * Footer text for the dashboard 25 + * @default "Astrally projected from witchcraft.systems" 26 + */ 27 + static readonly FOOTER_TEXT: string = 28 + "Astrally projected from <a href='https://witchcraft.systems' target='_blank'>witchcraft.systems</a>"; 22 29 23 30 /** 24 - * Footer text for the dashboard 25 - * @default "Astrally projected from witchcraft.systems" 31 + * Whether to show the posts that are in the future 32 + * @default false 26 33 */ 27 - static readonly FOOTER_TEXT: string = "Astrally projected from <a href='https://witchcraft.systems' target='_blank'>witchcraft.systems</a>"; 28 - } 34 + static readonly SHOW_FUTURE_POSTS: boolean = false; 35 + }
+1 -1
index.html
··· 1 - <!doctype html> 1 + <!DOCTYPE html> 2 2 <html lang="en"> 3 3 <head> 4 4 <meta charset="UTF-8" />
+27 -28
src/App.svelte
··· 9 9 10 10 <main> 11 11 <div id="Content"> 12 - {#await accountsPromise} 13 - <p>Loading...</p> 14 - {:then accountsData} 15 - <div id="Account"> 16 - <h1 id="Header">ATProto PDS</h1> 17 - <p>Home to {accountsData.length} accounts</p> 18 - <div id="accountsList"> 19 - {#each accountsData as accountObject} 20 - <AccountComponent account={accountObject} /> 21 - {/each} 12 + {#await accountsPromise} 13 + <p>Loading...</p> 14 + {:then accountsData} 15 + <div id="Account"> 16 + <h1 id="Header">ATProto PDS</h1> 17 + <p>Home to {accountsData.length} accounts</p> 18 + <div id="accountsList"> 19 + {#each accountsData as accountObject} 20 + <AccountComponent account={accountObject} /> 21 + {/each} 22 + </div> 23 + <p>{@html Config.FOOTER_TEXT}</p> 22 24 </div> 23 - <p>{@html Config.FOOTER_TEXT}</p> 24 - </div> 25 - {:catch error} 26 - <p>Error: {error.message}</p> 27 - {/await} 25 + {:catch error} 26 + <p>Error: {error.message}</p> 27 + {/await} 28 28 29 - {#await postsPromise} 30 - <p>Loading...</p> 31 - {:then postsData} 32 - <div id="Feed"> 33 - <div id="spacer"></div> 34 - {#each postsData as postObject} 35 - <PostComponent post={postObject as Post} /> 36 - {/each} 37 - <div id="spacer"></div> 38 - </div> 39 - {/await} 29 + {#await postsPromise} 30 + <p>Loading...</p> 31 + {:then postsData} 32 + <div id="Feed"> 33 + <div id="spacer"></div> 34 + {#each postsData as postObject} 35 + <PostComponent post={postObject as Post} /> 36 + {/each} 37 + <div id="spacer"></div> 38 + </div> 39 + {/await} 40 40 </div> 41 41 </main> 42 42 43 43 <style> 44 - 45 44 /* desktop style */ 46 45 47 46 #Content { ··· 74 73 #Account { 75 74 width: 35%; 76 75 display: flex; 77 - flex-direction: column; 76 + flex-direction: column; 78 77 border: 1px solid var(--border-color); 79 78 background-color: var(--content-background-color); 80 79 height: 80vh;
+3 -3
src/app.css
··· 1 1 @font-face { 2 - font-family: 'ProggyClean'; 2 + font-family: "ProggyClean"; 3 3 src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf); 4 4 } 5 5 ··· 62 62 min-width: 320px; 63 63 min-height: 100vh; 64 64 background-color: var(--background-color); 65 - font-family: 'ProggyClean', monospace; 65 + font-family: "ProggyClean", monospace; 66 66 font-size: 24px; 67 67 color: var(--text-color); 68 68 border-color: var(--border-color); ··· 80 80 margin-left: auto; 81 81 margin-right: auto; 82 82 text-align: center; 83 - } 83 + }
+1 -1
src/lib/PostComponent.svelte
··· 145 145 </div> 146 146 147 147 <style> 148 - 149 148 a:hover { 150 149 text-decoration: underline; 151 150 } ··· 197 196 background-color: var(--content-background-color); 198 197 color: var(--text-color); 199 198 overflow-wrap: break-word; 199 + white-space: pre-line; 200 200 } 201 201 #replyingText { 202 202 font-size: 0.7em;
+51 -41
src/lib/pdsfetch.ts
··· 45 45 46 46 constructor( 47 47 record: ComAtprotoRepoListRecords.Record, 48 - account: AccountMetadata, 48 + account: AccountMetadata 49 49 ) { 50 50 this.postCid = record.cid; 51 51 this.recordName = processAtUri(record.uri).rkey; ··· 67 67 this.videosLinkCid = null; 68 68 switch (post.embed?.$type) { 69 69 case "app.bsky.embed.images": 70 - this.imagesCid = post.embed.images.map((imageRecord: any) => 71 - imageRecord.image.ref.$link 70 + this.imagesCid = post.embed.images.map( 71 + (imageRecord: any) => imageRecord.image.ref.$link 72 72 ); 73 73 break; 74 74 case "app.bsky.embed.video": ··· 81 81 this.quotingUri = processAtUri(post.embed.record.record.uri); 82 82 switch (post.embed.media.$type) { 83 83 case "app.bsky.embed.images": 84 - this.imagesCid = post.embed.media.images.map((imageRecord) => 85 - imageRecord.image.ref.$link 84 + this.imagesCid = post.embed.media.images.map( 85 + (imageRecord) => imageRecord.image.ref.$link 86 86 ); 87 87 88 88 break; ··· 111 111 }), 112 112 }); 113 113 114 - const getDidsFromPDS = async () : Promise<At.Did[]> => { 114 + const getDidsFromPDS = async (): Promise<At.Did[]> => { 115 115 const { data } = await rpc.get("com.atproto.sync.listRepos", { 116 116 params: {}, 117 117 }); 118 - return data.repos.map((repo: any) => (repo.did)) as At.Did[]; 118 + return data.repos.map((repo: any) => repo.did) as At.Did[]; 119 119 }; 120 - const getAccountMetadata = async (did: `did:${string}:${string}`) : Promise<AccountMetadata> => { 120 + const getAccountMetadata = async ( 121 + did: `did:${string}:${string}` 122 + ): Promise<AccountMetadata> => { 121 123 // gonna assume self exists in the app.bsky.actor.profile 122 124 try { 123 - const { data } = await rpc.get("com.atproto.repo.getRecord", { 124 - params: { 125 - repo: did, 126 - collection: "app.bsky.actor.profile", 127 - rkey: "self", 128 - }, 129 - }); 130 - const value = data.value as AppBskyActorProfile.Record; 131 - const handle = await blueskyHandleFromDid(did); 132 - const account: AccountMetadata = { 133 - did: did, 134 - handle: handle, 135 - displayName: value.displayName || "", 136 - avatarCid: null, 137 - }; 138 - if (value.avatar) { 139 - account.avatarCid = value.avatar.ref["$link"]; 140 - } 141 - return account; 142 - } 143 - catch (e) { 125 + const { data } = await rpc.get("com.atproto.repo.getRecord", { 126 + params: { 127 + repo: did, 128 + collection: "app.bsky.actor.profile", 129 + rkey: "self", 130 + }, 131 + }); 132 + const value = data.value as AppBskyActorProfile.Record; 133 + const handle = await blueskyHandleFromDid(did); 134 + const account: AccountMetadata = { 135 + did: did, 136 + handle: handle, 137 + displayName: value.displayName || "", 138 + avatarCid: null, 139 + }; 140 + if (value.avatar) { 141 + account.avatarCid = value.avatar.ref["$link"]; 142 + } 143 + return account; 144 + } catch (e) { 144 145 console.error(`Error fetching metadata for ${did}:`, e); 145 146 return { 146 147 did: "error", ··· 151 152 } 152 153 }; 153 154 154 - const getAllMetadataFromPds = async () : Promise<AccountMetadata[]> => { 155 + const getAllMetadataFromPds = async (): Promise<AccountMetadata[]> => { 155 156 const dids = await getDidsFromPDS(); 156 157 const metadata = await Promise.all( 157 158 dids.map(async (repo: `did:${string}:${string}`) => { 158 159 return await getAccountMetadata(repo); 159 - }), 160 + }) 160 161 ); 161 - return metadata.filter(account => account.did !== "error"); 162 + return metadata.filter((account) => account.did !== "error"); 162 163 }; 163 164 164 165 const fetchPosts = async (did: string) => { ··· 173 174 return { 174 175 records: data.records as ComAtprotoRepoListRecords.Record[], 175 176 did: did, 176 - error: false 177 + error: false, 177 178 }; 178 179 } catch (e) { 179 180 console.error(`Error fetching posts for ${did}:`, e); 180 181 return { 181 182 records: [], 182 183 did: did, 183 - error: true 184 + error: true, 184 185 }; 185 186 } 186 187 }; ··· 195 196 196 197 if (did.startsWith("did:plc:") || did.startsWith("did:web:")) { 197 198 const doc = await resolver.resolve( 198 - did as `did:plc:${string}` | `did:web:${string}`, 199 + did as `did:plc:${string}` | `did:web:${string}` 199 200 ); 200 201 return doc; 201 202 } else { ··· 221 222 const fetchAllPosts = async () => { 222 223 const users: AccountMetadata[] = await getAllMetadataFromPds(); 223 224 const postRecords = await Promise.all( 224 - users.map(async (metadata: AccountMetadata) => 225 - await fetchPosts(metadata.did) 226 - ), 225 + users.map( 226 + async (metadata: AccountMetadata) => await fetchPosts(metadata.did) 227 + ) 227 228 ); 228 - const validPostRecords = postRecords.filter(record => !record.error); 229 + const validPostRecords = postRecords.filter((record) => !record.error); 229 230 const posts: Post[] = validPostRecords.flatMap((userFetch) => 230 231 userFetch.records.map((record) => { 231 - const user = users.find((user: AccountMetadata) => 232 - user.did == userFetch.did 232 + const user = users.find( 233 + (user: AccountMetadata) => user.did == userFetch.did 233 234 ); 234 235 if (!user) { 235 236 throw new Error(`User with DID ${userFetch.did} not found`); ··· 237 238 return new Post(record, user); 238 239 }) 239 240 ); 241 + 240 242 posts.sort((a, b) => b.timestamp - a.timestamp); 243 + 244 + if(!Config.SHOW_FUTURE_POSTS) { 245 + // Filter out posts that are in the future 246 + const now = Date.now(); 247 + const filteredPosts = posts.filter((post) => post.timestamp <= now); 248 + return filteredPosts.slice(0, Config.MAX_POSTS); 249 + } 250 + 241 251 return posts.slice(0, Config.MAX_POSTS); 242 252 }; 243 253 export { fetchAllPosts, getAllMetadataFromPds, Post };
+6 -6
src/main.ts
··· 1 - import { mount } from 'svelte' 2 - import './app.css' 3 - import App from './App.svelte' 1 + import { mount } from "svelte"; 2 + import "./app.css"; 3 + import App from "./App.svelte"; 4 4 5 5 const app = mount(App, { 6 - target: document.getElementById('app')!, 7 - }) 6 + target: document.getElementById("app")!, 7 + }); 8 8 9 - export default app 9 + export default app;
+2 -2
svelte.config.js
··· 1 - import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 1 + import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 2 3 3 export default { 4 4 // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 5 // for more information about preprocessors 6 6 preprocess: vitePreprocess(), 7 - } 7 + };
+3 -3
vite.config.ts
··· 1 - import { defineConfig } from 'vite' 2 - import { svelte } from '@sveltejs/vite-plugin-svelte' 1 + import { defineConfig } from "vite"; 2 + import { svelte } from "@sveltejs/vite-plugin-svelte"; 3 3 4 4 // https://vite.dev/config/ 5 5 export default defineConfig({ 6 6 plugins: [svelte()], 7 - }) 7 + });