From cff9eed1a43be8a40fc4d5fa63db99d6db8a4dfe Mon Sep 17 00:00:00 2001 From: ari Date: Tue, 22 Apr 2025 03:14:37 +0000 Subject: [PATCH 01/14] Dynamic post loading (#2) Dynamically load the posts so that you can scroll a chronologically sorted timeline infinitely Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/2 Co-authored-by: ari Co-committed-by: ari --- config.ts | 21 ++-- deno.lock | 5 + package.json | 3 +- src/App.svelte | 46 +++++--- src/lib/PostComponent.svelte | 4 +- src/lib/pdsfetch.ts | 217 ++++++++++++++++++++++++----------- 6 files changed, 205 insertions(+), 91 deletions(-) diff --git a/config.ts b/config.ts index 2b1b511..0dc01b0 100644 --- a/config.ts +++ b/config.ts @@ -9,16 +9,19 @@ export class Config { static readonly PDS_URL: string = "https://pds.witchcraft.systems"; /** - * The base URL of the frontend service for linking to replies + * The base URL of the frontend service for linking to replies/quotes/accounts etc. * @default "https://deer.social" */ static readonly FRONTEND_URL: string = "https://deer.social"; /** - * Maximum number of posts to show in the feed (across all users) - * @default 100 + * Maximum number of posts to fetch from the PDS per request + * Should be around 20 for about 10 users on the pds + * The more users you have, the lower the number should be + * since sorting is slow and is done on the frontend + * @default 20 */ - static readonly MAX_POSTS: number = 100; + static readonly MAX_POSTS: number = 20; /** * Footer text for the dashboard @@ -27,9 +30,9 @@ export class Config { static readonly FOOTER_TEXT: string = "Astrally projected from witchcraft.systems"; - /** - * Whether to show the posts that are in the future - * @default false - */ - static readonly SHOW_FUTURE_POSTS: boolean = false; + /** + * Whether to show the posts that are in the future + * @default false + */ + static readonly SHOW_FUTURE_POSTS: boolean = false; } diff --git a/deno.lock b/deno.lock index 0616852..724a5c0 100644 --- a/deno.lock +++ b/deno.lock @@ -8,6 +8,7 @@ "npm:@tsconfig/svelte@^5.0.4": "5.0.4", "npm:moment@^2.30.1": "2.30.1", "npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3", + "npm:svelte-infinite-loading@^1.4.0": "1.4.0", "npm:svelte@^5.23.1": "5.28.1_acorn@8.14.1", "npm:typescript@~5.7.2": "5.7.3", "npm:vite@^6.3.1": "6.3.2_picomatch@4.0.2" @@ -415,6 +416,9 @@ "typescript" ] }, + "svelte-infinite-loading@1.4.0": { + "integrity": "sha512-Jo+f/yr/HmZQuIiiKKzAHVFXdAUWHW2RBbrcQTil8JVk1sCm/riy7KTJVzjBgQvHasrFQYKF84zvtc9/Y4lFYg==" + }, "svelte@5.28.1_acorn@8.14.1": { "integrity": "sha512-iOa9WmfNG95lSOSJdMhdjJ4Afok7IRAQYXpbnxhd5EINnXseG0GVa9j6WPght4eX78XfFez45Fi+uRglGKPV/Q==", "dependencies": [ @@ -476,6 +480,7 @@ "npm:@tsconfig/svelte@^5.0.4", "npm:moment@^2.30.1", "npm:svelte-check@^4.1.5", + "npm:svelte-infinite-loading@^1.4.0", "npm:svelte@^5.23.1", "npm:typescript@~5.7.2", "npm:vite@^6.3.1" diff --git a/package.json b/package.json index 9f84465..1db6461 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "@atcute/bluesky": "^2.0.2", "@atcute/client": "^3.0.1", "@atcute/identity-resolver": "^0.1.2", - "moment": "^2.30.1" + "moment": "^2.30.1", + "svelte-infinite-loading": "^1.4.0" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", diff --git a/src/App.svelte b/src/App.svelte index fa5a5c1..94367b1 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,10 +1,31 @@
@@ -26,17 +47,16 @@

Error: {error.message}

{/await} - {#await postsPromise} -

Loading...

- {:then postsData} -
-
- {#each postsData as postObject} - - {/each} -
-
- {/await} +
+
+ {#each posts as postObject} + + {/each} + +
+
diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index c7c59ac..3c4178c 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -113,7 +113,7 @@
@@ -125,7 +125,7 @@
diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index 0d36e8d..79edab0 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -18,11 +18,15 @@ import { Config } from "../../config"; // import { AppBskyActorDefs } from "@atcute/client/lexicons"; interface AccountMetadata { - did: string; + did: At.Did; displayName: string; handle: string; avatarCid: string | null; + currentCursor?: string; } + +let accountsMetadata: AccountMetadata[] = []; + interface atUriObject { repo: string; collection: string; @@ -45,7 +49,7 @@ class Post { constructor( record: ComAtprotoRepoListRecords.Record, - account: AccountMetadata + account: AccountMetadata, ) { this.postCid = record.cid; this.recordName = processAtUri(record.uri).rkey; @@ -68,7 +72,7 @@ class Post { switch (post.embed?.$type) { case "app.bsky.embed.images": this.imagesCid = post.embed.images.map( - (imageRecord: any) => imageRecord.image.ref.$link + (imageRecord: any) => imageRecord.image.ref.$link, ); break; case "app.bsky.embed.video": @@ -82,7 +86,7 @@ class Post { switch (post.embed.media.$type) { case "app.bsky.embed.images": this.imagesCid = post.embed.media.images.map( - (imageRecord) => imageRecord.image.ref.$link + (imageRecord) => imageRecord.image.ref.$link, ); break; @@ -118,8 +122,8 @@ const getDidsFromPDS = async (): Promise => { return data.repos.map((repo: any) => repo.did) as At.Did[]; }; const getAccountMetadata = async ( - did: `did:${string}:${string}` -): Promise => { + did: `did:${string}:${string}`, +) => { // gonna assume self exists in the app.bsky.actor.profile try { const { data } = await rpc.get("com.atproto.repo.getRecord", { @@ -143,12 +147,7 @@ const getAccountMetadata = async ( return account; } catch (e) { console.error(`Error fetching metadata for ${did}:`, e); - return { - did: "error", - displayName: "", - avatarCid: null, - handle: "error", - }; + return null; } }; @@ -157,33 +156,9 @@ const getAllMetadataFromPds = async (): Promise => { const metadata = await Promise.all( dids.map(async (repo: `did:${string}:${string}`) => { return await getAccountMetadata(repo); - }) + }), ); - return metadata.filter((account) => account.did !== "error"); -}; - -const fetchPosts = async (did: string) => { - try { - const { data } = await rpc.get("com.atproto.repo.listRecords", { - params: { - repo: did as At.Identifier, - collection: "app.bsky.feed.post", - limit: Config.MAX_POSTS, - }, - }); - return { - records: data.records as ComAtprotoRepoListRecords.Record[], - did: did, - error: false, - }; - } catch (e) { - console.error(`Error fetching posts for ${did}:`, e); - return { - records: [], - did: did, - error: true, - }; - } + return metadata.filter((account) => account !== null) as AccountMetadata[]; }; const identityResolve = async (did: At.Did) => { @@ -196,7 +171,7 @@ const identityResolve = async (did: At.Did) => { if (did.startsWith("did:plc:") || did.startsWith("did:web:")) { const doc = await resolver.resolve( - did as `did:plc:${string}` | `did:web:${string}` + did as `did:plc:${string}` | `did:web:${string}`, ); return doc; } else { @@ -219,36 +194,146 @@ const blueskyHandleFromDid = async (did: At.Did) => { } }; -const fetchAllPosts = async () => { - const users: AccountMetadata[] = await getAllMetadataFromPds(); - const postRecords = await Promise.all( - users.map( - async (metadata: AccountMetadata) => await fetchPosts(metadata.did) - ) - ); - const validPostRecords = postRecords.filter((record) => !record.error); - const posts: Post[] = validPostRecords.flatMap((userFetch) => - userFetch.records.map((record) => { - const user = users.find( - (user: AccountMetadata) => user.did == userFetch.did - ); - if (!user) { - throw new Error(`User with DID ${userFetch.did} not found`); +interface PostsAcc { + posts: ComAtprotoRepoListRecords.Record[]; + account: AccountMetadata; +} +const getCutoffDate = (postAccounts: PostsAcc[]) => { + const now = Date.now(); + let cutoffDate: Date | null = null; + postAccounts.forEach((postAcc) => { + const latestPost = new Date( + (postAcc.posts[postAcc.posts.length - 1].value as AppBskyFeedPost.Record) + .createdAt, + ); + if (!cutoffDate) { + cutoffDate = latestPost; + } else { + if (latestPost > cutoffDate) { + cutoffDate = latestPost; } - return new Post(record, user); - }) - ); + } + }); + if (cutoffDate) { + return cutoffDate; + } else { + return new Date(now); + } +}; - posts.sort((a, b) => b.timestamp - a.timestamp); - - if(!Config.SHOW_FUTURE_POSTS) { - // Filter out posts that are in the future - const now = Date.now(); - const filteredPosts = posts.filter((post) => post.timestamp <= now); - return filteredPosts.slice(0, Config.MAX_POSTS); +const filterPostsByDate = (posts: PostsAcc[], cutoffDate: Date) => { + // filter posts for each account that are older than the cutoff date and save the cursor of the last post included + const filteredPosts: PostsAcc[] = posts.map((postAcc) => { + const filtered = postAcc.posts.filter((post) => { + const postDate = new Date( + (post.value as AppBskyFeedPost.Record).createdAt, + ); + return postDate >= cutoffDate; + }); + if (filtered.length > 0) { + postAcc.account.currentCursor = processAtUri(filtered[filtered.length - 1].uri).rkey; + } + return { + posts: filtered, + account: postAcc.account, + }; + }); + return filteredPosts; +}; +// nightmare function. However it works so I am not touching it +const getNextPosts = async () => { + if (!accountsMetadata.length) { + accountsMetadata = await getAllMetadataFromPds(); } - return posts.slice(0, Config.MAX_POSTS); + const postsAcc: PostsAcc[] = await Promise.all( + accountsMetadata.map(async (account) => { + const posts = await fetchPostsForUser( + account.did, + account.currentCursor || null, + ); + if (posts) { + return { + posts: posts, + account: account, + }; + } else { + return { + posts: [], + account: account, + }; + } + }), + ); + const recordsFiltered = postsAcc.filter((postAcc) => + postAcc.posts.length > 0 + ); + const cutoffDate = getCutoffDate(recordsFiltered); + const recordsCutoff = filterPostsByDate(recordsFiltered, cutoffDate); + // update the accountMetadata with the new cursor + accountsMetadata = accountsMetadata.map((account) => { + const postAcc = recordsCutoff.find( + (postAcc) => postAcc.account.did == account.did, + ); + if (postAcc) { + account.currentCursor = postAcc.account.currentCursor; + } + return account; + } + ); + // throw the records in a big single array + let records = recordsCutoff.flatMap((postAcc) => postAcc.posts); + // sort the records by timestamp + records = records.sort((a, b) => { + const aDate = new Date( + (a.value as AppBskyFeedPost.Record).createdAt, + ).getTime(); + const bDate = new Date( + (b.value as AppBskyFeedPost.Record).createdAt, + ).getTime(); + return bDate - aDate; + }); + // filter out posts that are in the future + if (!Config.SHOW_FUTURE_POSTS) { + const now = Date.now(); + records = records.filter((post) => { + const postDate = new Date( + (post.value as AppBskyFeedPost.Record).createdAt, + ).getTime(); + return postDate <= now; + }); + } + + const newPosts = records.map((record) => { + const account = accountsMetadata.find( + (account) => account.did == processAtUri(record.uri).repo, + ); + if (!account) { + throw new Error( + `Account with DID ${processAtUri(record.uri).repo} not found`, + ); + } + return new Post(record, account); + }); + return newPosts; }; -export { fetchAllPosts, getAllMetadataFromPds, Post }; + +const fetchPostsForUser = async (did: At.Did, cursor: string | null) => { + try { + const { data } = await rpc.get("com.atproto.repo.listRecords", { + params: { + repo: did as At.Identifier, + collection: "app.bsky.feed.post", + limit: Config.MAX_POSTS, + cursor: cursor || undefined, + }, + }); + return data.records as ComAtprotoRepoListRecords.Record[]; + } catch (e) { + console.error(`Error fetching posts for ${did}:`, e); + return null; + } +}; + +export { getAllMetadataFromPds, getNextPosts, Post }; export type { AccountMetadata }; From 5eca07724e23c8b72b1afba10b9da29a45d05f5f Mon Sep 17 00:00:00 2001 From: astra Date: Tue, 22 Apr 2025 03:53:05 +0000 Subject: [PATCH 02/14] Distance tweaks for hidden loads --- src/App.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index 94367b1..9e36d30 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -18,6 +18,7 @@ // Infinite loading function const onInfinite = ({ detail: { loaded, complete } } : { detail : { loaded : () => void, complete : () => void}}) => { getNextPosts().then((newPosts) => { + console.log("Loading next posts..."); if (newPosts.length > 0) { posts = [...posts, ...newPosts]; loaded(); @@ -53,7 +54,7 @@ {/each}
From 3d38e0f68cb236909315c883a401cb70540a8f13 Mon Sep 17 00:00:00 2001 From: ari Date: Tue, 22 Apr 2025 05:27:05 +0000 Subject: [PATCH 03/14] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25b3cfb..d9eb2ea 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,9 @@ we use our own CI/CD workflow at [`.forgejo/workflows/deploy.yaml`](.forgejo/wor ## theming -currently the only way to theme the app is to edit css in the components directly, glhf +the colors are designated in [`src/app.css`](src/app.css) as variables, go crazy with them + +the rest is done by editing the css files and style tags directly, good luck relevant files: From 72ba5779507662049ba96b89c2224e1c45b42e35 Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 23 Apr 2025 04:10:14 +0000 Subject: [PATCH 04/14] Proper post word wrapping (#3) Co-authored-by: ari Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/3 --- src/App.svelte | 10 ++++++---- src/lib/PostComponent.svelte | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 9e36d30..733320e 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -16,7 +16,11 @@ }); }); // Infinite loading function - const onInfinite = ({ detail: { loaded, complete } } : { detail : { loaded : () => void, complete : () => void}}) => { + const onInfinite = ({ + detail: { loaded, complete }, + }: { + detail: { loaded: () => void; complete: () => void }; + }) => { getNextPosts().then((newPosts) => { console.log("Loading next posts..."); if (newPosts.length > 0) { @@ -53,9 +57,7 @@ {#each posts as postObject} {/each} - +
diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index 3c4178c..dc0b874 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -213,6 +213,10 @@ #postText { margin: 0; padding: 0; + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; } #headerText { margin-left: 10px; From 614f2b4c30e819c5134e48ae72bace7cebe1d769 Mon Sep 17 00:00:00 2001 From: Astra Date: Sat, 26 Apr 2025 14:46:30 +0900 Subject: [PATCH 05/14] Add links to the source code --- config.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config.ts b/config.ts index 0dc01b0..8d09cf6 100644 --- a/config.ts +++ b/config.ts @@ -24,11 +24,10 @@ export class Config { static readonly MAX_POSTS: number = 20; /** - * Footer text for the dashboard - * @default "Astrally projected from witchcraft.systems" + * Footer text for the dashboard, you probably want to change this */ static readonly FOOTER_TEXT: string = - "Astrally projected from witchcraft.systems"; + "Astrally projected from witchcraft.systems

Source (github mirror)"; /** * Whether to show the posts that are in the future From a495f724b9f0d21ce725ae9c886e351f96c50057 Mon Sep 17 00:00:00 2001 From: Astra Date: Sat, 10 May 2025 09:14:50 +0000 Subject: [PATCH 06/14] CSS text overflow and infinite loading fixes (#4) Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/4 --- src/App.svelte | 6 +++--- src/app.css | 5 +++++ src/lib/AccountComponent.svelte | 3 ++- src/lib/PostComponent.svelte | 12 ++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 733320e..fef3804 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -52,12 +52,12 @@

Error: {error.message}

{/await} -
+
{#each posts as postObject} {/each} - +
@@ -129,7 +129,7 @@ margin-top: 5%; } #Account { - width: auto; + width: 85%; padding-left: 5%; padding-right: 5%; margin-bottom: 20px; diff --git a/src/app.css b/src/app.css index 4c343e2..50da734 100644 --- a/src/app.css +++ b/src/app.css @@ -66,6 +66,10 @@ body { font-size: 24px; color: var(--text-color); border-color: var(--border-color); + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; } h1 { @@ -75,6 +79,7 @@ h1 { #app { max-width: 1400px; + width: 100%; margin: 0; padding: 0; margin-left: auto; diff --git a/src/lib/AccountComponent.svelte b/src/lib/AccountComponent.svelte index e324393..880db3f 100644 --- a/src/lib/AccountComponent.svelte +++ b/src/lib/AccountComponent.svelte @@ -32,17 +32,18 @@ #accountName { margin-left: 10px; font-size: 0.9em; + max-width: 80%; /* replace overflow with ellipsis */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - max-width: 80%; } #avatar { width: 50px; height: 50px; margin: 0px; + object-fit: cover; border-right: var(--border-color) 1px solid; } diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index dc0b874..d140180 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -222,13 +222,21 @@ margin-left: 10px; font-size: 0.9em; text-align: start; - overflow-wrap: break-word; + word-break: break-word; + max-width: 80%; + max-height: 95%; overflow: hidden; + align-self: flex-start; + margin-top: auto; + margin-bottom: auto; } #avatar { - height: 100%; + height: 60px; + width: 60px; margin: 0px; margin-left: 0px; + overflow: hidden; + object-fit: cover; border-right: var(--border-color) 1px solid; } #carouselContainer { From abeaa86096cee9fafb725b17479396cb6f5f4216 Mon Sep 17 00:00:00 2001 From: Astra Date: Sat, 10 May 2025 18:40:38 +0900 Subject: [PATCH 07/14] Actual infinite scrolling fix --- src/App.svelte | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index fef3804..43b87bd 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -52,12 +52,17 @@

Error: {error.message}

{/await} -
+
{#each posts as postObject} {/each} - + {#if screen.availWidth > 600} + + {/if} + {#if screen.availWidth <= 600} + + {/if}
@@ -138,6 +143,7 @@ height: auto; } #Feed { + overflow-y: scroll; width: 95%; margin: 0px; margin-left: 10%; From 79d5694dd168a085eab96c4084dafa06edd7fa1d Mon Sep 17 00:00:00 2001 From: Astra Date: Sat, 10 May 2025 20:00:20 +0900 Subject: [PATCH 08/14] Proper display name wrapping --- src/lib/PostComponent.svelte | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index d140180..3b4ed68 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -170,12 +170,21 @@ height: 60px; } #displayName { + display: block; color: var(--text-color); font-size: 1.2em; padding: 0; margin: 0; + overflow-wrap:normal; + word-wrap: break-word; + word-break: break-word; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; } #handle { + display: block; color: var(--border-color); font-size: 0.8em; padding: 0; From c3dfb2e48d2a09059dab126c54ab40497a2ab9cf Mon Sep 17 00:00:00 2001 From: Astra Date: Sat, 10 May 2025 15:52:44 +0000 Subject: [PATCH 09/14] GIF support (#5) Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/5 --- src/App.svelte | 12 +++--------- src/lib/PostComponent.svelte | 7 +++++++ src/lib/pdsfetch.ts | 5 +++++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 43b87bd..c6e7534 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -57,12 +57,7 @@ {#each posts as postObject} {/each} - {#if screen.availWidth > 600} - - {/if} - {#if screen.availWidth <= 600} - - {/if} +
@@ -83,9 +78,9 @@ color: var(--text-color); } #Feed { + overflow-y: scroll; width: 65%; height: 100vh; - overflow-y: scroll; padding: 20px; padding-bottom: 0; padding-top: 0; @@ -143,13 +138,12 @@ height: auto; } #Feed { - overflow-y: scroll; width: 95%; margin: 0px; margin-left: 10%; margin-right: 10%; padding: 0px; - height: auto; + overflow-y: visible; } #spacer { diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index 3b4ed68..43ad667 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -141,6 +141,13 @@ controls > {/if} + {#if post.gifLink} + Post GIF + {/if} diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index 79edab0..e782f62 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -46,6 +46,7 @@ class Post { replyingUri: atUriObject | null; imagesCid: string[] | null; videosLinkCid: string | null; + gifLink: string | null; constructor( record: ComAtprotoRepoListRecords.Record, @@ -69,6 +70,7 @@ class Post { this.quotingUri = null; this.imagesCid = null; this.videosLinkCid = null; + this.gifLink = null; switch (post.embed?.$type) { case "app.bsky.embed.images": this.imagesCid = post.embed.images.map( @@ -96,6 +98,9 @@ class Post { break; } break; + case "app.bsky.embed.external": // assuming that external embeds are gifs for now + this.gifLink = post.embed.external.uri; + break; } } } From c84ff140ca6ed99f3412894a33eb6753680cc0a0 Mon Sep 17 00:00:00 2001 From: Astra Date: Sun, 11 May 2025 00:56:39 +0900 Subject: [PATCH 10/14] GIF fix for safari --- src/lib/pdsfetch.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index e782f62..bc95acd 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -99,7 +99,9 @@ class Post { } break; case "app.bsky.embed.external": // assuming that external embeds are gifs for now - this.gifLink = post.embed.external.uri; + if (post.embed.external.uri.includes(".gif")) { + this.gifLink = post.embed.external.uri; + } break; } } From b3e8c7eac1bc4114ed1d7476fc32f202844641e0 Mon Sep 17 00:00:00 2001 From: Ari Date: Sat, 10 May 2025 14:07:37 -0400 Subject: [PATCH 11/14] Added mutex to fix duplicated posts --- deno.lock | 229 +++++++++++++++++++++++++++++++++----------- package.json | 1 + src/lib/pdsfetch.ts | 6 ++ 3 files changed, 179 insertions(+), 57 deletions(-) diff --git a/deno.lock b/deno.lock index 724a5c0..90a8393 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,5 @@ { - "version": "4", + "version": "5", "specifiers": { "npm:@atcute/bluesky@^2.0.2": "2.0.2_@atcute+client@3.0.1", "npm:@atcute/client@^3.0.1": "3.0.1", @@ -7,6 +7,7 @@ "npm:@sveltejs/vite-plugin-svelte@^5.0.3": "5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2", "npm:@tsconfig/svelte@^5.0.4": "5.0.4", "npm:moment@^2.30.1": "2.30.1", + "npm:mutex-ts@^1.2.1": "1.2.1", "npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3", "npm:svelte-infinite-loading@^1.4.0": "1.4.0", "npm:svelte@^5.23.1": "5.28.1_acorn@8.14.1", @@ -54,79 +55,129 @@ "integrity": "sha512-GEhUCk9c4XbNxi+0YZHZsV4fYNd6HejfWuN4Ti4c02DauX+LyX5WY1Y3WfyZ8Pxxl0zqhs+MLtW98cMh86vv6g==" }, "@esbuild/aix-ppc64@0.25.2": { - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==" + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.25.2": { - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==" + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm@0.25.2": { - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==" + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-x64@0.25.2": { - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==" + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.25.2": { - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==" + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.25.2": { - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==" + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.25.2": { - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==" + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.25.2": { - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==" + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.25.2": { - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==" + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.25.2": { - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==" + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.25.2": { - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==" + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.25.2": { - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==" + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.25.2": { - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==" + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.25.2": { - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==" + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.25.2": { - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==" + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.25.2": { - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==" + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.25.2": { - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==" + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "os": ["linux"], + "cpu": ["x64"] }, "@esbuild/netbsd-arm64@0.25.2": { - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==" + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "os": ["netbsd"], + "cpu": ["arm64"] }, "@esbuild/netbsd-x64@0.25.2": { - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==" + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "os": ["netbsd"], + "cpu": ["x64"] }, "@esbuild/openbsd-arm64@0.25.2": { - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==" + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "os": ["openbsd"], + "cpu": ["arm64"] }, "@esbuild/openbsd-x64@0.25.2": { - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==" + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.25.2": { - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==" + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.25.2": { - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==" + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.25.2": { - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==" + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.25.2": { - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==" + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "os": ["win32"], + "cpu": ["x64"] }, "@jridgewell/gen-mapping@0.3.8": { "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", @@ -153,64 +204,104 @@ ] }, "@rollup/rollup-android-arm-eabi@4.40.0": { - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==" + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "os": ["android"], + "cpu": ["arm"] }, "@rollup/rollup-android-arm64@4.40.0": { - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==" + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "os": ["android"], + "cpu": ["arm64"] }, "@rollup/rollup-darwin-arm64@4.40.0": { - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==" + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@rollup/rollup-darwin-x64@4.40.0": { - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==" + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "os": ["darwin"], + "cpu": ["x64"] }, "@rollup/rollup-freebsd-arm64@4.40.0": { - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==" + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@rollup/rollup-freebsd-x64@4.40.0": { - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==" + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@rollup/rollup-linux-arm-gnueabihf@4.40.0": { - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==" + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "os": ["linux"], + "cpu": ["arm"] }, "@rollup/rollup-linux-arm-musleabihf@4.40.0": { - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==" + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "os": ["linux"], + "cpu": ["arm"] }, "@rollup/rollup-linux-arm64-gnu@4.40.0": { - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==" + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "os": ["linux"], + "cpu": ["arm64"] }, "@rollup/rollup-linux-arm64-musl@4.40.0": { - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==" + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "os": ["linux"], + "cpu": ["arm64"] }, "@rollup/rollup-linux-loongarch64-gnu@4.40.0": { - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==" + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "os": ["linux"], + "cpu": ["loong64"] }, "@rollup/rollup-linux-powerpc64le-gnu@4.40.0": { - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==" + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@rollup/rollup-linux-riscv64-gnu@4.40.0": { - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==" + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@rollup/rollup-linux-riscv64-musl@4.40.0": { - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==" + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@rollup/rollup-linux-s390x-gnu@4.40.0": { - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==" + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "os": ["linux"], + "cpu": ["s390x"] }, "@rollup/rollup-linux-x64-gnu@4.40.0": { - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==" + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "os": ["linux"], + "cpu": ["x64"] }, "@rollup/rollup-linux-x64-musl@4.40.0": { - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==" + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "os": ["linux"], + "cpu": ["x64"] }, "@rollup/rollup-win32-arm64-msvc@4.40.0": { - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==" + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "os": ["win32"], + "cpu": ["arm64"] }, "@rollup/rollup-win32-ia32-msvc@4.40.0": { - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==" + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "os": ["win32"], + "cpu": ["ia32"] }, "@rollup/rollup-win32-x64-msvc@4.40.0": { - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==" + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "os": ["win32"], + "cpu": ["x64"] }, "@sveltejs/acorn-typescript@1.0.5_acorn@8.14.1": { "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", @@ -247,7 +338,8 @@ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, "acorn@8.14.1": { - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "bin": true }, "aria-query@5.3.2": { "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" @@ -275,7 +367,7 @@ }, "esbuild@0.25.2": { "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "dependencies": [ + "optionalDependencies": [ "@esbuild/aix-ppc64", "@esbuild/android-arm", "@esbuild/android-arm64", @@ -301,7 +393,9 @@ "@esbuild/win32-arm64", "@esbuild/win32-ia32", "@esbuild/win32-x64" - ] + ], + "scripts": true, + "bin": true }, "esm-env@1.2.2": { "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" @@ -316,10 +410,15 @@ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dependencies": [ "picomatch" + ], + "optionalPeers": [ + "picomatch" ] }, "fsevents@2.3.3": { - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true }, "is-reference@3.0.3": { "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", @@ -348,8 +447,12 @@ "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "mutex-ts@1.2.1": { + "integrity": "sha512-OkcXgf0viuCgYdnm48kiNQ9PzC5OzISQ261svHr/Ybc2vBYC/5xfLXn44hQ+dYRX74v7MCSqV/LKPEbpYdDybw==" + }, "nanoid@3.3.11": { - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "bin": true }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" @@ -371,6 +474,9 @@ "rollup@4.40.0": { "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ "@rollup/rollup-android-arm-eabi", "@rollup/rollup-android-arm64", "@rollup/rollup-darwin-arm64", @@ -391,9 +497,9 @@ "@rollup/rollup-win32-arm64-msvc", "@rollup/rollup-win32-ia32-msvc", "@rollup/rollup-win32-x64-msvc", - "@types/estree", "fsevents" - ] + ], + "bin": true }, "sade@1.8.1": { "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", @@ -414,7 +520,8 @@ "sade", "svelte", "typescript" - ] + ], + "bin": true }, "svelte-infinite-loading@1.4.0": { "integrity": "sha512-Jo+f/yr/HmZQuIiiKKzAHVFXdAUWHW2RBbrcQTil8JVk1sCm/riy7KTJVzjBgQvHasrFQYKF84zvtc9/Y4lFYg==" @@ -446,24 +553,31 @@ ] }, "typescript@5.7.3": { - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==" + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "bin": true }, "vite@6.3.2_picomatch@4.0.2": { "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", "dependencies": [ "esbuild", "fdir", - "fsevents", "picomatch", "postcss", "rollup", "tinyglobby" - ] + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true }, "vitefu@1.0.6_vite@6.3.2__picomatch@4.0.2": { "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", "dependencies": [ "vite" + ], + "optionalPeers": [ + "vite" ] }, "zimmerframe@1.1.2": { @@ -479,6 +593,7 @@ "npm:@sveltejs/vite-plugin-svelte@^5.0.3", "npm:@tsconfig/svelte@^5.0.4", "npm:moment@^2.30.1", + "npm:mutex-ts@^1.2.1", "npm:svelte-check@^4.1.5", "npm:svelte-infinite-loading@^1.4.0", "npm:svelte@^5.23.1", diff --git a/package.json b/package.json index 1db6461..3b293c0 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@atcute/client": "^3.0.1", "@atcute/identity-resolver": "^0.1.2", "moment": "^2.30.1", + "mutex-ts": "^1.2.1", "svelte-infinite-loading": "^1.4.0" }, "devDependencies": { diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index bc95acd..20120fb 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -13,6 +13,7 @@ import { WebDidDocumentResolver, } from "@atcute/identity-resolver"; import { Config } from "../../config"; +import { Mutex } from "mutex-ts" // import { ComAtprotoRepoListRecords.Record } from "@atcute/client/lexicons"; // import { AppBskyFeedPost } from "@atcute/client/lexicons"; // import { AppBskyActorDefs } from "@atcute/client/lexicons"; @@ -247,8 +248,11 @@ const filterPostsByDate = (posts: PostsAcc[], cutoffDate: Date) => { }); return filteredPosts; }; + +const postsMutex = new Mutex(); // nightmare function. However it works so I am not touching it const getNextPosts = async () => { + const release = await postsMutex.obtain(); if (!accountsMetadata.length) { accountsMetadata = await getAllMetadataFromPds(); } @@ -322,6 +326,8 @@ const getNextPosts = async () => { } return new Post(record, account); }); + // release the mutex + release(); return newPosts; }; From c77cdb4b79a7c888c4a9f64043318d373c21ab9d Mon Sep 17 00:00:00 2001 From: Ari Date: Thu, 29 May 2025 08:38:03 +0000 Subject: [PATCH 12/14] Custom themes and config overrides (#9) Co-authored-by: Astra Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/9 Reviewed-by: Astra Co-authored-by: Ari Co-committed-by: Ari --- .forgejo/workflows/deploy.yaml | 13 +- .gitignore | 5 +- README.md | 4 +- config.ts | 37 --- config.ts.example | 44 ++++ src/App.svelte | 109 ++------ src/app.css | 90 +------ src/lib/AccountComponent.svelte | 25 -- src/lib/PostComponent.svelte | 164 +------------ themes/default/theme.css | 423 ++++++++++++++++++++++++++++++++ themes/express/theme.css | 370 ++++++++++++++++++++++++++++ themes/witchcraft/theme.css | 367 +++++++++++++++++++++++++++ theming.ts | 32 +++ vite.config.ts | 6 +- 14 files changed, 1286 insertions(+), 403 deletions(-) delete mode 100644 config.ts create mode 100644 config.ts.example create mode 100644 themes/default/theme.css create mode 100644 themes/express/theme.css create mode 100644 themes/witchcraft/theme.css create mode 100644 theming.ts diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index c6a05d0..7f20d99 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -6,15 +6,26 @@ on: - main - astra/ci + jobs: deploy: name: Deploy runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout main repo uses: actions/checkout@v4 + - name: Checkout overrides repo + uses: actions/checkout@v4 + with: + repository: scientific-witchery/pds-dash-overrides + token: ${{ secrets.OVERRIDES_TOKEN}} + path: overrides + + - name: Copy config file to root + run: cp overrides/config.ts ./config.ts + - name: Setup Node.js uses: actions/setup-node@v3 with: diff --git a/.gitignore b/.gitignore index 42a517f..991a625 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,7 @@ dist .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz -.pnp.* \ No newline at end of file +.pnp.* + +# Config files +config.ts \ No newline at end of file diff --git a/README.md b/README.md index d9eb2ea..45276cb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ a frontend dashboard with stats for your ATProto PDS. ### installing -clone the repo, install dependencies using deno: +clone the repo, copy `config.ts.example` to `config.ts` and edit it to your liking. + +then, install dependencies using deno: ```sh deno install diff --git a/config.ts b/config.ts deleted file mode 100644 index 8d09cf6..0000000 --- a/config.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Configuration module for the PDS Dashboard - */ -export class Config { - /** - * The base URL of the PDS (Personal Data Server) - * @default "https://pds.witchcraft.systems" - */ - static readonly PDS_URL: string = "https://pds.witchcraft.systems"; - - /** - * The base URL of the frontend service for linking to replies/quotes/accounts etc. - * @default "https://deer.social" - */ - static readonly FRONTEND_URL: string = "https://deer.social"; - - /** - * Maximum number of posts to fetch from the PDS per request - * Should be around 20 for about 10 users on the pds - * The more users you have, the lower the number should be - * since sorting is slow and is done on the frontend - * @default 20 - */ - static readonly MAX_POSTS: number = 20; - - /** - * Footer text for the dashboard, you probably want to change this - */ - static readonly FOOTER_TEXT: string = - "Astrally projected from witchcraft.systems

Source (github mirror)"; - - /** - * Whether to show the posts that are in the future - * @default false - */ - static readonly SHOW_FUTURE_POSTS: boolean = false; -} diff --git a/config.ts.example b/config.ts.example new file mode 100644 index 0000000..87894d3 --- /dev/null +++ b/config.ts.example @@ -0,0 +1,44 @@ +/** + * Configuration module for the PDS Dashboard + */ +export class Config { + /** + * The base URL of the PDS (Personal Data Server). + * @default none + */ + static readonly PDS_URL: string = ""; + + /** + * Theme to be used + * @default "default" + */ + static readonly THEME: string = "default"; + + /** + * The base URL of the frontend service for linking to replies/quotes/accounts etc. + * @default "https://deer.social" // or https://bsky.app if you're boring + */ + static readonly FRONTEND_URL: string = "https://deer.social"; + + /** + * Maximum number of posts to fetch from the PDS per request + * Should be around 20 for about 10 users on the pds + * The more users you have, the lower the number should be + * since sorting is slow and is done on the frontend + * @default 20 + */ + static readonly MAX_POSTS: number = 20; + + /** + * Footer text for the dashboard, you probably want to change this. Supports HTML. + * @default "Source (github mirror)" + */ + static readonly FOOTER_TEXT: string = + "Source (github mirror)"; + + /** + * Whether to show the posts with timestamps that are in the future. + * @default false + */ + static readonly SHOW_FUTURE_POSTS: boolean = false; +} diff --git a/src/App.svelte b/src/App.svelte index c6e7534..798b8a7 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -9,6 +9,26 @@ let posts: Post[] = []; + let hue: number = 1; + const cycleColors = async () => { + while (true) { + hue += 1; + if (hue > 360) { + hue = 0; + } + document.documentElement.style.setProperty("--primary-h", hue.toString()); + await new Promise((resolve) => setTimeout(resolve, 10)); + } + } + let clickCounter = 0; + const carameldansenfusion = async () => { + clickCounter++; + if (clickCounter >= 10) { + clickCounter = 0; + cycleColors(); + } + }; + onMount(() => { // Fetch initial posts getNextPosts().then((initialPosts) => { @@ -39,7 +59,7 @@

Loading...

{:then accountsData}
-

ATProto PDS

+

ATProto PDS

Home to {accountsData.length} accounts

{#each accountsData as accountObject} @@ -64,90 +84,5 @@ diff --git a/src/app.css b/src/app.css index 50da734..cb315e4 100644 --- a/src/app.css +++ b/src/app.css @@ -1,88 +1,4 @@ -@font-face { - font-family: "ProggyClean"; - src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf); -} - -:root { - --link-color: #646cff; - --link-hover-color: #535bf2; - --background-color: #12082b; - --header-background-color: #1f1145; - --content-background-color: #0d0620; - --text-color: white; - --border-color: #8054f0; - --indicator-inactive-color: #4a4a4a; - --indicator-active-color: #8054f0; -} - -::-webkit-scrollbar { - width: 0px; - background: transparent; - padding: 0; - margin: 0; -} -::-webkit-scrollbar-thumb { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-track { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-corner { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-button { - background: transparent; - border-radius: 0; -} -* { - scrollbar-width: none; - scrollbar-color: transparent transparent; - -ms-overflow-style: none; /* IE and Edge */ - -webkit-overflow-scrolling: touch; - -webkit-scrollbar: none; /* Safari */ -} - -a { - font-weight: 500; - color: var(--link-color); - text-decoration: inherit; -} -a:hover { - color: var(--link-hover-color); - text-decoration: underline; -} - +@import url('./themes/colors.css'); body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; - background-color: var(--background-color); - font-family: "ProggyClean", monospace; - font-size: 24px; - color: var(--text-color); - border-color: var(--border-color); - overflow-wrap: break-word; - word-wrap: normal; - word-break: break-word; - hyphens: none; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -#app { - max-width: 1400px; - width: 100%; - margin: 0; - padding: 0; - margin-left: auto; - margin-right: auto; - text-align: center; -} + background-color: red; +} \ No newline at end of file diff --git a/src/lib/AccountComponent.svelte b/src/lib/AccountComponent.svelte index 880db3f..3f12cf7 100644 --- a/src/lib/AccountComponent.svelte +++ b/src/lib/AccountComponent.svelte @@ -20,30 +20,5 @@ diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index 43ad667..1cacc28 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -71,7 +71,7 @@ >

{post.authorHandle}@{post.authorHandle} diff --git a/themes/default/theme.css b/themes/default/theme.css new file mode 100644 index 0000000..e4061ec --- /dev/null +++ b/themes/default/theme.css @@ -0,0 +1,423 @@ +/* Modern Theme for pds-dash */ + +:root { + /* Modern color palette */ + --primary-h: 243; + --link-color: hsl(var(--primary-h), 73%, 59%); + --link-hover-color: #4338ca; + --time-color: #8b5cf6; + --background-color: #f8fafc; + --header-background-color: #ffffff; + --content-background-color: #ffffff; + --text-color: #111827; + --text-secondary-color: #4b5563; + --border-color: #e2e8f0; + --indicator-inactive-color: #cbd5e1; + --indicator-active-color: #6366f1; + + /* Modern shadows */ + --button-hover: #f3f4f6; +} + + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; + background-color: var(--background-color); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + font-size: 18px; + line-height: 1.5; + color: var(--text-color); + border-color: var(--border-color); + overflow-wrap: break-word; + word-break: break-word; + hyphens: none; +} + +a { + font-weight: 500; + color: var(--link-color); + text-decoration: none; + transition: color 0.15s ease; +} +a:hover { + color: var(--link-hover-color); +} + +h1 { + font-size: 2.5em; + line-height: 1.2; + font-weight: 700; +} + +#app { + max-width: 1400px; + width: 100%; + margin: 0 auto; + padding: 0; + text-align: center; +} + +/* Post Component */ +#postContainer { + display: flex; + flex-direction: column; + border-radius: 12px; + border: 1px solid var(--border-color); + background-color: var(--content-background-color); + margin-bottom: 20px; + overflow-wrap: break-word; + overflow: hidden; + box-shadow: var(--card-shadow); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +#postContainer:hover { + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +#postHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + background-color: var(--header-background-color); + padding: 12px 16px; + height: 60px; + border-bottom: 1px solid var(--border-color); + font-weight: 600; + overflow-wrap: break-word; +} + +#displayName { + display: block; + color: var(--text-color); + font-size: 1.1em; + padding: 0; + margin: 0 0 2px 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + letter-spacing: -0.01em; +} + +#handle { + display: flex; + align-items: center; + color: #6b7280; + font-size: 0.85em; + font-weight: 400; + padding: 0; + margin: 0; + gap: 8px; +} + +#postLink { + color: var(--time-color); + font-size: 0.85em; + padding: 0; + margin: 0; + opacity: 0.9; +} + +#postContent { + display: flex; + text-align: start; + flex-direction: column; + padding: 16px; + background-color: var(--content-background-color); + color: var(--text-color); + overflow-wrap: break-word; + white-space: pre-line; + line-height: 1.6; +} + +#replyingText, #quotingText { + font-size: 0.8em; + margin: 0; + padding: 0 0 10px 0; + color: #6b7280; +} + +#postText { + margin: 0 0 8px 0; + padding: 0; + overflow-wrap: break-word; + word-break: break-word; + hyphens: none; + font-size: 1.05em; +} + +#headerText { + margin-left: 12px; + font-size: 0.9em; + text-align: start; + word-break: break-word; + max-width: 80%; + max-height: 95%; + overflow: hidden; + align-self: flex-start; + margin-top: auto; + margin-bottom: auto; +} + +#carouselContainer { + position: relative; + width: 100%; + margin-top: 12px; + display: flex; + flex-direction: column; + align-items: center; + border-radius: 8px; + overflow: hidden; +} + +#carouselControls { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + max-width: 500px; + margin-top: 10px; +} + +#carouselIndicators { + display: flex; + gap: 6px; +} + +.indicator { + width: 6px; + height: 6px; + background-color: var(--indicator-inactive-color); + border-radius: 50%; + transition: background-color 0.2s ease, transform 0.2s ease; +} + +.indicator.active { + background-color: var(--indicator-active-color); + transform: scale(1.3); +} + +#prevBtn, +#nextBtn { + background-color: var(--button-bg); + color: var(--text-color); + border: 1px solid var(--border-color); + width: 32px; + height: 32px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background-color 0.15s ease, transform 0.15s ease; + font-size: 16px; +} + +#prevBtn:hover:not(:disabled), +#nextBtn:hover:not(:disabled) { + background-color: var(--button-hover); + transform: scale(1.05); +} + +#prevBtn:disabled, +#nextBtn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +#embedVideo { + width: 100%; + max-width: 500px; + margin-top: 12px; + align-self: center; + border-radius: 8px; + overflow: hidden; +} + +#embedImages { + min-width: min(100%, 500px); + max-width: min(100%, 500px); + max-height: 500px; + object-fit: contain; + margin: 0; + border-radius: 8px; +} + +/* Account Component */ +#accountContainer { + display: flex; + text-align: start; + align-items: center; + background-color: var(--content-background-color); + padding: 12px; + margin-bottom: 15px; + border: 1px solid var(--border-color); + border-radius: 12px; + transition: background-color 0.15s ease; +} + +#accountContainer:hover { + background-color: var(--hover-bg); +} + +#accountName { + margin-left: 12px; + font-size: 0.95em; + max-width: 80%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; +} + +#avatar { + width: 48px; + height: 48px; + margin: 0; + object-fit: cover; + border-radius: 50%; + border: 2px solid white; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +/* App.Svelte Layout */ +#Content { + display: flex; + width: 100%; + height: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: var(--background-color); + color: var(--text-color); + gap: 24px; +} + +#Feed { + overflow-y: auto; + width: 65%; + height: 100vh; + padding-right: 16px; + align-self: flex-start; +} + +#spacer { + padding: 0; + margin: 0; + height: 10vh; + width: 100%; +} + +#Account { + width: 35%; + display: flex; + flex-direction: column; + border: 1px solid var(--border-color); + background-color: var(--content-background-color); + max-height: 80vh; + padding: 24px; + margin-left: 16px; + border-radius: 12px; + box-shadow: var(--card-shadow); +} + +#accountsList { + display: flex; + flex-direction: column; + overflow-y: auto; + height: 100%; + width: 100%; + padding: 8px 0; + margin: 0; +} + +#Header { + text-align: center; + font-size: 1.8em; + margin-bottom: 16px; + font-weight: 700; + background: linear-gradient(to right, var(--link-color), #8b5cf6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Mobile Styles */ +@media screen and (max-width: 768px) { + #Content { + flex-direction: column; + width: auto; + padding: 12px; + margin-top: 0; + } + + #Account { + width: calc(100% - 32px); + padding: 16px; + margin-bottom: 20px; + margin-left: 0; + margin-right: 0; + height: auto; + order: -1; + } + + #Feed { + width: 100%; + margin: 0; + padding: 0; + overflow-y: visible; + } + + #spacer { + height: 5vh; + } + + body { + font-size: 16px; + } + + #postHeader { + padding: 10px; + height: auto; + min-height: 50px; + } +} + +/* Scrollbar Styles */ +::-webkit-scrollbar { + width: 0px; + background: transparent; + padding: 0; + margin: 0; +} +::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-track { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-corner { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-button { + background: transparent; + border-radius: 0; +} + +* { + scrollbar-width: none; + scrollbar-color: transparent transparent; + -ms-overflow-style: none; /* IE and Edge */ + -webkit-overflow-scrolling: touch; + -webkit-scrollbar: none; /* Safari */ +} \ No newline at end of file diff --git a/themes/express/theme.css b/themes/express/theme.css new file mode 100644 index 0000000..cbc2336 --- /dev/null +++ b/themes/express/theme.css @@ -0,0 +1,370 @@ +@import url("https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap"); + +:root { + /* Color overrides, edit to whatever you want */ + --primary-h: 341; /* Hue */ + --background-color: hsl(var(--primary-h), 62%, 30%); + --text-color: hsl(var(--primary-h), 69%, 18%); + --link-color: hsl(var(--primary-h), 100%, 20%); + --link-hover-color: hsl(var(--primary-h), 20%, 20%); + --border-color: hsl(var(--primary-h), 59%, 52%); + --content-background-color: hsl(var(--primary-h), 97%, 73%); + + --header-background-color: hsl(var(--primary-h), 97%, 73%); + --indicator-inactive-color: #4a4a4a; + --indicator-active-color: var(--border-color); +} + +a { + font-weight: 500; + color: var(--link-color); + text-decoration: inherit; +} +a:hover { + color: var(--link-hover-color); + text-decoration: underline; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; + background-color: var(--background-color); + font-family: "Share Tech Mono", monospace; + font-size: 24px; + color: var(--text-color); + border-color: var(--border-color); + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1400px; + width: 100%; + margin: 0; + padding: 0; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +/* Post Component */ +a:hover { + text-decoration: underline; +} +#postContainer { + display: flex; + flex-direction: column; + border: 4px solid var(--border-color); + background-color: var(--background-color); + margin-bottom: 15px; + overflow-wrap: break-word; + box-shadow: var(--border-color) 10px 10px; +} +#postHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + background-color: var(--header-background-color); + padding: 0px 0px; + height: fit-content; + + font-weight: bold; + overflow-wrap: break-word; + height: 64px; +} +#displayName { + display: block; + color: var(--text-color); + font-size: 1.2em; + padding: 0; + margin: 0; + overflow-wrap: normal; + word-wrap: break-word; + word-break: break-word; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; +} +#handle { + display: block; + color: var(--border-color); + font-size: 0.8em; + padding: 0; + margin: 0; +} + +#postLink { + color: var(--link-hover-color); + font-size: 0.8em; + padding: 0; + margin: 0; +} +#postContent { + display: flex; + text-align: start; + flex-direction: column; + padding: 10px; + background-color: var(--content-background-color); + color: var(--text-color); + overflow-wrap: break-word; + white-space: pre-line; +} +#replyingText { + font-size: 0.7em; + margin: 0; + padding: 0; + padding-bottom: 5px; +} +#quotingText { + font-size: 0.7em; + margin: 0; + padding: 0; + padding-bottom: 5px; +} +#postText { + margin: 0; + padding: 0; + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; +} +#headerText { + margin-left: 10px; + font-size: 0.9em; + text-align: start; + word-break: break-word; + max-width: 80%; + max-height: 95%; + overflow: hidden; + align-self: flex-start; + margin-top: auto; + margin-bottom: auto; +} +#avatar { + height: 30px; + width: 30px; + overflow: hidden; + object-fit: cover; +} +#postContainer #avatar { + height: 60px; + width: 60px; + border-right: var(--border-color) 4px solid; + border-bottom: var(--border-color) 4px solid; +} +#carouselContainer { + position: relative; + width: 100%; + margin-top: 10px; + display: flex; + flex-direction: column; + align-items: center; +} +#carouselControls { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + max-width: 500px; + margin-top: 5px; +} +#carouselIndicators { + display: flex; + gap: 5px; +} +.indicator { + width: 8px; + height: 8px; + background-color: var(--indicator-inactive-color); +} +.indicator.active { + background-color: var(--indicator-active-color); +} +#prevBtn, +#nextBtn { + background-color: rgba(31, 17, 69, 0.7); + color: var(--text-color); + border: 4px solid var(--border-color); + width: 30px; + height: 30px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +#prevBtn:disabled, +#nextBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +#embedVideo { + width: 100%; + max-width: 500px; + margin-top: 10px; + align-self: center; +} + +#embedImages { + min-width: min(100%, 500px); + max-width: min(100%, 500px); + max-height: 500px; + object-fit: contain; + + margin: 0; +} + +/* Account Component */ +#accountContainer { + display: flex; + text-align: start; + align-items: center; + background-color: var(--header-background-color); + padding: 0px; + margin-bottom: 15px; + margin-right: 4px; + border: 4px solid var(--border-color); + box-shadow: var(--border-color) 10px 10px; +} +#accountName { + margin-left: 10px; + font-size: 1em; + max-width: 80%; + + /* replace overflow with ellipsis */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* App.Svelte */ +/* desktop style */ + +#Content { + display: flex; + /* split the screen in half, left for accounts, right for posts */ + width: 100%; + height: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: var(--background-color); + color: var(--text-color); +} +#Feed { + overflow-y: scroll; + width: 65%; + height: 100vh; + padding: 20px; + padding-bottom: 0; + padding-top: 0; + margin-top: 0; + margin-bottom: 0; +} +#spacer { + padding: 0; + margin: 0; + height: 10vh; + width: 100%; +} +#Account { + width: 35%; + display: flex; + flex-direction: column; + border: 4px solid var(--border-color); + background-color: var(--content-background-color); + box-shadow: var(--border-color) 10px 10px; + height: 80vh; + padding: 20px; + margin-left: 20px; +} +#accountsList { + display: flex; + flex-direction: column; + overflow-y: scroll; + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; +} + +#Header { + text-align: center; + font-size: 2em; + margin-bottom: 20px; +} + +/* mobile style */ +@media screen and (max-width: 600px) { + #Content { + flex-direction: column; + width: auto; + padding-left: 0px; + padding-right: 0px; + margin-top: 5%; + } + #Account { + width: 85%; + padding-left: 5%; + padding-right: 5%; + margin-bottom: 20px; + margin-left: 5%; + margin-right: 5%; + height: auto; + } + #Feed { + width: 95%; + margin: 0px; + margin-left: 10%; + margin-right: 10%; + padding: 0px; + overflow-y: visible; + } + + #spacer { + height: 0; + } +} + +::-webkit-scrollbar { + width: 0px; + background: transparent; + padding: 0; + margin: 0; +} +::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-track { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-corner { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-button { + background: transparent; + border-radius: 0; +} + +* { + scrollbar-width: none; + scrollbar-color: transparent transparent; + -ms-overflow-style: none; /* IE and Edge */ + -webkit-overflow-scrolling: touch; + -webkit-scrollbar: none; /* Safari */ +} diff --git a/themes/witchcraft/theme.css b/themes/witchcraft/theme.css new file mode 100644 index 0000000..cd15805 --- /dev/null +++ b/themes/witchcraft/theme.css @@ -0,0 +1,367 @@ +@font-face { + font-family: "ProggyClean"; + src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf); +} + +:root { + /* Color overrides, edit to whatever you want */ + --primary-h: 260; /* Hue */ + + --link-color: hsl(calc(var(--primary-h) - 30), 75%, 60%); + --link-hover-color: hsl(calc(var(--primary-h) - 30), 75%, 50%); + --background-color: hsl(var(--primary-h), 75%, 10%); + --header-background-color: hsl(var(--primary-h), 75%, 18%); + --content-background-color: hsl(var(--primary-h), 75%, 8%); + --text-color: #fff; + --border-color: hsl(var(--primary-h), 75%, 60%); + --indicator-inactive-color: #4a4a4a; + --indicator-active-color: var(--border-color); +} + + +a { + font-weight: 500; + color: var(--link-color); + text-decoration: inherit; +} +a:hover { + color: var(--link-hover-color); + text-decoration: underline; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; + background-color: var(--background-color); + font-family: "ProggyClean", monospace; + font-size: 24px; + color: var(--text-color); + border-color: var(--border-color); + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1400px; + width: 100%; + margin: 0; + padding: 0; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +/* Post Component */ +a:hover { + text-decoration: underline; +} +#postContainer { + display: flex; + flex-direction: column; + border: 1px solid var(--border-color); + background-color: var(--background-color); + margin-bottom: 15px; + overflow-wrap: break-word; +} +#postHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: start; + background-color: var(--header-background-color); + padding: 0px 0px; + height: fit-content; + border-bottom: 1px solid var(--border-color); + font-weight: bold; + overflow-wrap: break-word; + height: 60px; +} +#displayName { + display: block; + color: var(--text-color); + font-size: 1.2em; + padding: 0; + margin: 0; + overflow-wrap: normal; + word-wrap: break-word; + word-break: break-word; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; +} +#handle { + display: block; + color: var(--border-color); + font-size: 0.8em; + padding: 0; + margin: 0; +} + +#postLink { + color: var(--border-color); + font-size: 0.8em; + padding: 0; + margin: 0; +} +#postContent { + display: flex; + text-align: start; + flex-direction: column; + padding: 10px; + background-color: var(--content-background-color); + color: var(--text-color); + overflow-wrap: break-word; + white-space: pre-line; +} +#replyingText { + font-size: 0.7em; + margin: 0; + padding: 0; + padding-bottom: 5px; +} +#quotingText { + font-size: 0.7em; + margin: 0; + padding: 0; + padding-bottom: 5px; +} +#postText { + margin: 0; + padding: 0; + overflow-wrap: break-word; + word-wrap: normal; + word-break: break-word; + hyphens: none; +} +#headerText { + margin-left: 10px; + font-size: 0.9em; + text-align: start; + word-break: break-word; + max-width: 80%; + max-height: 95%; + overflow: hidden; + align-self: flex-start; + margin-top: auto; + margin-bottom: auto; +} +#avatar { + height: 60px; + width: 60px; + margin: 0px; + margin-left: 0px; + overflow: hidden; + object-fit: cover; + border-right: var(--border-color) 1px solid; +} +#carouselContainer { + position: relative; + width: 100%; + margin-top: 10px; + display: flex; + flex-direction: column; + align-items: center; +} +#carouselControls { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + max-width: 500px; + margin-top: 5px; +} +#carouselIndicators { + display: flex; + gap: 5px; +} +.indicator { + width: 8px; + height: 8px; + background-color: var(--indicator-inactive-color); +} +.indicator.active { + background-color: var(--indicator-active-color); +} +#prevBtn, +#nextBtn { + background-color: rgba(31, 17, 69, 0.7); + color: var(--text-color); + border: 1px solid var(--border-color); + width: 30px; + height: 30px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +#prevBtn:disabled, +#nextBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +#embedVideo { + width: 100%; + max-width: 500px; + margin-top: 10px; + align-self: center; +} + +#embedImages { + min-width: min(100%, 500px); + max-width: min(100%, 500px); + max-height: 500px; + object-fit: contain; + + margin: 0; +} + +/* Account Component */ +#accountContainer { + display: flex; + text-align: start; + align-items: center; + background-color: var(--background-color); + padding: 0px; + margin-bottom: 15px; + border: 1px solid var(--border-color); +} +#accountName { + margin-left: 10px; + font-size: 1em; + max-width: 80%; + + /* replace overflow with ellipsis */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* App.Svelte */ +/* desktop style */ + +#Content { + display: flex; + /* split the screen in half, left for accounts, right for posts */ + width: 100%; + height: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: var(--background-color); + color: var(--text-color); +} +#Feed { + overflow-y: scroll; + width: 65%; + height: 100vh; + padding: 20px; + padding-bottom: 0; + padding-top: 0; + margin-top: 0; + margin-bottom: 0; +} +#spacer { + padding: 0; + margin: 0; + height: 10vh; + width: 100%; +} +#Account { + width: 35%; + display: flex; + flex-direction: column; + border: 1px solid var(--border-color); + background-color: var(--content-background-color); + height: 80vh; + padding: 20px; + margin-left: 20px; +} +#accountsList { + display: flex; + flex-direction: column; + overflow-y: scroll; + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; +} + +#Header { + text-align: center; + font-size: 2em; + margin-bottom: 20px; +} + +/* mobile style */ +@media screen and (max-width: 600px) { + #Content { + flex-direction: column; + width: auto; + padding-left: 0px; + padding-right: 0px; + margin-top: 5%; + } + #Account { + width: 85%; + padding-left: 5%; + padding-right: 5%; + margin-bottom: 20px; + margin-left: 5%; + margin-right: 5%; + height: auto; + } + #Feed { + width: 95%; + margin: 0px; + margin-left: 10%; + margin-right: 10%; + padding: 0px; + overflow-y: visible; + } + + #spacer { + height: 0; + } +} + +::-webkit-scrollbar { + width: 0px; + background: transparent; + padding: 0; + margin: 0; +} +::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-track { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-corner { + background: transparent; + border-radius: 0; +} +::-webkit-scrollbar-button { + background: transparent; + border-radius: 0; +} + +* { + scrollbar-width: none; + scrollbar-color: transparent transparent; + -ms-overflow-style: none; /* IE and Edge */ + -webkit-overflow-scrolling: touch; + -webkit-scrollbar: none; /* Safari */ +} \ No newline at end of file diff --git a/theming.ts b/theming.ts new file mode 100644 index 0000000..3b5964b --- /dev/null +++ b/theming.ts @@ -0,0 +1,32 @@ +import { Plugin } from 'vite'; +import { Config } from './config'; + + +// Replaces app.css with the contents of the file specified in the +// config file. +export const themePlugin = (): Plugin => { + const themeFolder = Config.THEME; + console.log(`Using theme folder: ${themeFolder}`); + return { + name: 'theme-generator', + enforce: 'pre', // Ensure this plugin runs first + transform(code, id) { + if (id.endsWith('app.css')) { + // Read the theme file and replace the contents of app.css with it + // Needs full path to the file + const themeCode = Deno.readTextFileSync(Deno.cwd() + '/themes/' + themeFolder + '/theme.css'); + // Replace the contents of app.css with the theme code + + // and add a comment at the top + const themeComment = `/* Generated from ${themeFolder} */\n`; + const themeCodeWithComment = themeComment + themeCode; + // Return the theme code as the new contents of app.css + return { + code: themeCodeWithComment, + map: null, + }; + } + return null; + } + }; +}; \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 20d2272..96d3c8c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,11 @@ import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { themePlugin } from "./theming"; // https://vite.dev/config/ export default defineConfig({ - plugins: [svelte()], + plugins: [ + themePlugin(), + svelte(), + ], }); From 273741d2213905f45fb1cede8d3bb6bc7e84cf54 Mon Sep 17 00:00:00 2001 From: Astra Date: Thu, 29 May 2025 20:19:25 +0900 Subject: [PATCH 13/14] Update the info on theming in README.md --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 45276cb..5fd9458 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,11 @@ we use our own CI/CD workflow at [`.forgejo/workflows/deploy.yaml`](.forgejo/wor ## theming -the colors are designated in [`src/app.css`](src/app.css) as variables, go crazy with them +themes are located in the `themes/` directory, you can create your own theme by copying one of the existing themes and modifying it to your liking. -the rest is done by editing the css files and style tags directly, good luck +currently, the name of the theme is determined by the directory name, and the theme itself is defined in `theme.css` inside that directory. -relevant files: - -- [`src/App.svelte`](src/App.svelte) -- [`src/app.css`](src/app.css) -- [`src/lib/AccountComponent.svelte`](src/lib/AccountComponent.svelte) -- [`src/lib/PostComponent.svelte`](src/lib/PostComponent.svelte) +you can switch themes by changing the `theme` property in `config.ts`. the favicon is located at [`public/favicon.ico`](public/favicon.ico) From c348ed5d46a0d95422ea6f4925420be8ff3ce8f0 Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 30 May 2025 03:05:05 +0000 Subject: [PATCH 14/14] Fix users with no profile record not being shown (#7) Co-authored-by: Casey Co-authored-by: Ari Reviewed-on: https://git.witchcraft.systems/scientific-witchery/pds-dash/pulls/7 Reviewed-by: Ariadna --- src/lib/AccountComponent.svelte | 10 +++++++--- src/lib/pdsfetch.ts | 27 +++++++++++++++++---------- themes/express/theme.css | 5 +++++ themes/witchcraft/theme.css | 6 ++++++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/lib/AccountComponent.svelte b/src/lib/AccountComponent.svelte index 3f12cf7..faf6251 100644 --- a/src/lib/AccountComponent.svelte +++ b/src/lib/AccountComponent.svelte @@ -12,10 +12,14 @@ alt="avatar of {account.displayName}" src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}" /> +

+ {:else} +
+ {account.displayName || account.handle || account.did} +
{/if} -
- {account.displayName || account.handle || account.did} -
diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index 20120fb..d57aff4 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -132,7 +132,13 @@ const getDidsFromPDS = async (): Promise => { const getAccountMetadata = async ( did: `did:${string}:${string}`, ) => { - // gonna assume self exists in the app.bsky.actor.profile + const account: AccountMetadata = { + did: did, + handle: "", // Guaranteed to be filled out later + displayName: "", + avatarCid: null, + }; + try { const { data } = await rpc.get("com.atproto.repo.getRecord", { params: { @@ -142,21 +148,22 @@ const getAccountMetadata = async ( }, }); const value = data.value as AppBskyActorProfile.Record; - const handle = await blueskyHandleFromDid(did); - const account: AccountMetadata = { - did: did, - handle: handle, - displayName: value.displayName || "", - avatarCid: null, - }; + account.displayName = value.displayName || ""; if (value.avatar) { account.avatarCid = value.avatar.ref["$link"]; } - return account; } catch (e) { - console.error(`Error fetching metadata for ${did}:`, e); + console.warn(`Error fetching profile for ${did}:`, e); + } + + try { + account.handle = await blueskyHandleFromDid(did); + } catch (e) { + console.error(`Error fetching handle for ${did}:`, e); return null; } + + return account; }; const getAllMetadataFromPds = async (): Promise => { diff --git a/themes/express/theme.css b/themes/express/theme.css index cbc2336..58c399e 100644 --- a/themes/express/theme.css +++ b/themes/express/theme.css @@ -237,6 +237,7 @@ a:hover { margin-right: 4px; border: 4px solid var(--border-color); box-shadow: var(--border-color) 10px 10px; + min-height: 30px; } #accountName { margin-left: 10px; @@ -249,6 +250,10 @@ a:hover { white-space: nowrap; } +.no-avatar { + margin-left: 40px !important; +} + /* App.Svelte */ /* desktop style */ diff --git a/themes/witchcraft/theme.css b/themes/witchcraft/theme.css index cd15805..8ad63d4 100644 --- a/themes/witchcraft/theme.css +++ b/themes/witchcraft/theme.css @@ -235,6 +235,7 @@ a:hover { padding: 0px; margin-bottom: 15px; border: 1px solid var(--border-color); + min-height: 30px; } #accountName { margin-left: 10px; @@ -247,6 +248,11 @@ a:hover { white-space: nowrap; } + +.no-avatar { + margin-left: 70px !important; +} + /* App.Svelte */ /* desktop style */