import { type ComponentType, type ReactNode } from 'react'; import { type AnyExtension } from '@tiptap/core'; import { Editor, type JSONContent } from '@tiptap/react'; import { type ReferenceViewProps } from './BlockNodes'; import { type CodeFormat, type MentionPerson, type ReferenceSection, type SlashCommand } from './ComposerTriggers'; /** * The one box every app writes a comment, a reply and a chat line in, from the * approved prototype (`concept/comments-everywhere`, Miky 17 October): the * box's one frame (`commentBoxFrame`) with everything each app built around it — `@`, * `!@`, `[` and `/`, the keys, drafts, files, the edit mode, sending and a * refused send. An app hands over only its people, its `[` sections, its `/` * Insert rows and a send that says whether the relay accepted it. * * It speaks the editor's document. What that document writes as a message, * and the document an edit or a draft opens with, are `@estiva-app/conversation`'s * (`composerText`, `composerDocument`, `composerDraft`). * * why: https://github.com/estiva-app/estiva-docs/blob/main/decisions/every-app-writes-and-reacts-with-one-shared-box.md */ /** What the relay said about a send, an edit or an upload. */ export type SendAnswer = { accepted: true; } | { accepted: false; reason: string; }; /** What an upload answered: the app's own record of the file, or why not. */ export type UploadAnswer = { accepted: true; value: unknown; } | { accepted: false; reason: string; }; /** Where a file in the box is: Send waits for one uploading. */ export type BoxFileState = 'uploading' | 'ready' | 'failed'; /** A file in the box. */ export interface BoxFile { id: string; name: string; size?: number; contentType?: string; state: BoxFileState; /** Why it failed, under its name. */ note?: string; /** What `upload` answered for it: the app's own record (an `imeta`, a URL). */ value?: unknown; } /** A draft, in the box's terms — `@estiva-app/conversation`'s `composerDraft` is one. */ export interface CommentBoxDraft { read(): JSONContent | undefined; write(body: JSONContent): void; clear(): void; } /** Editing something already sent: the same box, with Cancel and Save. */ export interface CommentBoxEdit { /** What it says now, as `composerDocument` opens it. */ body: JSONContent; /** Its files, which can be taken off; none can be added. */ files?: BoxFile[]; onCancel: () => void; /** Save; the box holds the words until the answer, and a refusal is said under it. */ onSave: (body: JSONContent, files: BoxFile[], under: BoxSentUnder) => Promise; } /** * What a draft shows under the box, as the app reads it from the box's * document — `@estiva-app/conversation`'s `boxReadOf`, which reads it as the * message will be sent (why: * https://github.com/estiva-app/estiva-docs/blob/main/decisions/a-link-on-words-counts-toward-the-card.md). */ export interface BoxUnderRead { /** The one card, before any ✕: what it names, and the app's own view of it, drawn as it draws a sent one. */ card?: { value: string; view: ReactNode; }; /** The live parts, drawn as the app draws them under a sent message. An edit carries none, so the edit box shows none. */ parts?: readonly { key: string; view: ReactNode; }[]; /** Everything the draft names. A ✕ is forgotten once what it took off is no longer named, so a link put back gets its card again. */ names: readonly string[]; } /** What the box shows under itself while something is written. */ export interface BoxUnder { /** Read on every change, and once more as it is sent. */ read: (body: JSONContent) => BoxUnderRead; /** Called with a listener; call it when `read` would answer differently (what a link names came back). */ changed?: (listener: () => void) => () => void; } /** What was under the box as it was sent or saved. */ export interface BoxSentUnder { /** The card still shown, by what it names: absent when there was none, or ✕ took it off. */ card?: string; } export interface CommentBoxProps { /** Who `@` and `!@` offer. */ people: readonly MentionPerson[]; /** What `[` lists for what was typed, already in the app's order. Absent, `[` opens nothing. */ references?: (query: string) => readonly ReferenceSection[]; /** Called with a listener while `[` is open; call it when `references` would answer differently (a search landed). */ referencesChanged?: (listener: () => void) => () => void; /** A `[` row's and chip's 16px icon from what it points at; absent, a neutral square. */ referenceIcon?: (uri: string | null) => ReactNode | undefined; /** The app's own `/` Insert rows, after the shared Format section: {@link commentBoxRows} and its own. */ insert?: readonly SlashCommand[]; /** `/` → Code: a mark inside the line, or a block. */ code?: CodeFormat; /** Send what is written, with the files that are ready and the card still shown; resolves with the relay's answer. */ send: (body: JSONContent, files: BoxFile[], under: BoxSentUnder) => Promise; /** The card and parts under the box while something is written, with ✕ on the card. Absent, nothing shows under it. */ under?: BoxUnder; /** * The app shows a send before the relay answers: the box empties at once, * and a refusal puts the words back. Otherwise the box holds the words, shut, * until the answer. */ showsAtOnce?: boolean; /** The editor's accessible name: what the box is for. */ label: string; /** Said in the empty box. */ placeholder: ReactNode; /** Keeps what is written when the box goes away. Give the box a React `key` per destination. */ draft?: CommentBoxDraft; /** Editing something already sent. */ edit?: CommentBoxEdit; /** Offers the paperclip, `/` → File, a pasted picture and a dropped file: uploads one, and answers. */ upload?: (file: File) => Promise; /** The file picker's `accept`. */ accept?: string; /** The box cannot write here (an archived file, signed out): a line saying why takes its place. */ closed?: { reason: ReactNode; }; /** How a `nostr:` reference the box holds is drawn — the app's own chip. Absent, as its URI. */ referenceView?: ComponentType; /** The app's own extra editor extensions: link chips, highlights. */ extensions?: readonly AnyExtension[]; autoFocus?: boolean; /** Called with what was sent once it went out: at once when `showsAtOnce`, else on the answer. */ onSent?: (body: JSONContent, files: BoxFile[], under: BoxSentUnder) => void; /** Called when a send shown at once was refused, so the app takes it back. */ onRefused?: () => void; /** The bar down the text's left edge shows while this holds, besides while an urgent mention is in the box: the app's own marks (a highlight). */ marked?: (editor: Editor) => boolean; /** Called with the box's editor once it is drawn, and with `null` as it goes: an app's launcher inserts into the box last used. */ onEditor?: (editor: Editor | null) => void; className?: string; } /** * The `/` Insert rows the kit offers. Pass the ones an app can perform, in * its order, with its own after them; an edit drops Upload and Urgent itself. * `file` is the Upload row, named as in a document's `/`. */ export declare const commentBoxRows: Readonly>; /** Keeps an editor built ahead for the next empty box given these `extensions` and `referenceView`, so it opens at once. */ export declare function keepCommentBoxWarm(app?: { extensions?: readonly AnyExtension[]; referenceView?: ComponentType; }): void; export declare function CommentBox({ people, references, referencesChanged, referenceIcon, insert, code, send, under, showsAtOnce, label, placeholder, draft, edit, upload, accept, closed, referenceView, extensions, autoFocus, onSent, onRefused, marked, onEditor, className, }: CommentBoxProps): import("react").JSX.Element; //# sourceMappingURL=CommentBox.d.ts.map