SummaryEditor

tcmts.studio.SummaryEditor · #jcxpaffp · term · runs in the browser

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>
  );
}

uses

used by