import { IconCode, IconEdit, IconX } from "@tabler/icons-react"; import { useEffect, useId, useState } from "react"; import { AiEditableFieldLabel } from "../AiEditableField.js"; import { defineBlock } from "../types.js"; import type { BlockReadProps, BlockEditProps } from "../types.js"; import { htmlSchema, htmlMdx, type HtmlBlockData } from "./html.config.js"; import { useIsDark } from "./wireframe-kit.js"; /** * Standard library HTML / Tailwind block. The registry form of the plan * `custom-html` block: an author-supplied HTML (+ optional CSS) fragment * rendered inside a sandboxed iframe, with an inline source editor. * * Security: the fragment is rendered in a `sandbox="allow-same-origin"` iframe * with `referrerPolicy="no-referrer"` — no scripts execute — and the schema's * `noFullHtmlDocument` refine rejects document/script/handler markup before it * is ever stored. When the app injects `ctx.sanitizeHtml`, the fragment + CSS * are additionally sanitized before being placed in the iframe `srcDoc`. * * Styling uses app-agnostic shadcn utility classes (`border`, `bg-muted`, * `text-muted-foreground`) so the block renders cleanly in any template, not * just the plan app. */ /** Build the iframe document for a fragment, applying app sanitization if given. */ function buildSrcDoc( data: HtmlBlockData, theme: "light" | "dark", sanitize?: (html: string, css?: string) => string, ): string { const css = data.css ?? ""; const body = sanitize ? sanitize(data.html, data.css) : data.html; // The iframe is isolated from the host's `.dark` class and CSS variables, so // bridge the current theme explicitly and expose the same semantic tokens that // generated wireframe/diagram HTML already uses. return `${body}`; } function HtmlPreview({ data, title, sanitize, }: { data: HtmlBlockData; title?: string; sanitize?: (html: string, css?: string) => string; }) { const isDark = useIsDark(); const theme = isDark ? "dark" : "light"; return ( <>