Compare commits

..

No commits in common. "main" and "astra/cleanup" have entirely different histories.

17 changed files with 530 additions and 1711 deletions

View file

@ -6,26 +6,15 @@ on:
- main
- astra/ci
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout main repo
- name: Checkout 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:

5
.gitignore vendored
View file

@ -149,7 +149,4 @@ dist
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Config files
config.ts
.pnp.*

View file

@ -10,9 +10,7 @@ a frontend dashboard with stats for your ATProto PDS.
### installing
clone the repo, copy `config.ts.example` to `config.ts` and edit it to your liking.
then, install dependencies using deno:
clone the repo, install dependencies using deno:
```sh
deno install
@ -46,11 +44,14 @@ we use our own CI/CD workflow at [`.forgejo/workflows/deploy.yaml`](.forgejo/wor
## theming
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.
currently the only way to theme the app is to edit css in the components directly, glhf
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:
you can switch themes by changing the `theme` property in `config.ts`.
- [`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)
the favicon is located at [`public/favicon.ico`](public/favicon.ico)

35
config.ts Normal file
View file

@ -0,0 +1,35 @@
/**
* 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
* @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
*/
static readonly MAX_POSTS: number = 100;
/**
* Footer text for the dashboard
* @default "Astrally projected from witchcraft.systems"
*/
static readonly FOOTER_TEXT: string =
"Astrally projected from <a href='https://witchcraft.systems' target='_blank'>witchcraft.systems</a>";
/**
* Whether to show the posts that are in the future
* @default false
*/
static readonly SHOW_FUTURE_POSTS: boolean = false;
}

View file

@ -1,44 +0,0 @@
/**
* 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 "<a href='https://git.witchcraft.systems/scientific-witchery/pds-dash' target='_blank'>Source</a> (<a href='https://github.com/witchcraft-systems/pds-dash/' target='_blank'>github mirror</a>)"
*/
static readonly FOOTER_TEXT: string =
"<a href='https://git.witchcraft.systems/scientific-witchery/pds-dash' target='_blank'>Source</a> (<a href='https://github.com/witchcraft-systems/pds-dash/' target='_blank'>github mirror</a>)";
/**
* Whether to show the posts with timestamps that are in the future.
* @default false
*/
static readonly SHOW_FUTURE_POSTS: boolean = false;
}

234
deno.lock generated
View file

