// --------------------------------------------------------------------------- // Component Registry // // Each entry describes a custom MDX component that can be used inside the // editor. Factories (factory.ts) consume these definitions to auto-generate // TipTap extensions, NodeViews, slash-menu items and MDX serializers. // // Structural data (name, kind, fields, content) comes from the shared module. // This file adds UI metadata (tag, icon, label, description, placeholders). // --------------------------------------------------------------------------- import { SHARED_COMPONENT_DEFS, type SharedComponentDef } from "#shared/component-defs"; export interface ComponentField { name: string; type: "string" | "boolean" | "select"; default?: unknown; options?: string[]; label: string; placeholder?: string; } export interface ComponentDef { /** Internal TipTap node name, e.g. "accordion" */ name: string; /** MDX / Astro tag name, e.g. "Accordion" */ tag: string; /** Single-char or emoji icon for the slash menu */ icon: string; /** Human-readable label shown in the slash menu */ label: string; /** Short description shown below the label */ description: string; /** "wrapper" = has editable children, "atomic" = self-closing */ kind: "wrapper" | "atomic"; /** Editable attributes exposed in the toolbar */ fields: ComponentField[]; /** ProseMirror content expression (wrapper only, defaults to "block+") */ content?: string; } // UI metadata per component (tag, icon, label, description, field labels/placeholders) interface UIMeta { tag: string; icon: string; label: string; description: string; fieldMeta: Record; } const UI_META: Record = { accordion: { tag: "Accordion", icon: "▸", label: "Accordion", description: "Collapsible content section", fieldMeta: { title: { label: "Title", placeholder: "Section title…" }, open: { label: "Open by default" } }, }, note: { tag: "Note", icon: "ℹ", label: "Note / Callout", description: "Highlighted callout box", fieldMeta: { title: { label: "Title", placeholder: "Optional title…" }, emoji: { label: "Emoji", placeholder: "e.g. 💡" }, variant: { label: "Variant" } }, }, quoteBlock: { tag: "Quote", icon: "❝", label: "Quote block", description: "Quote with author attribution", fieldMeta: { author: { label: "Author", placeholder: "Author name…" }, source: { label: "Source", placeholder: "Book, URL…" } }, }, wide: { tag: "Wide", icon: "↔", label: "Wide", description: "Wider-than-column container", fieldMeta: {}, }, fullWidth: { tag: "FullWidth", icon: "⟷", label: "Full width", description: "Full-width container", fieldMeta: {}, }, sidenote: { tag: "Sidenote", icon: "¶", label: "Sidenote", description: "Margin note alongside content", fieldMeta: {}, }, reference: { tag: "Reference", icon: "🏷", label: "Reference / Figure", description: "Captioned figure with anchor ID", fieldMeta: { id: { label: "ID", placeholder: "fig-1" }, caption: { label: "Caption", placeholder: "Figure caption…" } }, }, htmlEmbed: { tag: "HtmlEmbed", icon: "📊", label: "HTML Embed", description: "Embed an external HTML visualization", fieldMeta: { src: { label: "Source file", placeholder: "d3-chart.html" }, title: { label: "Title", placeholder: "Chart title…" }, desc: { label: "Description", placeholder: "Chart description…" }, wide: { label: "Wide" }, downloadable: { label: "Downloadable" }, height: { label: "Height (px)", placeholder: "400" } }, }, hfUser: { tag: "HfUser", icon: "👤", label: "HF User card", description: "Hugging Face user profile card", fieldMeta: { username: { label: "Username", placeholder: "username" }, name: { label: "Display name", placeholder: "Full Name" }, url: { label: "URL", placeholder: "https://huggingface.co/username" } }, }, iframe: { tag: "Iframe", icon: "🔗", label: "Iframe", description: "Embed a remote URL (Space, widget, demo…)", fieldMeta: { src: { label: "URL", placeholder: "https://my-space.hf.space/" }, title: { label: "Title", placeholder: "Embed title…" }, desc: { label: "Description", placeholder: "Caption shown below…" }, height: { label: "Height (px)", placeholder: "600" }, wide: { label: "Wide" }, }, }, rawHtml: { tag: "RawHtml", icon: "", label: "Raw HTML", description: "Inject raw HTML content", fieldMeta: { html: { label: "HTML", placeholder: "
" } }, }, mermaid: { tag: "Mermaid", icon: "◈", label: "Mermaid diagram", description: "Flowchart, sequence, Gantt, etc.", fieldMeta: { code: { label: "Code", placeholder: "graph LR\\n A --> B" } }, }, }; function buildComponentDef(shared: SharedComponentDef): ComponentDef { const ui = UI_META[shared.name]; if (!ui) throw new Error(`Missing UI_META for component "${shared.name}"`); return { name: shared.name, tag: ui.tag, icon: ui.icon, label: ui.label, description: ui.description, kind: shared.kind, content: shared.content, fields: shared.fields.map((f) => ({ name: f.name, type: f.type, default: f.default, options: f.options, label: ui.fieldMeta[f.name]?.label ?? f.name, placeholder: ui.fieldMeta[f.name]?.placeholder, })), }; } // --------------------------------------------------------------------------- // Registry - built from shared definitions + UI metadata // --------------------------------------------------------------------------- export const COMPONENTS: ComponentDef[] = SHARED_COMPONENT_DEFS.map(buildComponentDef);