chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQueryClient } from '@tanstack/react-query';
import { PencilIcon } from 'lucide-react';
import { useState } from 'react';
import { Button } from '#/components/atoms/button.jsx';
import { MarkdownField } from '#/components/molecules/markdown-field.jsx';
import { StatementCard } from '#/components/molecules/statement-card.jsx';
import { publishPull } from '#/lib/publish.js';
import { useSession } from '#/lib/session.jsx';
/**
* The author's description of their own pull request: read as the
* conversation's opening word, and edited in its place with the reading
* beside the writing.
*
* The description is markdown wherever it is read, so it is written the way a
* review and a comment are: in a field that previews the same renderer. The
* record holds the title and status too, so a description edit carries the
* title and status the author last set.
*
* Editing is the author's alone. A record in anybody else's repo would be a
* statement about somebody else's work, which is what a review is.
*/
export function PullNoteEdit({ repo, pull }) {
const { session } = useSession();
const queryClient = useQueryClient();
const mine = session?.did === pull.author.did;
const [editing, setEditing] = useState(false);
const [note, setNote] = useState(pull.note ?? '');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const save = async (event) => {
event.preventDefault();
setBusy(true);
setError('');
try {
await publishPull(session, {
repo,
ref: `refs/heads/${pull.branch}`,
sha: pull.sha,
title: pull.title ?? '',
status: pull.status ?? '',
note: note.trim(),
});
setEditing(false);
// The list holds every author's words; the next look at it re-reads.
queryClient.invalidateQueries({ queryKey: ['pulls', repo] });
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setBusy(false);
}
};
if (editing) {
return (
<form onSubmit={save} className="flex flex-col gap-2.5">
<div className="overflow-hidden rounded-lg border border-input bg-background">
<MarkdownField
value={note}
onChange={setNote}
placeholder="What this is for, and what is left to do."
label="Description"
rows={4}
disabled={busy}
/>
</div>
<div className="flex items-center gap-3">
<Button type="submit" size="sm" disabled={busy}>
Save
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setEditing(false)}
>
Cancel
</Button>
{error && (
<span className="text-[12.5px] text-destructive">{error}</span>
)}
</div>
</form>
);
}
return (
<>
{pull.note && (
<StatementCard who={pull.author} at={pull.noteAt} note={pull.note} />
)}
{mine && (
<button
type="button"
onClick={() => {
setNote(pull.note ?? '');
setError('');
setEditing(true);
}}
className="mt-2 flex cursor-pointer items-center gap-1.5 text-[12.5px] text-key hover:underline"
>
<PencilIcon className="size-3.5" />
{pull.note ? 'Edit description' : 'Add a description'}
</button>
)}
</>
);
}