@ewanc26/pds-landing 1.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -14
- package/dist/components/ContactSection.svelte +59 -0
- package/dist/components/ContactSection.svelte.d.ts +17 -0
- package/dist/components/Divider.svelte +9 -0
- package/dist/components/Divider.svelte.d.ts +26 -0
- package/dist/components/KVGrid.svelte +87 -0
- package/dist/components/KVGrid.svelte.d.ts +13 -0
- package/dist/components/LinkList.svelte +60 -0
- package/dist/components/LinkList.svelte.d.ts +16 -0
- package/dist/components/PDSFooter.svelte +43 -0
- package/dist/components/PDSFooter.svelte.d.ts +9 -0
- package/dist/components/PDSPage.svelte +172 -0
- package/dist/components/PDSPage.svelte.d.ts +32 -0
- package/dist/components/PromptLine.svelte +48 -0
- package/dist/components/PromptLine.svelte.d.ts +11 -0
- package/dist/components/SectionLabel.svelte +25 -0
- package/dist/components/SectionLabel.svelte.d.ts +8 -0
- package/dist/components/StatusGrid.svelte +92 -0
- package/dist/components/StatusGrid.svelte.d.ts +10 -0
- package/dist/components/Tagline.svelte +24 -0
- package/dist/components/Tagline.svelte.d.ts +8 -0
- package/dist/components/TerminalCard.svelte +77 -0
- package/dist/components/TerminalCard.svelte.d.ts +10 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +17 -0
- package/dist/utils/fetchPDSStatus.d.ts +34 -0
- package/dist/utils/fetchPDSStatus.js +65 -0
- package/package.json +68 -31
- package/LICENSE +0 -661
- package/dist/assets/icon/android-icon-192x192.png +0 -0
- package/dist/assets/icon/apple-icon-114x114.png +0 -0
- package/dist/assets/icon/apple-icon-120x120.png +0 -0
- package/dist/assets/icon/apple-icon-144x144.png +0 -0
- package/dist/assets/icon/apple-icon-152x152.png +0 -0
- package/dist/assets/icon/apple-icon-180x180.png +0 -0
- package/dist/assets/icon/apple-icon-57x57.png +0 -0
- package/dist/assets/icon/apple-icon-60x60.png +0 -0
- package/dist/assets/icon/apple-icon-72x72.png +0 -0
- package/dist/assets/icon/apple-icon-76x76.png +0 -0
- package/dist/assets/icon/browserconfig.xml +0 -2
- package/dist/assets/icon/favicon-16x16.png +0 -0
- package/dist/assets/icon/favicon-256x256.png +0 -0
- package/dist/assets/icon/favicon-32x32.png +0 -0
- package/dist/assets/icon/favicon-96x96.png +0 -0
- package/dist/assets/icon/favicon.ico +0 -0
- package/dist/assets/icon/instructions.txt +0 -22
- package/dist/assets/icon/manifest.json +0 -41
- package/dist/assets/icon/ms-icon-144x144.png +0 -0
- package/dist/assets/icon/ms-icon-150x150.png +0 -0
- package/dist/assets/icon/ms-icon-310x310.png +0 -0
- package/dist/assets/icon/ms-icon-70x70.png +0 -0
- package/dist/assets/thumb.svg +0 -78
- package/dist/index.html +0 -231
- package/dist/script.js +0 -100
- package/dist/style.css +0 -1
package/README.md
CHANGED
|
@@ -1,24 +1,132 @@
|
|
|
1
|
-
# pds-landing
|
|
1
|
+
# @ewanc26/pds-landing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composable Svelte 5 components for an ATProto PDS landing page — terminal aesthetic, live status fetching, zero config to drop in.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
`/xrpc/com.atproto.server.describeServer` on load.
|
|
5
|
+
## Install
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @ewanc26/pds-landing @ewanc26/ui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start — full page
|
|
12
|
+
|
|
13
|
+
```svelte
|
|
14
|
+
<script>
|
|
15
|
+
import { PDSPage } from '@ewanc26/pds-landing';
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<!-- Drop-in: renders the complete terminal landing page -->
|
|
19
|
+
<PDSPage
|
|
20
|
+
cardTitle="ewan's pds"
|
|
21
|
+
promptUser="server"
|
|
22
|
+
promptHost="pds.ewancroft.uk"
|
|
23
|
+
tagline="Bluesky-compatible ATProto PDS · personal instance"
|
|
24
|
+
blueskyHandle="ewancroft.uk"
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Import the PDS design tokens and base styles once in your layout:
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
/* app.css / layout.css */
|
|
32
|
+
@import '@ewanc26/ui/styles/pds-tokens.css';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Mix-and-match — primitives
|
|
38
|
+
|
|
39
|
+
All primitives are exported individually so you can compose custom layouts.
|
|
40
|
+
|
|
41
|
+
```svelte
|
|
42
|
+
<script>
|
|
43
|
+
import {
|
|
44
|
+
TerminalCard,
|
|
45
|
+
PromptLine,
|
|
46
|
+
Tagline,
|
|
47
|
+
SectionLabel,
|
|
48
|
+
Divider,
|
|
49
|
+
StatusGrid,
|
|
50
|
+
LinkList,
|
|
51
|
+
ContactSection,
|
|
52
|
+
PDSFooter,
|
|
53
|
+
} from '@ewanc26/pds-landing';
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<TerminalCard title="my pds">
|
|
57
|
+
<PromptLine user="server" host="pds.example.com" />
|
|
58
|
+
<Tagline text="My custom tagline" />
|
|
59
|
+
|
|
60
|
+
<SectionLabel label="status" />
|
|
61
|
+
<StatusGrid /> <!-- fetches live from same origin -->
|
|
62
|
+
|
|
63
|
+
<Divider />
|
|
9
64
|
|
|
10
|
-
|
|
11
|
-
|
|
65
|
+
<SectionLabel label="links" />
|
|
66
|
+
<LinkList links={[
|
|
67
|
+
{ href: 'https://bsky.app', label: 'Bluesky' },
|
|
68
|
+
{ href: 'https://atproto.com', label: 'ATProto docs' },
|
|
69
|
+
]} />
|
|
70
|
+
|
|
71
|
+
<Divider />
|
|
72
|
+
|
|
73
|
+
<SectionLabel label="contact" />
|
|
74
|
+
<ContactSection blueskyHandle="you.bsky.social" />
|
|
75
|
+
</TerminalCard>
|
|
76
|
+
|
|
77
|
+
<PDSFooter />
|
|
12
78
|
```
|
|
13
79
|
|
|
14
|
-
|
|
15
|
-
|
|
80
|
+
### Use raw KV data
|
|
81
|
+
|
|
82
|
+
```svelte
|
|
83
|
+
<script>
|
|
84
|
+
import { KVGrid } from '@ewanc26/pds-landing';
|
|
85
|
+
import type { KVItem } from '@ewanc26/pds-landing';
|
|
16
86
|
|
|
17
|
-
|
|
87
|
+
const items: KVItem[] = [
|
|
88
|
+
{ key: 'status', value: '✓ online', status: 'ok' },
|
|
89
|
+
{ key: 'region', value: 'eu-west-1' },
|
|
90
|
+
{ key: 'invite', value: 'required', status: 'warn' },
|
|
91
|
+
];
|
|
92
|
+
</script>
|
|
18
93
|
|
|
19
|
-
|
|
20
|
-
|
|
94
|
+
<KVGrid {items} />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Fetch status yourself
|
|
21
98
|
|
|
22
|
-
```
|
|
23
|
-
|
|
99
|
+
```ts
|
|
100
|
+
import { fetchPDSStatus } from '@ewanc26/pds-landing';
|
|
101
|
+
|
|
102
|
+
const { health, description, accountCount } = await fetchPDSStatus('https://pds.example.com');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Components
|
|
108
|
+
|
|
109
|
+
| Component | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `PDSPage` | Full assembled landing page (convenience) |
|
|
112
|
+
| `TerminalCard` | Terminal window shell with traffic-light dots titlebar |
|
|
113
|
+
| `PromptLine` | `user@host:path $` bash prompt header |
|
|
114
|
+
| `Tagline` | Dimmed subtitle beneath the prompt |
|
|
115
|
+
| `SectionLabel` | Uppercase section heading |
|
|
116
|
+
| `Divider` | Thin green-tinted `<hr>` |
|
|
117
|
+
| `KVGrid` | Key-value grid with ok/warn/err/loading states |
|
|
118
|
+
| `StatusGrid` | Live-fetching PDS status grid (wraps `KVGrid`) |
|
|
119
|
+
| `LinkList` | `→ link` list |
|
|
120
|
+
| `ContactSection` | Bluesky mention + optional email |
|
|
121
|
+
| `PDSFooter` | Footer with nixpkgs / atproto links |
|
|
122
|
+
|
|
123
|
+
## Design tokens
|
|
124
|
+
|
|
125
|
+
All components consume CSS custom properties from `@ewanc26/ui/styles/pds-tokens.css`:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
--pds-font-mono
|
|
129
|
+
--pds-color-crust / mantle / base / surface-0 / surface-1 / overlay-0
|
|
130
|
+
--pds-color-text / subtext-0
|
|
131
|
+
--pds-color-green / red / yellow / shadow
|
|
24
132
|
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
/**
|
|
4
|
+
* Bluesky handle (without @) to link to.
|
|
5
|
+
* Example: `'ewancroft.uk'`
|
|
6
|
+
*/
|
|
7
|
+
blueskyHandle?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Base URL of the Bluesky web client used to build the profile link.
|
|
10
|
+
* Defaults to `'https://bsky.app'`.
|
|
11
|
+
*/
|
|
12
|
+
blueskyClientUrl?: string;
|
|
13
|
+
/** Contact email address. Rendered only when provided. */
|
|
14
|
+
email?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
blueskyHandle,
|
|
19
|
+
blueskyClientUrl = 'https://bsky.app',
|
|
20
|
+
email
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
let profileUrl = $derived(
|
|
24
|
+
blueskyHandle ? `${blueskyClientUrl}/profile/${blueskyHandle}` : null
|
|
25
|
+
);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#if blueskyHandle && profileUrl}
|
|
29
|
+
<p class="pds-contact-note">
|
|
30
|
+
Send a mention on Bluesky to
|
|
31
|
+
<a href={profileUrl} target="_blank" rel="noopener">@{blueskyHandle}</a>
|
|
32
|
+
</p>
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
{#if email}
|
|
36
|
+
<p class="pds-contact-note pds-contact-email-row">
|
|
37
|
+
Email: <a href="mailto:{email}">{email}</a>
|
|
38
|
+
</p>
|
|
39
|
+
{/if}
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.pds-contact-note {
|
|
43
|
+
font-size: 0.88em;
|
|
44
|
+
color: var(--pds-color-subtext-0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.pds-contact-email-row {
|
|
48
|
+
margin-top: 0.4rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.pds-contact-note a {
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
color: var(--pds-color-green);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.pds-contact-note a:hover {
|
|
57
|
+
text-decoration: underline;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/**
|
|
3
|
+
* Bluesky handle (without @) to link to.
|
|
4
|
+
* Example: `'ewancroft.uk'`
|
|
5
|
+
*/
|
|
6
|
+
blueskyHandle?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Base URL of the Bluesky web client used to build the profile link.
|
|
9
|
+
* Defaults to `'https://bsky.app'`.
|
|
10
|
+
*/
|
|
11
|
+
blueskyClientUrl?: string;
|
|
12
|
+
/** Contact email address. Rendered only when provided. */
|
|
13
|
+
email?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const ContactSection: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type ContactSection = ReturnType<typeof ContactSection>;
|
|
17
|
+
export default ContactSection;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default Divider;
|
|
2
|
+
type Divider = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const Divider: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
16
|
+
$$bindings?: Bindings;
|
|
17
|
+
} & Exports;
|
|
18
|
+
(internal: unknown, props: {
|
|
19
|
+
$$events?: Events;
|
|
20
|
+
$$slots?: Slots;
|
|
21
|
+
}): Exports & {
|
|
22
|
+
$set?: any;
|
|
23
|
+
$on?: any;
|
|
24
|
+
};
|
|
25
|
+
z_$$bindings?: Bindings;
|
|
26
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** A single row in the key-value status grid. */
|
|
3
|
+
export interface KVItem {
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
/** Visual state applied to the value cell. */
|
|
7
|
+
status?: 'ok' | 'warn' | 'err' | 'loading';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
items: KVItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { items }: Props = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="pds-kv-grid" role="list">
|
|
18
|
+
{#each items as item (item.key)}
|
|
19
|
+
<span class="pds-kv-key" role="term">{item.key}</span>
|
|
20
|
+
<span class="pds-kv-val {item.status ?? ''}" role="definition">{item.value}</span>
|
|
21
|
+
{/each}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.pds-kv-grid {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: max-content 1fr;
|
|
28
|
+
gap: 0.25rem 1.2rem;
|
|
29
|
+
font-size: 0.88em;
|
|
30
|
+
margin-bottom: 1.4rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.pds-kv-key {
|
|
34
|
+
color: var(--pds-color-green);
|
|
35
|
+
opacity: 0.6;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.pds-kv-val {
|
|
40
|
+
color: var(--pds-color-text);
|
|
41
|
+
word-break: break-all;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.pds-kv-val.ok {
|
|
46
|
+
color: var(--pds-color-green);
|
|
47
|
+
}
|
|
48
|
+
.pds-kv-val.warn {
|
|
49
|
+
color: var(--pds-color-yellow);
|
|
50
|
+
}
|
|
51
|
+
.pds-kv-val.err {
|
|
52
|
+
color: var(--pds-color-red);
|
|
53
|
+
}
|
|
54
|
+
.pds-kv-val.loading {
|
|
55
|
+
color: var(--pds-color-surface-1);
|
|
56
|
+
animation: pds-kv-pulse 1.2s ease-in-out infinite;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@keyframes pds-kv-pulse {
|
|
60
|
+
0%,
|
|
61
|
+
100% {
|
|
62
|
+
opacity: 0.4;
|
|
63
|
+
}
|
|
64
|
+
50% {
|
|
65
|
+
opacity: 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (max-width: 440px) {
|
|
70
|
+
.pds-kv-grid {
|
|
71
|
+
grid-template-columns: 1fr;
|
|
72
|
+
gap: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.pds-kv-key {
|
|
76
|
+
text-transform: uppercase;
|
|
77
|
+
opacity: 1;
|
|
78
|
+
font-size: 0.7em;
|
|
79
|
+
letter-spacing: 0.1em;
|
|
80
|
+
margin-top: 0.7rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.pds-kv-key:first-child {
|
|
84
|
+
margin-top: 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A single row in the key-value status grid. */
|
|
2
|
+
export interface KVItem {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
/** Visual state applied to the value cell. */
|
|
6
|
+
status?: 'ok' | 'warn' | 'err' | 'loading';
|
|
7
|
+
}
|
|
8
|
+
interface Props {
|
|
9
|
+
items: KVItem[];
|
|
10
|
+
}
|
|
11
|
+
declare const KVGrid: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type KVGrid = ReturnType<typeof KVGrid>;
|
|
13
|
+
export default KVGrid;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** A single entry in a link list. */
|
|
3
|
+
export interface LinkItem {
|
|
4
|
+
href: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/**
|
|
7
|
+
* Whether to open in a new tab.
|
|
8
|
+
* Defaults to `true` — set to `false` for internal navigation.
|
|
9
|
+
*/
|
|
10
|
+
external?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
links: LinkItem[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { links }: Props = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<ul class="pds-link-list">
|
|
21
|
+
{#each links as link (link.href)}
|
|
22
|
+
<li>
|
|
23
|
+
<a
|
|
24
|
+
href={link.href}
|
|
25
|
+
target={link.external !== false ? '_blank' : undefined}
|
|
26
|
+
rel={link.external !== false ? 'noopener' : undefined}
|
|
27
|
+
>
|
|
28
|
+
{link.label}
|
|
29
|
+
</a>
|
|
30
|
+
</li>
|
|
31
|
+
{/each}
|
|
32
|
+
</ul>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
.pds-link-list {
|
|
36
|
+
list-style: none;
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 0.35rem;
|
|
40
|
+
font-size: 0.88em;
|
|
41
|
+
padding: 0;
|
|
42
|
+
margin: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.pds-link-list li::before {
|
|
46
|
+
content: '→ ';
|
|
47
|
+
color: var(--pds-color-green);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.pds-link-list a {
|
|
51
|
+
text-decoration: none;
|
|
52
|
+
color: var(--pds-color-green);
|
|
53
|
+
opacity: 0.85;
|
|
54
|
+
transition: opacity 0.15s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.pds-link-list a:hover {
|
|
58
|
+
opacity: 1;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** A single entry in a link list. */
|
|
2
|
+
export interface LinkItem {
|
|
3
|
+
href: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/**
|
|
6
|
+
* Whether to open in a new tab.
|
|
7
|
+
* Defaults to `true` — set to `false` for internal navigation.
|
|
8
|
+
*/
|
|
9
|
+
external?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
links: LinkItem[];
|
|
13
|
+
}
|
|
14
|
+
declare const LinkList: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LinkList = ReturnType<typeof LinkList>;
|
|
16
|
+
export default LinkList;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Show the `nixpkgs#bluesky-pds` credit. Defaults to `true`. */
|
|
4
|
+
showNixpkg?: boolean;
|
|
5
|
+
/** Show the `atproto.com` link. Defaults to `true`. */
|
|
6
|
+
showAtproto?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { showNixpkg = true, showAtproto = true }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<footer class="pds-footer">
|
|
13
|
+
{#if showNixpkg}
|
|
14
|
+
powered by
|
|
15
|
+
<a href="https://search.nixos.org/packages?show=bluesky-pds" target="_blank" rel="noopener"
|
|
16
|
+
>nixpkgs#bluesky-pds</a
|
|
17
|
+
>
|
|
18
|
+
{/if}
|
|
19
|
+
{#if showNixpkg && showAtproto}
|
|
20
|
+
·
|
|
21
|
+
{/if}
|
|
22
|
+
{#if showAtproto}
|
|
23
|
+
<a href="https://atproto.com" target="_blank" rel="noopener">atproto.com</a>
|
|
24
|
+
{/if}
|
|
25
|
+
</footer>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
.pds-footer {
|
|
29
|
+
text-align: center;
|
|
30
|
+
color: color-mix(in srgb, var(--pds-color-green) 35%, transparent);
|
|
31
|
+
font-size: 0.75em;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.pds-footer a {
|
|
35
|
+
text-decoration: underline;
|
|
36
|
+
color: color-mix(in srgb, var(--pds-color-green) 35%, transparent);
|
|
37
|
+
transition: color 0.15s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.pds-footer a:hover {
|
|
41
|
+
color: var(--pds-color-green);
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Show the `nixpkgs#bluesky-pds` credit. Defaults to `true`. */
|
|
3
|
+
showNixpkg?: boolean;
|
|
4
|
+
/** Show the `atproto.com` link. Defaults to `true`. */
|
|
5
|
+
showAtproto?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare const PDSFooter: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type PDSFooter = ReturnType<typeof PDSFooter>;
|
|
9
|
+
export default PDSFooter;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PDSPage — a fully assembled ATProto PDS landing page.
|
|
4
|
+
*
|
|
5
|
+
* All sections (status, endpoints, links, contact) are included by default.
|
|
6
|
+
* Each section can be individually disabled via boolean props, and the
|
|
7
|
+
* individual primitives (TerminalCard, StatusGrid, LinkList, …) can be
|
|
8
|
+
* composed manually for bespoke layouts.
|
|
9
|
+
*/
|
|
10
|
+
import { onMount } from 'svelte';
|
|
11
|
+
import TerminalCard from './TerminalCard.svelte';
|
|
12
|
+
import PromptLine from './PromptLine.svelte';
|
|
13
|
+
import Tagline from './Tagline.svelte';
|
|
14
|
+
import SectionLabel from './SectionLabel.svelte';
|
|
15
|
+
import Divider from './Divider.svelte';
|
|
16
|
+
import StatusGrid from './StatusGrid.svelte';
|
|
17
|
+
import LinkList from './LinkList.svelte';
|
|
18
|
+
import ContactSection from './ContactSection.svelte';
|
|
19
|
+
import PDSFooter from './PDSFooter.svelte';
|
|
20
|
+
import type { LinkItem } from './LinkList.svelte';
|
|
21
|
+
import { fetchPDSStatus } from '../utils/fetchPDSStatus.js';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
// ── Card chrome ──────────────────────────────────────────────────────
|
|
25
|
+
/** Text shown in the terminal titlebar. */
|
|
26
|
+
cardTitle?: string;
|
|
27
|
+
|
|
28
|
+
// ── Prompt ───────────────────────────────────────────────────────────
|
|
29
|
+
promptUser?: string;
|
|
30
|
+
promptHost?: string;
|
|
31
|
+
promptPath?: string;
|
|
32
|
+
tagline?: string;
|
|
33
|
+
|
|
34
|
+
// ── API ──────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Origin prepended to `/xrpc/…` calls.
|
|
37
|
+
* Leave empty (`''`) to use the current page's origin (default).
|
|
38
|
+
*/
|
|
39
|
+
baseUrl?: string;
|
|
40
|
+
|
|
41
|
+
// ── Sections ─────────────────────────────────────────────────────────
|
|
42
|
+
showStatus?: boolean;
|
|
43
|
+
showEndpoints?: boolean;
|
|
44
|
+
showLinks?: boolean;
|
|
45
|
+
showContact?: boolean;
|
|
46
|
+
showFooter?: boolean;
|
|
47
|
+
|
|
48
|
+
// ── Links section ────────────────────────────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Static links always shown in the Links section.
|
|
51
|
+
* Dynamic links (privacy policy / ToS) from `describeServer` are
|
|
52
|
+
* appended automatically.
|
|
53
|
+
*/
|
|
54
|
+
staticLinks?: LinkItem[];
|
|
55
|
+
|
|
56
|
+
// ── Contact section ──────────────────────────────────────────────────
|
|
57
|
+
blueskyHandle?: string;
|
|
58
|
+
blueskyClientUrl?: string;
|
|
59
|
+
|
|
60
|
+
// ── Footer ───────────────────────────────────────────────────────────
|
|
61
|
+
showNixpkg?: boolean;
|
|
62
|
+
showAtproto?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let {
|
|
66
|
+
cardTitle = "ewan's pds",
|
|
67
|
+
promptUser = 'server',
|
|
68
|
+
promptHost = 'pds.ewancroft.uk',
|
|
69
|
+
promptPath = '~',
|
|
70
|
+
tagline = 'Bluesky-compatible ATProto PDS · personal instance',
|
|
71
|
+
baseUrl = '',
|
|
72
|
+
showStatus = true,
|
|
73
|
+
showEndpoints = true,
|
|
74
|
+
showLinks = true,
|
|
75
|
+
showContact = true,
|
|
76
|
+
showFooter = true,
|
|
77
|
+
staticLinks = [{ href: 'https://witchsky.app', label: 'Witchsky Web Client' }],
|
|
78
|
+
blueskyHandle = 'ewancroft.uk',
|
|
79
|
+
blueskyClientUrl = 'https://witchsky.app',
|
|
80
|
+
showNixpkg = true,
|
|
81
|
+
showAtproto = true
|
|
82
|
+
}: Props = $props();
|
|
83
|
+
|
|
84
|
+
const ENDPOINT_LINKS: LinkItem[] = [
|
|
85
|
+
{ href: 'https://github.com/bluesky-social/atproto', label: 'atproto source code' },
|
|
86
|
+
{ href: 'https://github.com/bluesky-social/pds', label: 'self-hosting guide' },
|
|
87
|
+
{ href: 'https://atproto.com', label: 'protocol docs' }
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Dynamic state populated from describeServer
|
|
91
|
+
let extraLinks: LinkItem[] = $state([]);
|
|
92
|
+
let dynamicLinks: LinkItem[] = $derived([...staticLinks, ...extraLinks]);
|
|
93
|
+
let contactEmail: string | null = $state(null);
|
|
94
|
+
|
|
95
|
+
onMount(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const { description } = await fetchPDSStatus(baseUrl);
|
|
98
|
+
const extras: LinkItem[] = [];
|
|
99
|
+
if (description.links?.privacyPolicy) {
|
|
100
|
+
extras.push({ href: description.links.privacyPolicy, label: 'Privacy Policy' });
|
|
101
|
+
}
|
|
102
|
+
if (description.links?.termsOfService) {
|
|
103
|
+
extras.push({ href: description.links.termsOfService, label: 'Terms of Service' });
|
|
104
|
+
}
|
|
105
|
+
if (extras.length) extraLinks = extras;
|
|
106
|
+
if (description.contact?.email) contactEmail = description.contact.email;
|
|
107
|
+
} catch {
|
|
108
|
+
// silently fall back to static defaults
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<div class="pds-page">
|
|
114
|
+
<TerminalCard title={cardTitle}>
|
|
115
|
+
<PromptLine user={promptUser} host={promptHost} path={promptPath} />
|
|
116
|
+
<Tagline text={tagline} />
|
|
117
|
+
|
|
118
|
+
{#if showStatus}
|
|
119
|
+
<SectionLabel label="status" />
|
|
120
|
+
<StatusGrid {baseUrl} />
|
|
121
|
+
{/if}
|
|
122
|
+
|
|
123
|
+
{#if showEndpoints}
|
|
124
|
+
<Divider />
|
|
125
|
+
<SectionLabel label="endpoints" />
|
|
126
|
+
<p class="pds-endpoints-note">
|
|
127
|
+
Most API routes are under <span class="pds-highlight">/xrpc/</span>
|
|
128
|
+
</p>
|
|
129
|
+
<LinkList links={ENDPOINT_LINKS} />
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
{#if showLinks}
|
|
133
|
+
<Divider />
|
|
134
|
+
<SectionLabel label="links" />
|
|
135
|
+
<LinkList links={dynamicLinks} />
|
|
136
|
+
{/if}
|
|
137
|
+
|
|
138
|
+
{#if showContact}
|
|
139
|
+
<Divider />
|
|
140
|
+
<SectionLabel label="contact" />
|
|
141
|
+
<ContactSection
|
|
142
|
+
{blueskyHandle}
|
|
143
|
+
{blueskyClientUrl}
|
|
144
|
+
email={contactEmail ?? undefined}
|
|
145
|
+
/>
|
|
146
|
+
{/if}
|
|
147
|
+
</TerminalCard>
|
|
148
|
+
|
|
149
|
+
{#if showFooter}
|
|
150
|
+
<PDSFooter {showNixpkg} {showAtproto} />
|
|
151
|
+
{/if}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<style>
|
|
155
|
+
.pds-page {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 2rem;
|
|
160
|
+
width: 100%;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.pds-endpoints-note {
|
|
164
|
+
font-size: 0.88em;
|
|
165
|
+
color: var(--pds-color-subtext-0);
|
|
166
|
+
margin-bottom: 0.7rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.pds-highlight {
|
|
170
|
+
color: var(--pds-color-green);
|
|
171
|
+
}
|
|
172
|
+
</style>
|