SummaryEditor
uses useState
What a release is about, in its notes: as it'll read (html, made on the server), or nothing, with a button that
opens it to write (Add notes, Edit notes); written, it's saved (Save, or ⌘↩) to the package's draft
(writeReleaseSummary), which releasing takes it from.
import { refresh } from "#codebase/tcmts/react"; import { toasts } from "#codebase/tcmts/share"; import { writeReleaseSummary } from "#codebase/tcmts/studio"; import { useState } from "react"; import { Button, TextArea, TextField } from "react-aria-components"; export function SummaryEditor({ name, version, initial, html, }: { name: string; version: string; initial: string; html: string; }) { const [editing, setEditing] = useState(false); const [text, setText] = useState(initial); const [busy, setBusy] = useState(false); const save = async () => { setBusy(true); try { await writeReleaseSummary(name, version, text); setEditing(false); refresh(); } catch (e) { toasts.add( { title: "Couldn't save it", description: (e as Error).message, tone: "bad" }, { timeout: 8000 }, ); } setBusy(false); }; if (!editing) return ( <div className="summary"> {html && <div dangerouslySetInnerHTML={{ __html: html }} />} <Button className="btn small" onPress={() => (setText(initial), setEditing(true))}> {initial ? "Edit notes" : "Add notes"} </Button> </div> ); return ( <TextField className="summary" aria-label={`What ${name} ${version} is about`} value={text} onChange={setText} autoFocus > <TextArea className="field" rows={Math.max(3, text.split("\n").length)} placeholder={`What's ${version} about, for the people who use ${name}? (Markdown)`} onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(); if (e.key === "Escape") setEditing(false); }} /> <div className="actions" style={{ marginTop: 6 }}> <Button className="btn small primary" isPending={busy} onPress={save}> {busy ? "Saving…" : "Save"} </Button> <Button className="btn small" onPress={() => setEditing(false)}> Cancel </Button> </div> </TextField> ); }