/** * The reference mention plugin — `@[label](id)` mentions as native chips. * * The proving consumer of the P3 plugin API (#156): one plugin wires all * three surfaces together — * * - **parser**: `@[label](id)` parses to an `extension` node (streaming-safe: * a half-typed `@[lab` stays literal text), * - **editor field**: the node maps to a `mention` span over a single * U+FFFC (the chip invariant — see `InlineSpanType` in lynx-richtext), * rendered natively as a pill; selecting a suggestion inserts the chip * via `controller.insertChip`, replacing the typed trigger run, * - **markdown out**: the span serializes back to `@[label](id)` from its * attrs (the covered text is the U+FFFC, never the label). * * v1 label rule: `]`, `)`, and CR/LF are forbidden in both labels and ids — * the serializer strips them and the parser regex doesn't match them, so * round-trips are idempotent by construction. * * A factory (not a constant) because the consumer supplies the candidate * source: * * ```tsx * const mentions = createMentionPlugin({ * search: (q) => users.filter((u) => u.label.toLowerCase().startsWith(q.toLowerCase())), * }); * * ``` */ import type { JSXElement } from '@sigx/lynx'; import type { ParserInlineExtension } from '../parser/extensions.js'; import type { ExtensionProps, MarkdownChild } from '../render/components.js'; import type { MarkdownEditorPlugin, TriggerItem } from '../editor/plugin.js'; export interface MentionCandidate { id: string; label: string; kind?: string; /** * Extra **display-only** fields (e.g. `avatar`, `subtitle`) — carried * through to the suggestion row's `renderItem` as `item.` for a * richer popup UI. They never reach the chip payload or the serialized * markdown (only `id`/`label`/`kind` do), so they can hold anything the row * needs without affecting round-tripping. */ [key: string]: unknown; } export interface MentionPluginOptions { /** Resolve candidates for the typed query (sync or async). */ search(query: string): MentionCandidate[] | Promise; /** Re-skin a suggestion row in the popup. */ renderItem?(item: TriggerItem, active: boolean): JSXElement; /** * Preview-pill renderer, exposed as `plugin.inline.component`. * `MarkdownView` is not plugin-aware — wire it up explicitly: * `components={{ extension: { mention: plugin.inline.component } }}` * (with `extensions={[mentionSyntax]}`). */ component?: (props: ExtensionProps) => MarkdownChild; /** Popup trigger char (the markdown syntax stays `@[label](id)`). Default `'@'`. */ trigger?: string; /** Debounce between `search` calls in ms. */ debounce?: number; } /** The parser extension for `@[label](id)` (exported for MarkdownView previews). */ export declare const mentionSyntax: ParserInlineExtension; export declare function createMentionPlugin(options: MentionPluginOptions): MarkdownEditorPlugin;