"use client"; import { type Descendant, KEYS } from "platejs"; import { createPlatePlugin, type PlateElementProps } from "platejs/react"; import type { FC } from "react"; import { InlineCitation, UrlCitation } from "@/components/assistant-ui/inline-citation"; import { CITATION_REGEX, type CitationUrlMap, parseTextWithCitations, } from "@/lib/citations/citation-parser"; /** * Plate inline-void node modeling a single `[citation:...]` reference. * * Modeled after the existing `MentionPlugin` pattern in * `inline-mention-editor.tsx` — the only confirmed pattern in this repo * for non-text inline UI. Inline-void elements satisfy Slate's invariant * that the editor renders both atomic widgets and surrounding text * cleanly without breaking selection / caret semantics. */ export type CitationElementNode = { type: "citation"; kind: "chunk" | "doc" | "url"; chunkId?: number; url?: string; /** Original `[citation:...]` substring for traceability/debugging. */ rawText: string; children: [{ text: "" }]; }; const CITATION_TYPE = "citation"; const CitationElement: FC> = ({ attributes, children, element, }) => { const isUrl = element.kind === "url"; return ( {isUrl && element.url ? ( ) : element.chunkId !== undefined ? ( ) : null} {children} ); }; const CitationPlugin = createPlatePlugin({ key: CITATION_TYPE, node: { isElement: true, isInline: true, isVoid: true, type: CITATION_TYPE, component: CitationElement, }, }); /** Plugin kit shape used elsewhere in the editor. */ export const CitationKit = [CitationPlugin]; // --------------------------------------------------------------------------- // Slate value transform — runs after MarkdownPlugin.deserialize // --------------------------------------------------------------------------- // Structural shapes used by the value transform. We cannot use Plate's // generic Element / Text type predicates directly because `Descendant` is a // constrained union and our predicates would over-narrow. Casting through // these row types keeps the walker readable without fighting the types. type SlateText = { text: string } & Record; type SlateElement = { type?: string; children: Descendant[] } & Record; function isText(node: Descendant): boolean { return typeof (node as { text?: unknown }).text === "string"; } function asText(node: Descendant): SlateText { return node as unknown as SlateText; } function asElement(node: Descendant): SlateElement { return node as unknown as SlateElement; } /** * Element types whose subtrees we MUST NOT inject citation void elements * into. Each rationale documented in the citation plan: * - `KEYS.codeBlock` / `code_line` — Plate's schema rejects inline elements * inside code containers; the user expects literal text inside code. * - `KEYS.link` — `