this repo has no description

Formatting, cleanup, misc style changes, more config options

astrra.space 2db2ca4a 3af16a98

verified
+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 + });