@ -1,5 +1,5 @@
{
"version": "5",
"version": "4",
"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,9 +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",
"npm:typescript@~5.7.2": "5.7.3",
"npm:vite@^6.3.1": "6.3.2_picomatch@4.0.2"
@ -55,129 +53,79 @@
"integrity": "sha512-GEhUCk9c4XbNxi+0YZHZsV4fYNd6HejfWuN4Ti4c02DauX+LyX5WY1Y3WfyZ8Pxxl0zqhs+MLtW98cMh86vv6g=="
},
"@esbuild/aix-ppc64@0.25.2": {
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
"os": ["aix"],
"cpu": ["ppc64"]
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="
},
"@esbuild/android-arm64@0.25.2": {
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
"os": ["android"],
"cpu": ["arm64"]
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="
},
"@esbuild/android-arm@0.25.2": {
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
"os": ["android"],
"cpu": ["arm"]
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="
},
"@esbuild/android-x64@0.25.2": {
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
"os": ["android"],
"cpu": ["x64"]
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="
},
"@esbuild/darwin-arm64@0.25.2": {
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
"os": ["darwin"],
"cpu": ["arm64"]
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="
},
"@esbuild/darwin-x64@0.25.2": {
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
"os": ["darwin"],
"cpu": ["x64"]
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="
},
"@esbuild/freebsd-arm64@0.25.2": {
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
"os": ["freebsd"],
"cpu": ["arm64"]
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="
},
"@esbuild/freebsd-x64@0.25.2": {
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
"os": ["freebsd"],
"cpu": ["x64"]
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="
},
"@esbuild/linux-arm64@0.25.2": {
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
"os": ["linux"],
"cpu": ["arm64"]
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="
},
"@esbuild/linux-arm@0.25.2": {
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
"os": ["linux"],
"cpu": ["arm"]
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="
},
"@esbuild/linux-ia32@0.25.2": {
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
"os": ["linux"],
"cpu": ["ia32"]
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="
},
"@esbuild/linux-loong64@0.25.2": {
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
"os": ["linux"],
"cpu": ["loong64"]
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="
},
"@esbuild/linux-mips64el@0.25.2": {
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
"os": ["linux"],
"cpu": ["mips64el"]
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="
},
"@esbuild/linux-ppc64@0.25.2": {
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
"os": ["linux"],
"cpu": ["ppc64"]
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="
},
"@esbuild/linux-riscv64@0.25.2": {
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
"os": ["linux"],
"cpu": ["riscv64"]
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="
},
"@esbuild/linux-s390x@0.25.2": {
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
"os": ["linux"],
"cpu": ["s390x"]
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="
},
"@esbuild/linux-x64@0.25.2": {
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
"os": ["linux"],
"cpu": ["x64"]
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="
},
"@esbuild/netbsd-arm64@0.25.2": {
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
"os": ["netbsd"],
"cpu": ["arm64"]
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="
},
"@esbuild/netbsd-x64@0.25.2": {
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
"os": ["netbsd"],
"cpu": ["x64"]
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="
},
"@esbuild/openbsd-arm64@0.25.2": {
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
"os": ["openbsd"],
"cpu": ["arm64"]
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="
},
"@esbuild/openbsd-x64@0.25.2": {
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
"os": ["openbsd"],
"cpu": ["x64"]
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="
},
"@esbuild/sunos-x64@0.25.2": {
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
"os": ["sunos"],
"cpu": ["x64"]
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="
},
"@esbuild/win32-arm64@0.25.2": {
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
"os": ["win32"],
"cpu": ["arm64"]
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="
},
"@esbuild/win32-ia32@0.25.2": {
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
"os": ["win32"],
"cpu": ["ia32"]
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="
},
"@esbuild/win32-x64@0.25.2": {
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
"os": ["win32"],
"cpu": ["x64"]
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="
},
"@jridgewell/gen-mapping@0.3.8": {
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
@ -204,104 +152,64 @@
]
},
"@rollup/rollup-android-arm-eabi@4.40.0": {
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
"os": ["android"],
"cpu": ["arm"]
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="
},
"@rollup/rollup-android-arm64@4.40.0": {
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
"os": ["android"],
"cpu": ["arm64"]
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="
},
"@rollup/rollup-darwin-arm64@4.40.0": {
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
"os": ["darwin"],
"cpu": ["arm64"]
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="
},
"@rollup/rollup-darwin-x64@4.40.0": {
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
"os": ["darwin"],
"cpu": ["x64"]
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="
},
"@rollup/rollup-freebsd-arm64@4.40.0": {
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
"os": ["freebsd"],
"cpu": ["arm64"]
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="
},
"@rollup/rollup-freebsd-x64@4.40.0": {
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
"os": ["freebsd"],
"cpu": ["x64"]
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="
},
"@rollup/rollup-linux-arm-gnueabihf@4.40.0": {
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
"os": ["linux"],
"cpu": ["arm"]
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="
},
"@rollup/rollup-linux-arm-musleabihf@4.40.0": {
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
"os": ["linux"],
"cpu": ["arm"]
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="
},
"@rollup/rollup-linux-arm64-gnu@4.40.0": {
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
"os": ["linux"],
"cpu": ["arm64"]
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="
},
"@rollup/rollup-linux-arm64-musl@4.40.0": {
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
"os": ["linux"],
"cpu": ["arm64"]
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="
},
"@rollup/rollup-linux-loongarch64-gnu@4.40.0": {
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
"os": ["linux"],
"cpu": ["loong64"]
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="
},
"@rollup/rollup-linux-powerpc64le-gnu@4.40.0": {
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
"os": ["linux"],
"cpu": ["ppc64"]
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="
},
"@rollup/rollup-linux-riscv64-gnu@4.40.0": {
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
"os": ["linux"],
"cpu": ["riscv64"]
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="
},
"@rollup/rollup-linux-riscv64-musl@4.40.0": {
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
"os": ["linux"],
"cpu": ["riscv64"]
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="
},
"@rollup/rollup-linux-s390x-gnu@4.40.0": {
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
"os": ["linux"],
"cpu": ["s390x"]
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="
},
"@rollup/rollup-linux-x64-gnu@4.40.0": {
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
"os": ["linux"],
"cpu": ["x64"]
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="
},
"@rollup/rollup-linux-x64-musl@4.40.0": {
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
"os": ["linux"],
"cpu": ["x64"]
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="
},
"@rollup/rollup-win32-arm64-msvc@4.40.0": {
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
"os": ["win32"],
"cpu": ["arm64"]
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="
},
"@rollup/rollup-win32-ia32-msvc@4.40.0": {
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
"os": ["win32"],
"cpu": ["ia32"]
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="
},
"@rollup/rollup-win32-x64-msvc@4.40.0": {
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
"os": ["win32"],
"cpu": ["x64"]
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="
},
"@sveltejs/acorn-typescript@1.0.5_acorn@8.14.1": {
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
@ -338,8 +246,7 @@
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
},
"acorn@8.14.1": {
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"bin": true
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
},
"aria-query@5.3.2": {
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
@ -367,7 +274,7 @@
},
"esbuild@0.25.2": {
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
"optionalDependencies": [
"dependencies": [
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
"@esbuild/android-arm64",
@ -393,9 +300,7 @@
"@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=="
@ -410,15 +315,10 @@
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dependencies": [
"picomatch"
],
"optionalPeers": [
"picomatch"
]
},
"fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"os": ["darwin"],
"scripts": true
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
},
"is-reference@3.0.3": {
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
@ -447,12 +347,8 @@
"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==",
"bin": true
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
@ -474,9 +370,6 @@
"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",
@ -497,9 +390,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==",
@ -520,11 +413,7 @@
"sade",
"svelte",
"typescript"
],
"bin": true
},
"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==",
@ -553,31 +442,24 @@
]
},
"typescript@5.7.3": {
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"bin": true
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="
},
"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": {
@ -593,9 +475,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",
"npm:typescript@~5.7.2",
"npm:vite@^6.3.1"

View file

@ -13,9 +13,7 @@
"@atcute/bluesky": "^2.0.2",
"@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"
"moment": "^2.30.1"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.3",

View file

@ -1,56 +1,10 @@
<script lang="ts">
import PostComponent from "./lib/PostComponent.svelte";
import AccountComponent from "./lib/AccountComponent.svelte";
import InfiniteLoading from "svelte-infinite-loading";
import { getNextPosts, Post, getAllMetadataFromPds } from "./lib/pdsfetch";
import { fetchAllPosts, Post, getAllMetadataFromPds } from "./lib/pdsfetch";
import { Config } from "../config";
const postsPromise = fetchAllPosts();
const accountsPromise = getAllMetadataFromPds();
import { onMount } from "svelte";
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) => {
posts = initialPosts;
});
});
// 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();
} else {
complete();
}
});
};
</script>
<main>
@ -59,7 +13,7 @@
<p>Loading...</p>
{:then accountsData}
<div id="Account">
<h1 onclick={carameldansenfusion} id="Header">ATProto PDS</h1>
<h1 id="Header">ATProto PDS</h1>
<p>Home to {accountsData.length} accounts</p>
<div id="accountsList">
{#each accountsData as accountObject}
@ -72,17 +26,105 @@
<p>Error: {error.message}</p>
{/await}
<div id="Feed">
<div id="spacer"></div>
{#each posts as postObject}
<PostComponent post={postObject as Post} />
{/each}
<InfiniteLoading on:infinite={onInfinite} distance={3000} />
<div id="spacer"></div>
</div>
{#await postsPromise}
<p>Loading...</p>
{:then postsData}
<div id="Feed">
<div id="spacer"></div>
{#each postsData as postObject}
<PostComponent post={postObject as Post} />
{/each}
<div id="spacer"></div>
</div>
{/await}
</div>
</main>
<style>
/* 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 {
width: 65%;
height: 100vh;
overflow-y: scroll;
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: auto;
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;
height: auto;
}
#spacer {
height: 0;
}
}
</style>

View file

@ -1,4 +1,83 @@
@import url('./themes/colors.css');
@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;
}
body {
background-color: red;
}
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);
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1400px;
margin: 0;
padding: 0;
margin-left: auto;
margin-right: auto;
text-align: center;
}

View file

@ -12,17 +12,37 @@
alt="avatar of {account.displayName}"
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}"
/>
<div id="accountName">
{account.displayName || account.handle || account.did}
</div>
{:else}
<div id="accountName" class="no-avatar">
{account.displayName || account.handle || account.did}
</div>
{/if}
<div id="accountName">
{account.displayName || account.handle || account.did}
</div>
</div>
</a>
<style>
#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: 0.9em;
/* replace overflow with ellipsis */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 80%;
}
#avatar {
width: 50px;
height: 50px;
margin: 0px;
border-right: var(--border-color) 1px solid;
}
</style>

View file

@ -71,7 +71,7 @@
>
<p id="handle">
<a href="{Config.FRONTEND_URL}/profile/{post.authorHandle}"
>@{post.authorHandle}</a
>{post.authorHandle}</a
>
<a
@ -113,7 +113,7 @@
<div id="carouselControls">
<button
id="prevBtn"
onclick={prevImage}
on:click={prevImage}
disabled={currentImageIndex === 0}>←</button
>
<div id="carouselIndicators">
@ -125,7 +125,7 @@
</div>
<button
id="nextBtn"
onclick={nextImage}
on:click={nextImage}
disabled={currentImageIndex === post.imagesCid.length - 1}
>→</button
>
@ -141,16 +141,150 @@
controls
></video>
{/if}
{#if post.gifLink}
<img
id="embedVideo"
src="{post.gifLink}"
alt="Post GIF"
/>
{/if}
</div>
</div>
<style>
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 {
color: var(--text-color);
font-size: 1.2em;
padding: 0;
margin: 0;
}
#handle {
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;
}
#headerText {
margin-left: 10px;
font-size: 0.9em;
text-align: start;
overflow-wrap: break-word;
overflow: hidden;
}
#avatar {
height: 100%;
margin: 0px;
margin-left: 0px;
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;
}
</style>

