tfrere's picture
tfrere HF Staff
feat(editor): Iframe embed component for remote URLs
0c69852
Raw
History Blame Contribute Delete
5.76 kB
// ---------------------------------------------------------------------------
// 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<string, { label: string; placeholder?: string }>;
}
const UI_META: Record<string, UIMeta> = {
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: "<div>…</div>" } },
},
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);