+7
bun.lock
+7
bun.lock
···
7
7
"@astrojs/db": "^0.17.1",
8
8
"@astrojs/node": "^9.4.3",
9
9
"@atproto/api": "^0.16.7",
10
+
"@floating-ui/dom": "^1.7.4",
10
11
"@fujocoded/authproto": "^0.0.4",
11
12
"@lucide/astro": "^0.542.0",
12
13
"@tailwindcss/vite": "^4.1.13",
···
147
148
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
148
149
149
150
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
151
+
152
+
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
153
+
154
+
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
155
+
156
+
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
150
157
151
158
"@fujocoded/authproto": ["@fujocoded/authproto@0.0.4", "", { "dependencies": { "@astrojs/db": "^0.17.1", "@atproto/identity": "^0.4.8", "@atproto/oauth-client-node": "^0.3.3", "astro-integration-kit": "^0.19.0", "unstorage": "^1.16.1" }, "peerDependencies": { "astro": "^5.13.0" } }, "sha512-VoKfScLMaGLAOB6WKFsU7lEsCNvy98KW7MaPfqLKIbjg0MqWrbeyvCak4mGDjQUe96jm8DBMkd6b2W+T369PNA=="],
152
159
+1
package.json
+1
package.json
+22
-2
src/assets/styles/global.css
+22
-2
src/assets/styles/global.css
···
1
1
@import "tailwindcss";
2
-
@plugin "daisyui";
2
+
@plugin "daisyui" {
3
+
themes: all;
4
+
/* add new themes here */
5
+
}
3
6
@plugin "@tailwindcss/typography";
4
7
8
+
/* default theme */
5
9
@theme {
6
10
/* font tokens */
7
11
--font-sans: var(--atkinson);
···
26
30
--text-7xl: clamp(7.4506rem, 71.4115rem + -82.5302cqi, 52.8422rem);
27
31
--text-8xl: clamp(9.3132rem, 116.6654rem + -138.5189cqi, 85.4986rem);
28
32
--text-9xl: clamp(11.6415rem, 190.1667rem + -230.355cqi, 138.3368rem);
29
-
}
33
+
}
34
+
35
+
@custom-variant dark (&:where(
36
+
[data-theme=dark],
37
+
[data-theme=dracula],
38
+
[data-theme=synthwave],
39
+
[data-theme=halloween],
40
+
[data-theme=forest],
41
+
[data-theme=aqua],
42
+
[data-theme=black],
43
+
[data-theme=luxury],
44
+
[data-theme=business],
45
+
[data-theme=night],
46
+
[data-theme=coffee],
47
+
[data-theme=sunset],
48
+
[data-theme=abyss]
49
+
));
+13
-11
src/components/Dialog.astro
+13
-11
src/components/Dialog.astro
···
13
13
<dialog
14
14
{id}
15
15
class:list={[
16
-
"m-auto",
17
-
"rounded-box",
18
-
"shadow"
19
-
, className,
16
+
"modal modal-bottom sm:modal-middle",
17
+
className,
20
18
]}
21
19
role={alert ? "alertdialog" : undefined}
22
20
closedby="any"
23
21
>
24
-
<div class="card">
25
-
<header class="flex items-center justify-between">
26
-
<h1 class="card-title">{title}</h1>
22
+
<div class="modal-box">
23
+
<header class="modal-header">
24
+
<h1 class="text-lg leading-none flex-1 pl-2">{title}</h1>
27
25
<form method="dialog">
28
-
<button aria-label="close" class="close">
26
+
<button aria-label="close" class="btn btn-error">
29
27
<X />
30
28
</button>
31
29
</form>
32
30
</header>
33
-
34
-
<div class="card-body">
31
+
32
+
<div class="mt-14">
35
33
<slot />
36
34
</div>
37
35
</div>
38
36
</dialog>
39
37
40
38
<style>
41
-
39
+
@reference "../assets/styles/global.css";
40
+
41
+
.modal-header {
42
+
@apply absolute top-0 left-0 flex items-center justify-between p-2 container bg-accent text-accent-content;
43
+
}
42
44
</style>
+62
-24
src/components/Popover.astro
+62
-24
src/components/Popover.astro
···
4
4
interface Props {
5
5
id?: string;
6
6
label: string;
7
-
direction: "top" | "bottom";
8
7
icon?: "info" | "warning" | "danger";
9
8
title?: string;
10
9
class?: string;
11
10
}
12
11
13
-
const { id, label, direction = "top", icon, title, class: className, ...rest } = Astro.props;
12
+
const { id, label, icon, title, class: className, ...rest } = Astro.props;
14
13
---
15
14
<!-- type button needs to be set here, otherwise it doesn't work inside forms -->
16
15
<button
17
16
type="button"
18
17
id={`${id}-trigger`}
19
18
class:list={[
20
-
"btn",
21
-
"btn-xs",
19
+
"btn btn-xs",
22
20
icon && ["btn-circle", "btn-ghost"],
23
21
icon &&
24
22
(icon === "info") ? "text-info" :
···
47
45
<div
48
46
{id}
49
47
class:list={[
50
-
"dropdown",
51
-
"card",
52
-
"bg-base-100",
53
-
"w-72",
54
-
"shadow",
55
48
"popover-content",
56
49
className,
57
50
]}
58
-
role="tooltip" {...rest}
59
-
popover="auto"
51
+
role="tooltip"
52
+
popover="auto"
53
+
{...rest}
60
54
>
61
55
<div class="card-body">
62
56
{title && (
···
67
61
</div>
68
62
</div>
69
63
70
-
<style define:vars={{ anchor: `--${id}-anchor`, direction }}>
64
+
<style define:vars={{ anchor: `--${id}-anchor` }}>
65
+
@reference "../assets/styles/global.css";
66
+
71
67
.popover-btn {
72
-
anchor-name: var(--anchor);
68
+
@supports (anchor-name: var(--anchor)) {
69
+
anchor-name: var(--anchor);
70
+
}
73
71
}
74
72
75
73
.popover-content {
76
-
position-anchor: var(--anchor);
77
-
top: anchor(var(--direction));
78
-
left: anchor(center);
79
-
transform: translateX(-50%);
80
-
position-try-fallbacks: flip-block, flip-inline;
74
+
@apply dropdown card mx-0 inset-auto bg-base-100 w-72 shadow;
75
+
76
+
@supports (position-anchor: var(--anchor)) and (left: anchor(center)) {
77
+
position-anchor: var(--anchor);
78
+
left: anchor(center);
79
+
transform: translateX(-50%);
80
+
}
81
81
}
82
82
</style>
83
83
84
-
<script define:vars={{ id }} is:inline>
85
-
const trigger = document.getElementById(`${id}-trigger`);
86
-
const popover = document.getElementById(id);
84
+
<script>
85
+
import { computePosition, autoUpdate, shift, flip } from "@floating-ui/dom";
86
+
const triggers = document.querySelectorAll(".popover-btn");
87
+
88
+
triggers.forEach(trigger => {
89
+
const btn = trigger as HTMLButtonElement;
90
+
// triggering button will always end with "-trigger"
91
+
// so slice that from the id
92
+
const id = btn.id.slice(0, -8);
93
+
const popover = document.getElementById(`${id}`) as HTMLElement;
94
+
95
+
btn.addEventListener("click", (e) => {
96
+
e.preventDefault();
97
+
popover.togglePopover();
98
+
});
87
99
88
-
trigger.addEventListener("click", (e) => {
89
-
e.preventDefault();
90
-
popover.togglePopover();
100
+
popover.addEventListener("toggle", (e) => {
101
+
const cleanup = autoUpdate(
102
+
btn,
103
+
popover,
104
+
() => {
105
+
computePosition(btn, popover, {
106
+
middleware: [
107
+
flip(),
108
+
shift({
109
+
crossAxis: false,
110
+
}),
111
+
],
112
+
}).then(({ placement, middlewareData }) => {
113
+
Object.assign(popover.style, {
114
+
top: `anchor(${placement})`,
115
+
...(placement === "top") && {
116
+
transform: (middlewareData.shift?.enabled.x)
117
+
? `translate(calc(-50% + ${middlewareData.shift.x}px), -100%)`
118
+
: `translate(-50%, -100%)`,
119
+
},
120
+
});
121
+
});
122
+
});
123
+
if (e.newState === "open") {
124
+
cleanup;
125
+
} else {
126
+
cleanup();
127
+
}
128
+
});
91
129
});
92
130
</script>
+44
-21
src/components/Settings.astro
+44
-21
src/components/Settings.astro
···
3
3
---
4
4
<Dialog id="settings" title="User preferences">
5
5
<form id="user-settings">
6
-
<label for="font-family">font family</label>
7
-
<select name="fontFamily" id="font-family">
8
-
<option value="default">choose...</option>
9
-
<option value="--serif">serif</option>
10
-
<option value="--mono">monospaced</option>
11
-
<option value="--sans">sans serif</option>
12
-
<option value="--dyslexic">dyslexic</option>
13
-
</select>
6
+
<fieldset class="fieldset">
7
+
<label for="font-family">font family</label>
8
+
<select class="select" name="fontFamily" id="font-family">
9
+
<option value="default">choose...</option>
10
+
<option value="--font-serif">serif</option>
11
+
<option value="--font-mono">monospaced</option>
12
+
<option value="--font-sans">sans serif</option>
13
+
<option value="--font-dyslexic">dyslexic</option>
14
+
</select>
15
+
</fieldset>
14
16
15
-
<label for="font-size">text size</label>
16
-
<input type="range" name="fontSize" id="font-size" min="-1" max="2" step="1" />
17
+
<fieldset class="fieldset">
18
+
<label for="font-size">text size</label>
19
+
<input class="range" type="range" name="fontSize" id="font-size" min="-1" max="2" step="1" />
20
+
</fieldset>
17
21
18
-
<label for="line-height">line height</label>
19
-
<input type="range" name="lineHeight" id="line-height" min="1" max="2" step="0.05" />
22
+
<fieldset class="fieldset">
23
+
<label for="line-height">line height</label>
24
+
<input class="range" type="range" name="lineHeight" id="line-height" min="1" max="2" step="0.05" />
25
+
</fieldset>
26
+
27
+
<fieldset class="fieldset">
28
+
<label for="letter-spacing">letter spacing</label>
29
+
<input class="range" type="range" name="letterSpacing" id="letter-spacing" min="0" max="0.1" step="0.01" />
30
+
</fieldset>
20
31
21
-
<label for="letter-spacing">letter spacing</label>
22
-
<input type="range" name="letterSpacing" id="letter-spacing" min="0" max="0.1" step="0.01" />
23
-
24
-
<label for="word-spacing">word spacing</label>
25
-
<input type="range" name="wordSpacing" id="word-spacing" min="0" max="0.5" step="0.01" />
32
+
<fieldset class="fieldset">
33
+
<label for="word-spacing">word spacing</label>
34
+
<input class="range" type="range" name="wordSpacing" id="word-spacing" min="0" max="0.5" step="0.01" />
35
+
</fieldset>
26
36
27
-
<div id="test-area">
37
+
<div id="test-area" class="mt-4 text-(length:--size) leading-(--line-height) tracking-(--letter-spacing) line-clamp-4">
28
38
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Asperiores quae dolorum debitis vero nostrum nobis aspernatur ipsam sunt dolorem, eum ut corrupti unde commodi soluta natus repellendus totam animi adipisci.</p>
29
39
</div>
30
40
31
-
<button id="confirm-settings">save</button>
41
+
<div class="modal-action">
42
+
<button formmethod="dialog" value="default" class="btn btn-neutral">Cancel</button>
43
+
<button id="confirm-settings" class="btn btn-primary">Save</button>
44
+
</div>
32
45
</form>
33
46
</Dialog>
34
47
35
48
<style>
36
49
#test-area {
37
-
--font: var(--body);
50
+
--step--1: clamp(0.6953rem, 0.5707rem + 0.554vw, 1rem);
51
+
--step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
52
+
--step-1: clamp(1.5625rem, 1.9257rem + -0.4686vw, 1.8203rem);
53
+
--step-2: clamp(1.9531rem, 3.351rem + -1.8037vw, 2.9452rem);
54
+
55
+
--font: var(--font-sans);
38
56
--size: var(--step-0);
39
57
--letter-spacing: 0em;
40
58
--word-spacing: 0em;
···
55
73
const test = document.getElementById("test-area");
56
74
57
75
form?.addEventListener("submit", (e) => {
58
-
e.preventDefault();
76
+
const target = e.target as HTMLFormElement;
77
+
if (target.nodeValue === null) {
78
+
return;
79
+
} else {
80
+
e.preventDefault();
81
+
}
59
82
});
60
83
61
84
inputs.forEach((input) => {
+30
-17
src/layouts/WorkPage.astro
+30
-17
src/layouts/WorkPage.astro
···
2
2
import Layout from "./Layout.astro";
3
3
4
4
interface Props {
5
+
slug: string;
5
6
title: string;
6
-
has_previous: boolean;
7
-
has_next: boolean;
7
+
author: string;
8
+
// tags: Tag[];
9
+
tags: any;
10
+
createdAt: Date;
11
+
updatedAt?: Date | null;
12
+
comments?: boolean;
13
+
previous?: boolean;
14
+
next?: boolean;
8
15
}
9
16
10
-
const { title, has_previous, has_next } = Astro.props;
17
+
const { slug, title, author, tags, createdAt, updatedAt, comments, previous, next } = Astro.props;
11
18
---
12
-
<Layout title={title}>
13
-
<a href="#workname-content">to content</a>
14
-
19
+
<Layout title={title} skipLink="work-body">
15
20
<nav id="work-menu">
16
-
{has_previous && (
21
+
{previous && (
17
22
// chapterid - 1?
18
23
<a href="">previous chaptertitle</a>
19
24
)}
20
25
<!-- if theres more than one chapter, render this box -->
21
-
<select name="workname-chapters" id="workname-chapters">
26
+
<select name="chapterSelect" id={`${slug}-chapters`}>
22
27
<option value="default" selected>Choose chapter...</option>
23
28
<!-- map each chapter here -->
24
29
</select>
25
30
26
-
{has_next && (
31
+
{next && (
27
32
// chapterid + 1 ?
28
33
<a href="">next chaptertitle</a>
29
34
)}
30
35
</nav>
31
36
32
-
<main>
37
+
<main id="work-body">
33
38
<header>
34
39
<h1>{title}</h1>
35
-
<h2>author name</h2>
40
+
<h2>{author}</h2>
41
+
<!-- replace this at some point -->
42
+
{JSON.stringify(tags)}
43
+
<time datetime={createdAt.toISOString()}>{createdAt}</time>
44
+
{updatedAt && (
45
+
<time datetime={updatedAt.toISOString()}>{updatedAt}</time>
46
+
)}
36
47
37
48
<div id="summary">
38
49
summary
39
50
</div>
40
51
</header>
41
52
42
-
<section id="workname-content">
53
+
<section id={`${slug}-content`} class="prose lg:prose-xl">
43
54
<!-- if work has its own style, render it here somehow -->
44
55
<details>
45
56
<summary>Author's notes</summary>
46
57
this should include author's notes
47
58
</details>
48
-
59
+
49
60
<slot />
50
61
</section>
51
62
52
-
<aside id="workname-comments">
53
-
<!-- use bsky api to render comments here -->
54
-
<!-- paginate this -->
55
-
</aside>
63
+
{comments && (
64
+
<aside id={`${slug}-comments`}>
65
+
<!-- use bsky api to render comments here -->
66
+
<!-- paginate this -->
67
+
</aside>
68
+
)}
56
69
</main>
57
70
</Layout>
+1
-1
src/pages/login.astro
+1
-1
src/pages/login.astro
···
19
19
<fieldset class="fieldset mx-auto place-content-center max-w-md">
20
20
<label class="fieldset-label" for="handle">
21
21
Input your handle
22
-
<Popover id="handle-help" icon="info" label="help" direction="bottom">
22
+
<Popover id="handle-help" icon="info" label="help">
23
23
<h3>What's my handle?</h3>
24
24
<p>It'll look like a website URL without the <samp>https://</samp> or slashes, so a typical BlueSky handle will look something like: <b>alice.bsky.social</b>.</p>
25
25
<p>What yours will look like depends on whether you made a custom handle!</p>
+84
-61
src/pages/user/index.astro
+84
-61
src/pages/user/index.astro
···
1
1
---
2
2
import Layout from "@/layouts/Layout.astro";
3
+
import { Info } from "@lucide/astro";
3
4
import { actions } from "astro:actions";
4
5
import { db, eq, Users, Works } from "astro:db";
5
6
import Dialog from "~/Dialog.astro";
6
7
import Popover from "~/Popover.astro";
8
+
import Settings from "~/Settings.astro";
7
9
8
10
const loggedInUser = Astro.locals.loggedInUser;
9
11
···
21
23
.from(Works)
22
24
.where(eq(Works.author, user?.userDid ?? loggedInUser.did));
23
25
---
24
-
<Layout>
25
-
<h1>User Settings</h1>
26
-
<p>{loggedInUser?.handle}</p>
27
-
28
-
<!-- registration will only happen in the below form! -->
29
-
{(query.length === 0) && (
30
-
<>
31
-
<h2>Connect account</h2>
32
-
<div class="info">
33
-
<p>Right now, you aren't connected to the site. You can connect your BlueSky / self-hosted PDS account to this website to post a work.</p>
34
-
<p>Please check out the Terms of Service, Privacy Policy, and Code of Conduct before connecting your account.</p>
35
-
</div>
36
-
<button id="trigger-confirm">Connect your PDS Account</button>
37
-
38
-
<Dialog id="connect-account" title="Are you sure?">
39
-
<form action={actions.usersActions.addUser} method="post">
40
-
<label for="nickname">Nickname</label>
41
-
<Popover id="nickname-info" label="info" icon="warning" direction="bottom">
42
-
<p>You can optionally set your nickname for this site. This is separate from your handle and acts as your identifier.</p>
43
-
<p>Think of your handle as what you use to log in with, and your nickname as the name you want to publish your works under.</p>
44
-
<h3>Important</h3>
45
-
<p>If you do set a nickname, please make sure it's unique! Having two people with the same nickname would cause confusion, unfortunately.</p>
46
-
</Popover>
47
-
<input type="text" name="nickname" id="nickname" />
26
+
<Layout skipLink="user-profile">
27
+
<main id="user-profile">
28
+
<h1 class="text-xl">User Settings</h1>
29
+
<p>{loggedInUser?.handle}</p>
30
+
31
+
<!-- registration will only happen in the below form! -->
32
+
{(query.length === 0) && (
33
+
<>
34
+
<h2 class="text-lg">Connect account</h2>
35
+
<div class="info">
36
+
<p>Right now, you aren't connected to the site. You can connect your BlueSky / self-hosted PDS account to this website to post a work.</p>
37
+
<p>Please check out the Terms of Service, Privacy Policy, and Code of Conduct before connecting your account.</p>
38
+
</div>
39
+
<button id="trigger-confirm" class="btn btn-accent">Connect your PDS Account</button>
40
+
41
+
<Dialog id="connect-account" title="Are you sure?">
42
+
<form action={actions.usersActions.addUser} method="post">
43
+
<fieldset class="fieldset">
44
+
<div class="flex gap-1">
45
+
<label for="nickname" class="label">Nickname</label>
46
+
<Popover id="nickname-note" label="info" icon="warning" title="Important">
47
+
<p>If you do set a nickname, please make sure it's unique! Having two people with the same nickname would cause confusion, unfortunately.</p>
48
+
</Popover>
49
+
</div>
50
+
<input class="input w-full" type="text" name="nickname" id="nickname" aria-describedby="nickname-info" />
51
+
<div id="nickname-info" class="alert">
52
+
<Info class="text-info" />
53
+
<div>
54
+
<p>You can optionally set your nickname for this site.</p>
55
+
<p>This is separate from your handle and acts similarly to a penname.</p>
56
+
</div>
57
+
</div>
58
+
</fieldset>
48
59
49
-
<button formmethod="dialog">Cancel</button>
50
-
<button>Confirm</button>
51
-
</form>
52
-
</Dialog>
53
-
</>
54
-
)}
55
-
56
-
{user && (
57
-
<>
58
-
<h2>your nickname???</h2>
59
-
<time datetime={user.joinedAt.toISOString()}>{user.joinedAt}</time>
60
-
<p>{user.userDid}</p>
60
+
<div class="modal-action">
61
+
<button class="btn btn-neutral" formmethod="dialog">Cancel</button>
62
+
<button class="btn btn-primary">Confirm</button>
63
+
</div>
64
+
</form>
65
+
</Dialog>
66
+
</>
67
+
)}
68
+
69
+
{user && (
70
+
<>
71
+
<h2>your nickname???</h2>
72
+
<time datetime={user.joinedAt.toISOString()}>{user.joinedAt}</time>
73
+
<p>{user.userDid}</p>
61
74
62
-
{works && (
63
-
<section>
64
-
<ul>
65
-
{works.map(work => (
66
-
<article>
67
-
<h3>{work.title}</h3>
75
+
{works && (
76
+
<section>
77
+
<ul>
78
+
{works.map(work => (
79
+
<article>
80
+
<h3>{work.title}</h3>
68
81
69
-
<time datetime={work.createdAt.toISOString()}>
70
-
{work.createdAt}
71
-
</time>
72
-
{work.updatedAt && (
73
-
<time datetime={work.updatedAt.toISOString()}>
74
-
{work.updatedAt}
82
+
<time datetime={work.createdAt.toISOString()}>
83
+
{work.createdAt}
75
84
</time>
76
-
)}
85
+
{work.updatedAt && (
86
+
<time datetime={work.updatedAt.toISOString()}>
87
+
{work.updatedAt}
88
+
</time>
89
+
)}
77
90
78
-
<ul>
79
-
{JSON.stringify(work.tags)}
80
-
</ul>
81
-
82
-
summary here
83
-
</article>
84
-
))}
85
-
</ul>
86
-
</section>
87
-
)}
88
-
</>
89
-
)}
91
+
<ul>
92
+
{JSON.stringify(work.tags)}
93
+
</ul>
94
+
95
+
summary here
96
+
</article>
97
+
))}
98
+
</ul>
99
+
</section>
100
+
)}
101
+
</>
102
+
)}
103
+
104
+
<button class="btn btn-primary" id="trigger-settings">Set preferences</button>
105
+
<Settings />
106
+
</main>
90
107
</Layout>
91
108
92
109
<script>
93
110
const trigger = document.getElementById("trigger-confirm");
111
+
const trigger2 = document.getElementById("trigger-settings");
94
112
const confirmDialog = document.getElementById("connect-account") as HTMLDialogElement;
113
+
const settingsDialog = document.getElementById("settings") as HTMLDialogElement;
95
114
96
115
trigger?.addEventListener("click", (_) => {
97
116
confirmDialog.showModal();
117
+
});
118
+
119
+
trigger2?.addEventListener("click", (_) => {
120
+
settingsDialog.showModal();
98
121
});
99
122
</script>
+13
-16
src/pages/works/[workId].astro
+13
-16
src/pages/works/[workId].astro
···
1
1
---
2
-
import Layout from "@/layouts/Layout.astro";
2
+
import WorkPage from "@/layouts/WorkPage.astro";
3
3
import { didToHandle } from "@/lib/atproto";
4
-
import type { Tag } from "@/lib/types";
5
4
import { db, eq, Users, Works } from "astro:db";
6
5
7
6
const { workId } = Astro.params;
···
16
15
return Astro.redirect("/not-found");
17
16
}
18
17
---
19
-
<Layout>
20
-
{work.map(async ({ Works, Users }) => (
21
-
<>
22
-
<h1>{Works.title}</h1>
23
-
<h2>{await didToHandle(Users.userDid)}</h2>
24
-
<time datetime={Works.createdAt.toISOString()}>{Works.createdAt}</time>
25
-
{(Works.tags as Tag[]).map(tag => (
26
-
<a href={tag.url}>{tag.label}</a>
27
-
))}
28
-
29
-
<Fragment set:html={Works.content} />
30
-
</>
31
-
))}
32
-
</Layout>
18
+
{work.map(async ({ Works, Users }) => (
19
+
<WorkPage
20
+
slug={Works.slug}
21
+
title={Works.title}
22
+
author={await didToHandle(Users.userDid)}
23
+
createdAt={Works.createdAt}
24
+
updatedAt={Works.updatedAt}
25
+
tags={Works.tags}
26
+
>
27
+
<Fragment set:html={Works.content} />
28
+
</WorkPage>
29
+
))}