View file

@ -13,21 +13,16 @@ 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";
interface AccountMetadata {
did: At.Did;
did: string;
displayName: string;
handle: string;
avatarCid: string | null;
currentCursor?: string;
}
let accountsMetadata: AccountMetadata[] = [];
interface atUriObject {
repo: string;
collection: string;
@ -47,11 +42,10 @@ class Post {
replyingUri: atUriObject | null;
imagesCid: string[] | null;
videosLinkCid: string | null;
gifLink: string | null;
constructor(
record: ComAtprotoRepoListRecords.Record,
account: AccountMetadata,
account: AccountMetadata
) {
this.postCid = record.cid;
this.recordName = processAtUri(record.uri).rkey;
@ -71,11 +65,10 @@ 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(
(imageRecord: any) => imageRecord.image.ref.$link,
(imageRecord: any) => imageRecord.image.ref.$link
);
break;
case "app.bsky.embed.video":
@ -89,7 +82,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;
@ -99,11 +92,6 @@ class Post {
break;
}
break;
case "app.bsky.embed.external": // assuming that external embeds are gifs for now
if (post.embed.external.uri.includes(".gif")) {
this.gifLink = post.embed.external.uri;
}
break;
}
}
}
@ -130,15 +118,9 @@ const getDidsFromPDS = async (): Promise<At.Did[]> => {
return data.repos.map((repo: any) => repo.did) as At.Did[];
};
const getAccountMetadata = async (
did: `did:${string}:${string}`,
) => {
const account: AccountMetadata = {
did: did,
handle: "", // Guaranteed to be filled out later
displayName: "",
avatarCid: null,
};
did: `did:${string}:${string}`
): Promise<AccountMetadata> => {
// gonna assume self exists in the app.bsky.actor.profile
try {
const { data } = await rpc.get("com.atproto.repo.getRecord", {
params: {
@ -148,22 +130,26 @@ const getAccountMetadata = async (
},
});
const value = data.value as AppBskyActorProfile.Record;
account.displayName = value.displayName || "";
const handle = await blueskyHandleFromDid(did);
const account: AccountMetadata = {
did: did,
handle: handle,
displayName: value.displayName || "",
avatarCid: null,
};
if (value.avatar) {
account.avatarCid = value.avatar.ref["$link"];
}
return account;
} catch (e) {
console.warn(`Error fetching profile for ${did}:`, e);
console.error(`Error fetching metadata for ${did}:`, e);
return {
did: "error",
displayName: "",
avatarCid: null,
handle: "error",
};
}
try {
account.handle = await blueskyHandleFromDid(did);
} catch (e) {
console.error(`Error fetching handle for ${did}:`, e);
return null;
}
return account;
};
const getAllMetadataFromPds = async (): Promise<AccountMetadata[]> => {
@ -171,9 +157,33 @@ const getAllMetadataFromPds = async (): Promise<AccountMetadata[]> => {
const metadata = await Promise.all(
dids.map(async (repo: `did:${string}:${string}`) => {
return await getAccountMetadata(repo);
}),
})
);
return metadata.filter((account) => account !== null) as AccountMetadata[];
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,
};
}
};
const identityResolve = async (did: At.Did) => {
@ -186,7 +196,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 {
@ -209,151 +219,36 @@ const blueskyHandleFromDid = async (did: At.Did) => {
}
};
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;
}
}
});
if (cutoffDate) {
return cutoffDate;
} else {
return new Date(now);
}
};
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,
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
);
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;
};
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();
}
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,
};
if (!user) {
throw new Error(`User with DID ${userFetch.did} not found`);
}
}),
return new Post(record, user);
})
);
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) {
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();
records = records.filter((post) => {
const postDate = new Date(
(post.value as AppBskyFeedPost.Record).createdAt,
).getTime();
return postDate <= now;
});
const filteredPosts = posts.filter((post) => post.timestamp <= now);
return filteredPosts.slice(0, Config.MAX_POSTS);
}
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);
});
// release the mutex
release();
return newPosts;
return posts.slice(0, Config.MAX_POSTS);
};
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 { fetchAllPosts, getAllMetadataFromPds, Post };
export type { AccountMetadata };

View file

@ -1,423 +0,0 @@
/* 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 */
}

View file

@ -1,375 +0,0 @@
@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;
min-height: 30px;
}
#accountName {
margin-left: 10px;
font-size: 1em;
max-width: 80%;
/* replace overflow with ellipsis */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.no-avatar {
margin-left: 40px !important;
}
/* 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 */
}

View file

@ -1,373 +0,0 @@
@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);
min-height: 30px;
}
#accountName {
margin-left: 10px;
font-size: 1em;
max-width: 80%;
/* replace overflow with ellipsis */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.no-avatar {
margin-left: 70px !important;
}
/* 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 */
}

View file

@ -1,32 +0,0 @@
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;
}
};
};

View file

@ -1,11 +1,7 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { themePlugin } from "./theming";
// https://vite.dev/config/
export default defineConfig({
plugins: [
themePlugin(),
svelte(),
],
plugins: [svelte()],
});