import type { ReactNode } from 'react'; import { Extension, type Editor } from '@tiptap/core'; import { type MentionOptions } from '@tiptap/extension-mention'; import type { SuggestionMenuRow } from './SuggestionMenu'; import { type SuggestionPopupOptions } from './suggestionPopup'; /** A person `@` and `!@` offer. */ export interface MentionPerson { /** The app's own key for the person — the row's key and the chip's `id`. */ id: string; name: string; /** The second line of the row: a role, a team. */ description?: string; /** Their Nostr key, when they have one — what the body is written with (SPEC §13.1). */ pubkey?: string | null; /** The face at the row's start. Default: the package `Avatar` for the name. */ avatar?: ReactNode; } /** How many people the list shows. */ export declare const PEOPLE_LIMIT = 6; /** The people whose name holds the query, the first {@link PEOPLE_LIMIT} of them. */ export declare function filterPeople(people: readonly MentionPerson[], query: string): MentionPerson[]; /** * The people list `@` and `!@` open: the face, the name, the second line, and * the Enter hint while highlighted — `MenuItem` at its tall size. */ export declare function peopleMenu(urgent: boolean): SuggestionPopupOptions; export interface PersonMentionOptions { /** Everyone the list can offer, asked each time the query changes. */ people: () => readonly MentionPerson[]; } type Doc = { descendants: (cb: (node: { type: { name: string; }; attrs: Record; }) => boolean | void) => void; }; /** * `@` — a person, as a chip that writes their key (SPEC §13.1). * * PersonMention.configure({ people: () => directory }) * * Its `pubkey` and `label` are what `mentionText` takes. */ export declare const PersonMention: import("@tiptap/core").Node & PersonMentionOptions, any>; /** * `!@` — a person, urgently: the same chip in the urgent tone. Its text is an * ordinary mention; the urgency travels as a tag (`urgentTagsFor`), so hand the * write {@link urgentPubkeys}. */ export declare const UrgentPersonMention: import("@tiptap/core").Node & PersonMentionOptions, any>; /** * The keys the document's urgent mentions name — what the write turns into * `["urgent", ]` tags. A chip with no key has no tag to carry; its text * is the old `!@Name`. */ export declare function urgentPubkeys(editor: { state: { doc: Doc; }; }): string[] | undefined; export interface PersonReferenceOptions { /** Everyone `@` can offer, asked each time the query changes. */ people: () => readonly MentionPerson[]; /** The `nostr:npub…` a person is written as; `null` for someone without one, who is not offered. */ uriOf: (person: MentionPerson) => string | null; /** The node a pick inserts. Default `reference`, `ReferenceNode`'s. */ nodeName: string; } /** * `@` in a document — a person, written as a reference to them: a * `ReferenceNode` whose `uri` is their `nostr:npub…` (SPEC §13.1, §13.3). Not * {@link PersonMention}'s chip, which is a message's: a document names people * the way it names anything else, and a save that newly names someone is what * adds them (`@estiva-app/conversation`'s `mentionAddsFor`, which reads only * that uri). * * PersonReference.configure({ people: () => directory, uriOf: (person) => npubUri(person.pubkey) }) * * The chip carries no name: the app's `ReferenceNode` view resolves the uri, * so a rename shows everywhere the person is named. * * why: https://github.com/estiva-app/estiva-docs/blob/main/decisions/a-mention-in-a-files-text-adds-the-person-with-no-unread.md */ export declare const PersonReference: Extension; /** Something `[` offers: a reference drawn as who said it and how it opens. */ export interface CaptionedItem { /** The referenced thing's id — the row's key and the chip's `id`. */ id: string; /** Who, as the list shows them. */ label: string; /** The opening words, marks stripped: the caption the row and the chip wear. */ snippet: string; /** `nostr:…`, exactly as the body will carry it (`messageReference`). */ uri: string; /** What the query is matched against beside `label`. Default: `snippet`. */ search?: string; /** The row's second line, e.g. where the thing sits. The chip does not wear it. */ description?: string; /** * What the thing is about, in a line of plain text — a file's summary. When * set, it is the row's second line and `description` moves up after the * label, muted; absent, the row is as before. The chip does not wear it, and * the query is not matched against it. */ summary?: string; /** * This row's own 16px icon, in place of the list's `icon`: what the thing is, * when the list holds several kinds — a topic, a project, a message * (Katerina, 7 October). Absent, the list's icon. */ icon?: ReactNode; } /** One labelled group of `[`'s rows, in the order the list draws them. */ export interface ReferenceSection { label: string; items: readonly CaptionedItem[]; } /** How many references the list shows. */ export declare const REFERENCE_LIMIT = 6; /** * The references whose label or text holds the query — the last * {@link REFERENCE_LIMIT}, in the order given: a thread is unbounded, and a * list that grew with it would be a hundred rows of prose. */ export declare function filterReferences(items: readonly CaptionedItem[], query: string): CaptionedItem[]; export interface CaptionedReferenceOptions { /** Drawn before the caption: a 14px icon. */ icon: ReactNode; /** * The chip's 14px icon from what it points at — its `uri` — when chips name * several kinds of thing; `undefined` from it, or no `iconFor`, draws `icon`. * The same answer the list's row gave (Katerina, 7 October). */ iconFor?: (uri: string | null) => ReactNode | undefined; } /** * A reference mid-sentence, drawn as `label: snippet` and written as its `uri` * verbatim (SPEC §13.1). The caption is the snapshot it was picked with — a * caption, never parsed. Inserted by {@link ReferenceTrigger}, a paste or a * draft; it opens no list of its own. */ export declare const CaptionedReference: import("@tiptap/core").Node & CaptionedReferenceOptions, any>; export interface ReferenceMenuOptions { /** Names the list, e.g. what can be referenced. */ ariaLabel: string; /** The section's heading. */ sectionLabel: string; /** At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`), or `iconFor` answers. */ icon: ReactNode; /** * A row's 16px icon from what it points at — its `uri` — when the list holds * several kinds of thing; `undefined` from it draws `icon`. Give the chip the * same answer at 14px (`CaptionedReference`'s `iconFor`), so a pick keeps it. */ iconFor?: (uri: string) => ReactNode | undefined; } /** * The list `[` opens: `label: snippet` rows, the same box as the people list. * One section headed `sectionLabel`, or — when the rows say which section they * are in — a heading for each, in the order the rows arrive. */ export declare function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }: ReferenceMenuOptions): SuggestionPopupOptions; export interface ReferenceTriggerOptions extends ReferenceMenuOptions { /** Everything `[` can offer, asked each time the query changes. Narrowed by {@link filterReferences}. */ items: () => readonly CaptionedItem[]; /** * In place of `items`: the sections to draw for what was typed, already * narrowed and ordered by the app. Empty sections are not drawn. */ sections?: (query: string) => readonly ReferenceSection[]; /** * Called with a listener while the list is open; the app calls it when what * `sections` would answer has changed — a search landing — and the open list * is drawn again for the same query. Returns the unsubscribe. */ subscribe?: (listener: () => void) => () => void; /** The node a pick inserts. Default `captionedReference`. */ nodeName: string; } /** * `[` — reference something, inserted as a {@link CaptionedReference} chip. * * Its own extension rather than the chip's suggestion, so the chip's text (its * `renderText`) does not change with the key that summons the list. */ export declare const ReferenceTrigger: Extension; /** A row of the `/` list, and what choosing it does. */ export interface SlashCommand { key: string; row: SuggestionMenuRow; /** Matched beside the row's label: an id, a trigger, a description. */ keywords?: readonly string[]; /** Runs once the `/query` that summoned the list has been removed. */ run: (editor: Editor) => void; /** * The extensions (by name) the row needs: an editor without one of them is * not offered the row, so no row makes something the editor cannot hold. */ needs?: readonly string[]; } export interface SlashSection { label: string; commands: readonly SlashCommand[]; } export type FormatId = 'paragraph' | 'heading' | 'subheading' | 'quote' | 'bulletList' | 'orderedList' | 'code' | 'checklist' | 'note' | 'warning'; export interface FormatDef { id: FormatId; label: string; /** What you type to get it — the input rule the extension registers. Blank when there is none. */ trigger?: string; /** The trigger when the format makes a block instead — Code's fence. Blank when it is the same. */ blockTrigger?: string; } /** * The formats `/` offers, in order. The hint is what you would type, not a key * chord. Bold, italic and underline are absent: they act on text already * written, so they are the selection toolbar's. */ export declare const FORMATS: readonly FormatDef[]; /** * What only a document holds — a checklist item and a callout (RIC-19) — so * only a block editor's `/` offers them (`formatSection('block')`): a * message's text has no spelling for either. They need the `Checklist` and * `Callout` extensions. Note and Warning are the two callouts offered (Miky, * 2026-10-13); the other tones are read and kept, not offered. */ export declare const DOCUMENT_FORMATS: readonly FormatDef[]; /** Code as a mark inside the line, or as a block of its own. */ export type CodeFormat = 'inline' | 'block'; /** * Apply one format to the current block. `set*` wherever Tiptap has one: * choosing Heading should make a heading, not turn one off. `false` means * nothing changed, not that anything failed. */ export declare function applyFormat(editor: Editor, id: FormatId, code?: CodeFormat): boolean; /** * The Format section every app's `/` opens with. A block editor's also offers * a checklist and the callouts — only where the editor has the `Checklist` and * `Callout` extensions (`SlashCommand.needs`). */ export declare function formatSection(code?: CodeFormat): SlashSection; /** A row that types another trigger, so its own list opens — `@` from `/`. */ export declare function typeTrigger(input: string): SlashCommand['run']; /** The sections narrowed to a query — the text typed after the `/`. Empty sections stay. */ export declare function filterSlashSections(sections: readonly SlashSection[], query: string): SlashSection[]; export interface SlashCommandsOptions { /** The sections, in order: {@link formatSection} first, then the app's own. */ sections: readonly SlashSection[]; } /** * `/` — format the line or insert something, at the caret, mid-sentence too. * A `/` inside a word (`and/or`, a path) opens nothing: the plugin wants a * space or the line's start before it. */ export declare const SlashCommands: Extension; export {}; //# sourceMappingURL=ComposerTriggers.d.ts.map