@mjml-agent-editor/editor 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/editor/EmailEditor.tsx","../src/components/ui/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport grapesjs, { Component, Editor } from \"grapesjs\";\nimport GjsEditor, { Canvas, useEditorMaybe } from \"@grapesjs/react\";\nimport grapesjsMJML from \"grapesjs-mjml\";\nimport { useRef, useState, type ReactNode } from \"react\";\n\nimport { STARTER_MJML, useDocumentStore, type CommentTarget } from \"../../index.js\";\nimport { cn } from \"../../lib/utils\";\nimport { toast } from \"sonner\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { Spinner } from \"../ui/spinner\";\nimport { Switch } from \"../ui/switch\";\nimport { Input } from \"../ui/input\";\nimport { Search } from \"lucide-react\";\nimport CanvasComments from \"../comments/CanvasComments.js\";\n\nexport type SaveStatus = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\n/** Snapshot of editor state pushed to the header (device, undo, save, zoom, width). */\nexport type EditorState = {\n device: string;\n canUndo: boolean;\n canRedo: boolean;\n saveStatus: SaveStatus;\n zoom: number;\n contentWidth: string;\n};\n\nexport type EditorApi = {\n /** Current MJML source from the editor. */\n getMjml: () => string;\n /** Compiled email HTML (via grapesjs-mjml's mjml-get-code). */\n getCompiledHtml: () => string;\n /** Immediately saves the editor state to the DB. */\n flushSave: () => Promise<void>;\n /** Reloads the document from the DB (after agent changes). */\n reloadFromDb: () => Promise<void>;\n /** Highlights a section in the canvas while the agent edits it. */\n highlightSection: (sectionId: string, on: boolean) => void;\n /** Device manager control (used by the top header). */\n getDevices: () => { id: string; name: string }[];\n setDevice: (name: string) => void;\n undo: () => void;\n redo: () => void;\n /** Canvas zoom (percentage). */\n setZoom: (z: number) => void;\n /** Email body width (mj-body width attribute, e.g. \"600px\"). */\n setContentWidth: (w: string) => void;\n /** Subscribe to editor state changes (device/undo/save/zoom/width). Returns unsubscribe. */\n onEditorState: (cb: (s: EditorState) => void) => () => void;\n};\n\ntype Props = {\n docId: string;\n onReady: (api: EditorApi) => void;\n /** Bump to force the canvas comment layer to refetch (after agent turns). */\n commentsRefresh: number;\n /** Reports the number of open comments (for the header indicator). */\n onOpenCountChange: (n: number) => void;\n};\n\nconst SEC_ID_RE = /\\bsec-([A-Za-z0-9_-]+)\\b/;\nconst OBJ_ID_RE = /\\bobj-([A-Za-z0-9_-]+)\\b/;\n\n// Element types that can receive a comment (besides a section).\nconst TYPE_LABEL: Record<string, string> = {\n \"mj-text\": \"Text\",\n \"mj-button\": \"Button\",\n \"mj-image\": \"Image\",\n \"mj-column\": \"Column\",\n};\n\nfunction tagOf(c: Component): string {\n return String(c.get(\"tagName\") || c.get(\"type\") || \"\");\n}\n\n// \"Sparkles\" icon (lucide Sparkles) used as a label in the GrapesJS toolbar.\nconst SPARKLES_SVG =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"/><path d=\"M20 3v4\"/><path d=\"M22 5h-4\"/><path d=\"M4 17v2\"/><path d=\"M5 18H3\"/></svg>';\n\nfunction isSection(c: Component): boolean {\n return tagOf(c) === \"mj-section\";\n}\n\nfunction isCommentable(c: Component): boolean {\n const t = tagOf(c);\n return t === \"mj-section\" || t in TYPE_LABEL;\n}\n\nfunction classMatch(c: Component, re: RegExp): string | null {\n const cssClass = String(c.getAttributes()[\"css-class\"] ?? \"\");\n return cssClass.match(re)?.[1] ?? null;\n}\n\nfunction sectionIdOf(c: Component): string | null {\n return classMatch(c, SEC_ID_RE);\n}\n\nfunction addIdClass(c: Component, prefix: \"sec\" | \"obj\"): string {\n const existing = classMatch(c, prefix === \"sec\" ? SEC_ID_RE : OBJ_ID_RE);\n if (existing) return existing;\n const cssClass = String(c.getAttributes()[\"css-class\"] ?? \"\");\n const id = Math.random().toString(36).slice(2, 10);\n c.addAttributes({ \"css-class\": `${cssClass} ${prefix}-${id}`.trim() });\n return id;\n}\n\nfunction closestSection(c: Component | undefined): Component | null {\n let cur: Component | undefined = c;\n while (cur) {\n if (isSection(cur)) return cur;\n cur = cur.parent() ?? undefined;\n }\n return null;\n}\n\n// Friendly names for the canvas breadcrumb.\nconst CRUMB_NAME: Record<string, string> = {\n \"mj-section\": \"Section\",\n \"mj-column\": \"Column\",\n \"mj-group\": \"Group\",\n \"mj-text\": \"Text\",\n \"mj-button\": \"Button\",\n \"mj-image\": \"Image\",\n \"mj-divider\": \"Divider\",\n \"mj-spacer\": \"Spacer\",\n \"mj-hero\": \"Hero\",\n \"mj-navbar\": \"Navbar\",\n \"mj-social\": \"Social\",\n \"mj-social-element\": \"Social\",\n \"mj-wrapper\": \"Wrapper\",\n};\n\nfunction crumbName(c: Component): string {\n const t = tagOf(c);\n return CRUMB_NAME[t] ?? t.replace(/^mj-/, \"\") ?? \"Element\";\n}\n\nfunction objectLabel(c: Component): string {\n const base = TYPE_LABEL[tagOf(c)] ?? tagOf(c);\n const text = String((c.getEl?.() as HTMLElement | undefined)?.textContent ?? \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 30);\n return text ? `${base}: “${text}”` : base;\n}\n\nconst RTE_FONTS = [\n \"Arial\",\n \"Helvetica\",\n \"Georgia\",\n \"Times New Roman\",\n \"Verdana\",\n \"Tahoma\",\n \"Trebuchet MS\",\n \"Courier New\",\n];\nconst RTE_SIZES = [11, 12, 13, 14, 16, 18, 20, 24, 28, 32, 40];\n\n// Wraps the current selection (in the canvas document) in a <span> with inline style.\nfunction wrapSelectionStyle(el: HTMLElement | undefined, style: Partial<CSSStyleDeclaration>) {\n const doc = el?.ownerDocument;\n const sel = doc?.getSelection?.();\n if (!doc || !sel || sel.rangeCount === 0 || sel.isCollapsed) return;\n const range = sel.getRangeAt(0);\n const span = doc.createElement(\"span\");\n Object.assign(span.style, style);\n try {\n span.appendChild(range.extractContents());\n range.insertNode(span);\n sel.removeAllRanges();\n const nr = doc.createRange();\n nr.selectNodeContents(span);\n sel.addRange(nr);\n } catch {\n /* ignore */\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Extends the default GrapesJS RTE (mjml-compatible) with font/size/colors.\nfunction setupRichText(editor: Editor) {\n const rte = editor.RichTextEditor as any;\n if (!rte || rte.__customActions) return;\n rte.__customActions = true;\n const val = (action: any, sel: string) =>\n (action.btn?.querySelector(sel) as HTMLInputElement | HTMLSelectElement | null)?.value ?? \"\";\n\n rte.add(\"fontName\", {\n icon: `<select class=\"gjs-rte-select\" title=\"Font\"><option value=\"\">Font</option>${RTE_FONTS.map(\n (f) => `<option value=\"${f}\">${f}</option>`,\n ).join(\"\")}</select>`,\n event: \"change\",\n result: (r: any, action: any) => {\n const v = val(action, \"select\");\n if (v) r.exec(\"fontName\", v);\n },\n });\n\n rte.add(\"fontSize\", {\n icon: `<select class=\"gjs-rte-select\" title=\"Size\"><option value=\"\">Size</option>${RTE_SIZES.map(\n (s) => `<option value=\"${s}px\">${s}</option>`,\n ).join(\"\")}</select>`,\n event: \"change\",\n result: (r: any, action: any) => {\n const v = val(action, \"select\");\n if (v) wrapSelectionStyle(r.el, { fontSize: v });\n },\n });\n\n rte.add(\"forecolor\", {\n icon: `<input type=\"color\" class=\"gjs-rte-color\" title=\"Text color\" value=\"#000000\">`,\n event: \"change\",\n result: (r: any, action: any) => r.exec(\"foreColor\", val(action, \"input\")),\n });\n\n rte.add(\"hilitecolor\", {\n icon: `<input type=\"color\" class=\"gjs-rte-color\" title=\"Background color\" value=\"#ffff00\">`,\n event: \"change\",\n result: (r: any, action: any) => r.exec(\"hiliteColor\", val(action, \"input\")),\n });\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nexport default function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }: Props) {\n // Injected by the host rather than imported, so the editor carries no database.\n const documents = useDocumentStore();\n // Refs instead of state — GrapesJS lives outside React's lifecycle.\n const loadingRef = useRef(false);\n const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const saveStatusRef = useRef<SaveStatus>(\"idle\");\n const stateListeners = useRef(new Set<(s: EditorState) => void>());\n const [loading, setLoading] = useState(true);\n const [sidebarView, setSidebarView] = useState<SidebarView>(\"blocks\");\n // Comment being composed (sparkles button) — opens the canvas popover.\n const [composeTarget, setComposeTarget] = useState<CommentTarget | null>(null);\n // Where the current selection came from — canvas click opens Settings,\n // selecting from the Layers tree does not (keeps you on Layers).\n const selectionSource = useRef<\"canvas\" | \"layers\" | \"other\">(\"other\");\n // Breadcrumb of the selected component's ancestry (Section › Column › …).\n const editorRef = useRef<Editor | null>(null);\n const [crumbs, setCrumbs] = useState<Component[]>([]);\n\n const onEditor = async (editor: Editor) => {\n editorRef.current = editor;\n\n // Build the breadcrumb chain from a component up to (but excluding) mj-body.\n const buildCrumbs = (c: Component | undefined) => {\n const chain: Component[] = [];\n let cur: Component | undefined = c;\n while (cur) {\n const t = tagOf(cur);\n if (!t || t === \"mj-body\" || t === \"wrapper\" || t === \"Wrapper\") break;\n chain.unshift(cur);\n cur = cur.parent() ?? undefined;\n }\n setCrumbs(chain);\n };\n\n // GrapesJS find() by MJML tag is unreliable — walk the tree instead.\n const findByTag = (tag: string): Component | undefined => {\n let found: Component | undefined;\n editor.getWrapper()?.onAll((c) => {\n if (!found && (c.get(\"tagName\") === tag || c.get(\"type\") === tag)) found = c;\n });\n return found;\n };\n const bodyWidth = (): string => {\n const body = findByTag(\"mj-body\");\n return String((body?.getAttributes?.() as Record<string, unknown>)?.width ?? \"600px\");\n };\n const snapshot = (): EditorState => ({\n device: editor.getDevice(),\n canUndo: editor.UndoManager.hasUndo(),\n canRedo: editor.UndoManager.hasRedo(),\n saveStatus: saveStatusRef.current,\n zoom: Math.round((editor.Canvas.getZoom?.() as number | undefined) ?? 100),\n contentWidth: bodyWidth(),\n });\n const notifyState = () => {\n const s = snapshot();\n stateListeners.current.forEach((l) => l(s));\n };\n const setSave = (s: SaveStatus) => {\n saveStatusRef.current = s;\n notifyState();\n };\n\n // Comment target for a component: its section + (if it's not the section\n // itself) the specific element. Assigns stable IDs (sec-/obj-) when needed.\n const targetOf = (c: Component | undefined): CommentTarget | null => {\n const section = closestSection(c);\n if (!section || !c) return null;\n const sectionId = addIdClass(section, \"sec\");\n if (isSection(c)) {\n return { sectionId, objectId: null, objectLabel: null };\n }\n const objectId = addIdClass(c, \"obj\");\n return { sectionId, objectId, objectLabel: objectLabel(c) };\n };\n\n // Gives the section a sec-<id> and adds a comment button to the toolbar of\n // every commentable element (section + text/button/image/column).\n const decorate = (c: Component) => {\n if (isSection(c)) addIdClass(c, \"sec\");\n if (!isCommentable(c)) return;\n const toolbar = [...((c.get(\"toolbar\") as { command?: string }[]) ?? [])];\n if (!toolbar.some((t) => t.command === \"open-comments\")) {\n toolbar.push({\n command: \"open-comments\",\n label: SPARKLES_SVG,\n attributes: { title: \"Comment on element / section\", \"data-role\": \"sparkles\" },\n } as never);\n c.set(\"toolbar\", toolbar as never);\n }\n };\n\n editor.Commands.add(\"open-comments\", {\n run(ed: Editor) {\n const target = targetOf(ed.getSelected() ?? undefined);\n if (target) setComposeTarget(target);\n },\n });\n\n setupRichText(editor);\n\n // Group the palette blocks into categories (the plugin ships them flat).\n const blockCategory = (label: string): string => {\n const l = label.toLowerCase();\n if (/(column|section)/.test(l)) return \"Layout\";\n if (/(text|image|button|divider|spacer|social)/.test(l)) return \"Basic\";\n return \"Advanced\";\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const bm = editor.BlockManager as any;\n bm.getAll().forEach(\n (b: { get: (k: string) => string; set: (k: string, v: unknown) => void }) => {\n b.set(\"category\", blockCategory(b.get(\"label\") || \"\"));\n },\n );\n bm.render();\n\n editor.on(\"component:add\", decorate);\n // Track selection origin: a mousedown inside the canvas iframe marks the\n // next selection as coming from the canvas (→ open Settings). Selecting via\n // the Layers tree leaves the source as \"layers\" (set by the sidebar).\n editor.on(\"load\", () => {\n const body = editor.Canvas.getBody();\n body?.addEventListener(\n \"mousedown\",\n () => {\n selectionSource.current = \"canvas\";\n },\n true,\n );\n });\n editor.on(\"component:selected\", (c: Component) => {\n if (selectionSource.current === \"canvas\") setSidebarView(\"settings\");\n selectionSource.current = \"other\";\n buildCrumbs(c);\n // mj-image has no src trait by default (src changes via the Asset\n // Manager) — add a URL field so the image can be set from the panel.\n if (c?.get(\"type\") === \"mj-image\" && !c.getTrait(\"src\")) {\n c.addTrait(\n {\n type: \"text\",\n name: \"src\",\n label: \"Image (URL)\",\n placeholder: \"https://…\",\n changeProp: true,\n } as never,\n { at: 0 },\n );\n }\n });\n editor.on(\"component:deselected\", () => setCrumbs([]));\n\n const save = async () => {\n if (loadingRef.current) return;\n setSave(\"saving\");\n try {\n await documents.save(docId, {\n mjml: editor.getHtml(),\n projectData: editor.getProjectData(),\n });\n setSave(\"saved\");\n } catch (e) {\n setSave(\"error\");\n toast.error(\"Failed to save changes.\");\n throw e;\n }\n };\n\n editor.on(\"update\", () => {\n notifyState();\n if (loadingRef.current) return;\n if (saveTimer.current) clearTimeout(saveTimer.current);\n saveTimer.current = setTimeout(() => void save().catch(console.error), 1200);\n });\n editor.on(\"change:device\", notifyState);\n\n const loadMjml = (mjml: string) => {\n // Mute autosave during loading and for a moment after — setComponents\n // can fire the \"update\" event asynchronously, which would otherwise\n // overwrite the agent's fresh change with the editor's normalized version.\n loadingRef.current = true;\n if (saveTimer.current) clearTimeout(saveTimer.current);\n editor.setComponents(mjml || STARTER_MJML);\n editor.getWrapper()?.find(\"mj-section\").forEach(decorate);\n for (const t of Object.keys(TYPE_LABEL)) {\n editor.getWrapper()?.find(t).forEach(decorate);\n }\n setTimeout(() => {\n loadingRef.current = false;\n }, 400);\n };\n\n // Highlight for the section the agent is editing. The canvas is an iframe with its\n // own document, so the host's stylesheet does not reach it and these rules have to be\n // injected. The colour is still read from the shared token rather than written out\n // again — the literal that used to live here was the pre-redesign violet and had\n // silently drifted from the brand for as long as the redesign had been in place.\n const ensureHighlightStyles = () => {\n const cdoc = editor.Canvas.getDocument();\n if (!cdoc || cdoc.getElementById(\"agent-edit-style\")) return;\n\n const accent =\n getComputedStyle(document.documentElement)\n .getPropertyValue(\"--mjml-editor-accent\")\n .trim() || \"#7c5cfc\";\n\n const style = cdoc.createElement(\"style\");\n style.id = \"agent-edit-style\";\n style.textContent = `\n @keyframes agentEditPulse {\n 0%, 100% { outline-color: color-mix(in srgb, ${accent} 25%, transparent); }\n 50% { outline-color: color-mix(in srgb, ${accent} 95%, transparent); }\n }\n .agent-editing {\n outline: 3px solid color-mix(in srgb, ${accent} 90%, transparent) !important;\n outline-offset: -3px;\n animation: agentEditPulse 1s ease-in-out infinite;\n transition: outline-color 0.2s;\n }`;\n cdoc.head.appendChild(style);\n };\n\n const sectionEl = (sectionId: string): HTMLElement | undefined => {\n const comp = editor\n .getWrapper()\n ?.find(\"mj-section\")\n .find((c) => sectionIdOf(c) === sectionId);\n return (comp?.getEl?.() as HTMLElement | undefined) ?? undefined;\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const compiledHtml = (): string => {\n try {\n // grapesjs-mjml compiles MJML → email HTML via this command ({ html, errors }).\n const res = editor.runCommand(\"mjml-code-to-html\") as { html?: string } | undefined;\n return res?.html ?? editor.getHtml();\n } catch {\n return editor.getHtml();\n }\n };\n\n try {\n const doc = await documents.get(docId);\n loadMjml(doc.mjml);\n } catch (e) {\n toast.error(\"Failed to load the document.\");\n console.error(e);\n } finally {\n setLoading(false);\n }\n\n onReady({\n getMjml: () => editor.getHtml(),\n getCompiledHtml: compiledHtml,\n flushSave: async () => {\n if (saveTimer.current) clearTimeout(saveTimer.current);\n await save();\n },\n reloadFromDb: async () => {\n const fresh = await documents.get(docId);\n if (fresh.mjml !== editor.getHtml()) loadMjml(fresh.mjml);\n },\n highlightSection: (sectionId, on) => {\n ensureHighlightStyles();\n const el = sectionEl(sectionId);\n if (!el) return;\n el.classList.toggle(\"agent-editing\", on);\n },\n getDevices: () =>\n editor.Devices.getDevices().map((d) => ({\n id: String(d.id),\n name: d.getName() || String(d.id),\n })),\n setDevice: (name) => {\n editor.setDevice(name);\n notifyState();\n },\n undo: () => {\n editor.UndoManager.undo();\n notifyState();\n },\n redo: () => {\n editor.UndoManager.redo();\n notifyState();\n },\n setZoom: (z) => {\n editor.Canvas.setZoom?.(Math.max(25, Math.min(200, z)));\n notifyState();\n },\n setContentWidth: (w) => {\n findByTag(\"mj-body\")?.addAttributes({ width: w });\n notifyState();\n },\n onEditorState: (cb) => {\n stateListeners.current.add(cb);\n cb(snapshot());\n return () => stateListeners.current.delete(cb);\n },\n });\n };\n\n return (\n <GjsEditor\n grapesjs={grapesjs}\n className=\"h-full\"\n options={{\n height: \"100%\",\n storageManager: false,\n fromElement: false,\n panels: { defaults: [] },\n blockManager: { appendTo: \"#gjs-blocks\" },\n styleManager: { appendTo: \"#gjs-styles\" },\n traitManager: { appendTo: \"#gjs-traits\" },\n layerManager: { appendTo: \"#gjs-layers\" },\n deviceManager: {\n devices: [\n { id: \"desktop\", name: \"Desktop\", width: \"\" },\n { id: \"mobile\", name: \"Mobile\", width: \"375px\", widthMedia: \"480px\" },\n ],\n },\n plugins: [grapesjsMJML],\n }}\n onEditor={onEditor}\n >\n <div className=\"flex h-full min-h-0 flex-1\">\n <LeftSidebar\n view={sidebarView}\n onViewChange={setSidebarView}\n markLayersSource={() => (selectionSource.current = \"layers\")}\n />\n <div className=\"flex min-w-0 flex-1 flex-col bg-surface-muted\">\n <div className=\"relative min-h-0 flex-1\">\n <Canvas className=\"h-full\" />\n {/* Canvas comment layer (pins + thread popovers) overlays the canvas. */}\n <CanvasComments\n docId={docId}\n composeTarget={composeTarget}\n onComposeConsumed={() => setComposeTarget(null)}\n refreshSignal={commentsRefresh}\n onOpenCountChange={onOpenCountChange}\n />\n {loading && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500\">\n <Spinner /> Loading editor…\n </div>\n )}\n </div>\n <Breadcrumb crumbs={crumbs} onSelect={(c) => editorRef.current?.select(c)} />\n </div>\n </div>\n </GjsEditor>\n );\n}\n\ntype SidebarView = \"blocks\" | \"settings\" | \"layers\";\n\n// Collapsible section — content ALWAYS mounted (appendTo target), hidden via `hidden`.\nfunction CollapseSection({\n title,\n defaultOpen = true,\n children,\n}: {\n title: string;\n defaultOpen?: boolean;\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <div className=\"border-b border-panel-border\">\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n className=\"flex w-full items-center gap-1.5 px-3 py-2 text-xs font-semibold tracking-wide text-panel-muted-fg uppercase hover:text-panel-fg\"\n >\n <ChevronRight className={cn(\"size-3.5 transition-transform\", open && \"rotate-90\")} />\n {title}\n </button>\n <div className={cn(!open && \"hidden\")}>{children}</div>\n </div>\n );\n}\n\n// Left sidebar (dark): Blocks / Settings / Layers. All managers are always\n// mounted (appendTo targets for GrapesJS) — visibility toggled via `hidden`.\nfunction LeftSidebar({\n view,\n onViewChange,\n markLayersSource,\n}: {\n view: SidebarView;\n onViewChange: (v: SidebarView) => void;\n markLayersSource: () => void;\n}) {\n const editor = useEditorMaybe();\n const [layersExpanded, setLayersExpanded] = useState(false);\n const [blockSearch, setBlockSearch] = useState(\"\");\n\n const changeView = (v: SidebarView) => {\n if (v === \"settings\" && editor && !editor.getSelected()) {\n const wrapper = editor.getWrapper();\n const body = wrapper?.find(\"mj-body\")[0] ?? wrapper;\n if (body) editor.select(body);\n }\n onViewChange(v);\n };\n\n const toggleLayers = (expand: boolean) => {\n setLayersExpanded(expand);\n editor?.getWrapper()?.onAll((c) => c.set(\"open\", expand));\n };\n\n // Filter the GrapesJS-rendered blocks (DOM lives outside React) by label;\n // hide category groups that end up empty.\n const filterBlocks = (q: string) => {\n setBlockSearch(q);\n const root = document.getElementById(\"gjs-blocks\");\n if (!root) return;\n const term = q.trim().toLowerCase();\n root.querySelectorAll<HTMLElement>(\".gjs-block\").forEach((b) => {\n const label = (b.querySelector(\".gjs-block-label\")?.textContent ?? b.textContent ?? \"\")\n .toLowerCase()\n .trim();\n b.style.display = !term || label.includes(term) ? \"\" : \"none\";\n });\n root.querySelectorAll<HTMLElement>(\".gjs-block-category\").forEach((cat) => {\n const anyVisible = [...cat.querySelectorAll<HTMLElement>(\".gjs-block\")].some(\n (b) => b.style.display !== \"none\",\n );\n cat.style.display = anyVisible ? \"\" : \"none\";\n });\n };\n\n return (\n <div className=\"editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg\">\n <div className=\"flex border-b border-panel-border\">\n {(\n [\n [\"blocks\", \"Blocks\"],\n [\"settings\", \"Settings\"],\n [\"layers\", \"Layers\"],\n ] as const\n ).map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => changeView(value)}\n className={cn(\n \"relative flex-1 px-3 py-2.5 text-sm transition-colors\",\n \"after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors\",\n view === value\n ? \"font-medium text-panel-fg after:bg-brand\"\n : \"text-panel-muted-fg after:bg-transparent hover:text-panel-fg\",\n )}\n >\n {label}\n </button>\n ))}\n </div>\n\n {/* Blocks: search + categorized block palette */}\n <div className={cn(\"flex min-h-0 flex-1 flex-col\", view !== \"blocks\" && \"hidden\")}>\n <div className=\"relative border-b border-panel-border p-2\">\n <Search className=\"pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg\" />\n <Input\n value={blockSearch}\n onChange={(e) => filterBlocks(e.target.value)}\n placeholder=\"Search blocks…\"\n className=\"h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg\"\n />\n <kbd className=\"pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 rounded border border-panel-border bg-panel px-1 py-0.5 font-mono text-[10px] text-panel-muted-fg\">\n ⌘K\n </kbd>\n </div>\n <div id=\"gjs-blocks\" className=\"min-h-0 flex-1 overflow-y-auto\" />\n </div>\n\n {/* Settings: Attributes + Style (collapsible, stacked) */}\n <div className={cn(\"min-h-0 flex-1 overflow-y-auto\", view !== \"settings\" && \"hidden\")}>\n <CollapseSection title=\"Attributes\">\n <div id=\"gjs-traits\" />\n </CollapseSection>\n <CollapseSection title=\"Style\">\n <div id=\"gjs-styles\" />\n </CollapseSection>\n </div>\n\n {/* Layers — with an expand/collapse-all switch. A pointer-down here marks\n the next selection as coming from the tree (→ do NOT open Settings). */}\n <div\n className={cn(\"flex min-h-0 flex-1 flex-col\", view !== \"layers\" && \"hidden\")}\n onMouseDownCapture={markLayersSource}\n >\n <label className=\"flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg\">\n <span>Expand all</span>\n <Switch\n checked={layersExpanded}\n onCheckedChange={toggleLayers}\n className=\"data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border\"\n />\n </label>\n <div id=\"gjs-layers\" className=\"min-h-0 flex-1 overflow-y-auto\" />\n </div>\n </div>\n );\n}\n\n// Breadcrumb under the canvas: the selected component's ancestry (clickable).\nfunction Breadcrumb({\n crumbs,\n onSelect,\n}: {\n crumbs: Component[];\n onSelect: (c: Component) => void;\n}) {\n return (\n <div className=\"flex h-9 shrink-0 items-center gap-0.5 border-t border-border bg-surface px-3 text-xs text-muted-foreground\">\n {crumbs.length === 0 ? (\n <span className=\"text-muted-foreground/60\">Select an element…</span>\n ) : (\n crumbs.map((c, i) => (\n <span key={i} className=\"flex items-center gap-0.5\">\n {i > 0 && <ChevronRight className=\"size-3 opacity-40\" />}\n <button\n type=\"button\"\n onClick={() => onSelect(c)}\n className={cn(\n \"rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground\",\n i === crumbs.length - 1 && \"font-medium text-brand\",\n )}\n >\n {crumbName(c)}\n </button>\n </span>\n ))\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Switch as SwitchPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Switch({\n className,\n size = \"default\",\n ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root> & {\n size?: \"sm\" | \"default\";\n}) {\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n data-size={size}\n className={cn(\n \"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground\"\n />\n </SwitchPrimitive.Root>\n );\n}\n\nexport { Switch };\n"],"mappings":"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","names":["jsx"]}
1
+ {"version":3,"sources":["../src/components/editor/EmailEditor.tsx","../src/components/ui/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport grapesjs, { Component, Editor } from \"grapesjs\";\nimport GjsEditor, { Canvas, useEditorMaybe } from \"@grapesjs/react\";\nimport grapesjsMJML from \"grapesjs-mjml\";\nimport { useRef, useState, type ReactNode } from \"react\";\n\nimport { STARTER_MJML } from \"@mjml-agent-editor/core\";\nimport { useDocumentStore, type CommentTarget } from \"../../index.js\";\nimport { cn } from \"../../lib/utils\";\nimport { toast } from \"sonner\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { Spinner } from \"../ui/spinner\";\nimport { Switch } from \"../ui/switch\";\nimport { Input } from \"../ui/input\";\nimport { Search } from \"lucide-react\";\nimport CanvasComments from \"../comments/CanvasComments.js\";\n\nexport type SaveStatus = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\n/** Snapshot of editor state pushed to the header (device, undo, save, zoom, width). */\nexport type EditorState = {\n device: string;\n canUndo: boolean;\n canRedo: boolean;\n saveStatus: SaveStatus;\n zoom: number;\n contentWidth: string;\n};\n\nexport type EditorApi = {\n /** Current MJML source from the editor. */\n getMjml: () => string;\n /** Compiled email HTML (via grapesjs-mjml's mjml-get-code). */\n getCompiledHtml: () => string;\n /** Immediately saves the editor state to the DB. */\n flushSave: () => Promise<void>;\n /** Reloads the document from the DB (after agent changes). */\n reloadFromDb: () => Promise<void>;\n /** Highlights a section in the canvas while the agent edits it. */\n highlightSection: (sectionId: string, on: boolean) => void;\n /** Device manager control (used by the top header). */\n getDevices: () => { id: string; name: string }[];\n setDevice: (name: string) => void;\n undo: () => void;\n redo: () => void;\n /** Canvas zoom (percentage). */\n setZoom: (z: number) => void;\n /** Email body width (mj-body width attribute, e.g. \"600px\"). */\n setContentWidth: (w: string) => void;\n /** Subscribe to editor state changes (device/undo/save/zoom/width). Returns unsubscribe. */\n onEditorState: (cb: (s: EditorState) => void) => () => void;\n};\n\ntype Props = {\n docId: string;\n onReady: (api: EditorApi) => void;\n /** Bump to force the canvas comment layer to refetch (after agent turns). */\n commentsRefresh: number;\n /** Reports the number of open comments (for the header indicator). */\n onOpenCountChange: (n: number) => void;\n};\n\nconst SEC_ID_RE = /\\bsec-([A-Za-z0-9_-]+)\\b/;\nconst OBJ_ID_RE = /\\bobj-([A-Za-z0-9_-]+)\\b/;\n\n// Element types that can receive a comment (besides a section).\nconst TYPE_LABEL: Record<string, string> = {\n \"mj-text\": \"Text\",\n \"mj-button\": \"Button\",\n \"mj-image\": \"Image\",\n \"mj-column\": \"Column\",\n};\n\nfunction tagOf(c: Component): string {\n return String(c.get(\"tagName\") || c.get(\"type\") || \"\");\n}\n\n// \"Sparkles\" icon (lucide Sparkles) used as a label in the GrapesJS toolbar.\nconst SPARKLES_SVG =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"/><path d=\"M20 3v4\"/><path d=\"M22 5h-4\"/><path d=\"M4 17v2\"/><path d=\"M5 18H3\"/></svg>';\n\nfunction isSection(c: Component): boolean {\n return tagOf(c) === \"mj-section\";\n}\n\nfunction isCommentable(c: Component): boolean {\n const t = tagOf(c);\n return t === \"mj-section\" || t in TYPE_LABEL;\n}\n\nfunction classMatch(c: Component, re: RegExp): string | null {\n const cssClass = String(c.getAttributes()[\"css-class\"] ?? \"\");\n return cssClass.match(re)?.[1] ?? null;\n}\n\nfunction sectionIdOf(c: Component): string | null {\n return classMatch(c, SEC_ID_RE);\n}\n\nfunction addIdClass(c: Component, prefix: \"sec\" | \"obj\"): string {\n const existing = classMatch(c, prefix === \"sec\" ? SEC_ID_RE : OBJ_ID_RE);\n if (existing) return existing;\n const cssClass = String(c.getAttributes()[\"css-class\"] ?? \"\");\n const id = Math.random().toString(36).slice(2, 10);\n c.addAttributes({ \"css-class\": `${cssClass} ${prefix}-${id}`.trim() });\n return id;\n}\n\nfunction closestSection(c: Component | undefined): Component | null {\n let cur: Component | undefined = c;\n while (cur) {\n if (isSection(cur)) return cur;\n cur = cur.parent() ?? undefined;\n }\n return null;\n}\n\n// Friendly names for the canvas breadcrumb.\nconst CRUMB_NAME: Record<string, string> = {\n \"mj-section\": \"Section\",\n \"mj-column\": \"Column\",\n \"mj-group\": \"Group\",\n \"mj-text\": \"Text\",\n \"mj-button\": \"Button\",\n \"mj-image\": \"Image\",\n \"mj-divider\": \"Divider\",\n \"mj-spacer\": \"Spacer\",\n \"mj-hero\": \"Hero\",\n \"mj-navbar\": \"Navbar\",\n \"mj-social\": \"Social\",\n \"mj-social-element\": \"Social\",\n \"mj-wrapper\": \"Wrapper\",\n};\n\nfunction crumbName(c: Component): string {\n const t = tagOf(c);\n return CRUMB_NAME[t] ?? t.replace(/^mj-/, \"\") ?? \"Element\";\n}\n\nfunction objectLabel(c: Component): string {\n const base = TYPE_LABEL[tagOf(c)] ?? tagOf(c);\n const text = String((c.getEl?.() as HTMLElement | undefined)?.textContent ?? \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 30);\n return text ? `${base}: “${text}”` : base;\n}\n\nconst RTE_FONTS = [\n \"Arial\",\n \"Helvetica\",\n \"Georgia\",\n \"Times New Roman\",\n \"Verdana\",\n \"Tahoma\",\n \"Trebuchet MS\",\n \"Courier New\",\n];\nconst RTE_SIZES = [11, 12, 13, 14, 16, 18, 20, 24, 28, 32, 40];\n\n// Wraps the current selection (in the canvas document) in a <span> with inline style.\nfunction wrapSelectionStyle(el: HTMLElement | undefined, style: Partial<CSSStyleDeclaration>) {\n const doc = el?.ownerDocument;\n const sel = doc?.getSelection?.();\n if (!doc || !sel || sel.rangeCount === 0 || sel.isCollapsed) return;\n const range = sel.getRangeAt(0);\n const span = doc.createElement(\"span\");\n Object.assign(span.style, style);\n try {\n span.appendChild(range.extractContents());\n range.insertNode(span);\n sel.removeAllRanges();\n const nr = doc.createRange();\n nr.selectNodeContents(span);\n sel.addRange(nr);\n } catch {\n /* ignore */\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Extends the default GrapesJS RTE (mjml-compatible) with font/size/colors.\nfunction setupRichText(editor: Editor) {\n const rte = editor.RichTextEditor as any;\n if (!rte || rte.__customActions) return;\n rte.__customActions = true;\n const val = (action: any, sel: string) =>\n (action.btn?.querySelector(sel) as HTMLInputElement | HTMLSelectElement | null)?.value ?? \"\";\n\n rte.add(\"fontName\", {\n icon: `<select class=\"gjs-rte-select\" title=\"Font\"><option value=\"\">Font</option>${RTE_FONTS.map(\n (f) => `<option value=\"${f}\">${f}</option>`,\n ).join(\"\")}</select>`,\n event: \"change\",\n result: (r: any, action: any) => {\n const v = val(action, \"select\");\n if (v) r.exec(\"fontName\", v);\n },\n });\n\n rte.add(\"fontSize\", {\n icon: `<select class=\"gjs-rte-select\" title=\"Size\"><option value=\"\">Size</option>${RTE_SIZES.map(\n (s) => `<option value=\"${s}px\">${s}</option>`,\n ).join(\"\")}</select>`,\n event: \"change\",\n result: (r: any, action: any) => {\n const v = val(action, \"select\");\n if (v) wrapSelectionStyle(r.el, { fontSize: v });\n },\n });\n\n rte.add(\"forecolor\", {\n icon: `<input type=\"color\" class=\"gjs-rte-color\" title=\"Text color\" value=\"#000000\">`,\n event: \"change\",\n result: (r: any, action: any) => r.exec(\"foreColor\", val(action, \"input\")),\n });\n\n rte.add(\"hilitecolor\", {\n icon: `<input type=\"color\" class=\"gjs-rte-color\" title=\"Background color\" value=\"#ffff00\">`,\n event: \"change\",\n result: (r: any, action: any) => r.exec(\"hiliteColor\", val(action, \"input\")),\n });\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nexport default function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }: Props) {\n // Injected by the host rather than imported, so the editor carries no database.\n const documents = useDocumentStore();\n // Refs instead of state — GrapesJS lives outside React's lifecycle.\n const loadingRef = useRef(false);\n const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const saveStatusRef = useRef<SaveStatus>(\"idle\");\n const stateListeners = useRef(new Set<(s: EditorState) => void>());\n const [loading, setLoading] = useState(true);\n const [sidebarView, setSidebarView] = useState<SidebarView>(\"blocks\");\n // Comment being composed (sparkles button) — opens the canvas popover.\n const [composeTarget, setComposeTarget] = useState<CommentTarget | null>(null);\n // Where the current selection came from — canvas click opens Settings,\n // selecting from the Layers tree does not (keeps you on Layers).\n const selectionSource = useRef<\"canvas\" | \"layers\" | \"other\">(\"other\");\n // Breadcrumb of the selected component's ancestry (Section › Column › …).\n const editorRef = useRef<Editor | null>(null);\n const [crumbs, setCrumbs] = useState<Component[]>([]);\n\n const onEditor = async (editor: Editor) => {\n editorRef.current = editor;\n\n // Build the breadcrumb chain from a component up to (but excluding) mj-body.\n const buildCrumbs = (c: Component | undefined) => {\n const chain: Component[] = [];\n let cur: Component | undefined = c;\n while (cur) {\n const t = tagOf(cur);\n if (!t || t === \"mj-body\" || t === \"wrapper\" || t === \"Wrapper\") break;\n chain.unshift(cur);\n cur = cur.parent() ?? undefined;\n }\n setCrumbs(chain);\n };\n\n // GrapesJS find() by MJML tag is unreliable — walk the tree instead.\n const findByTag = (tag: string): Component | undefined => {\n let found: Component | undefined;\n editor.getWrapper()?.onAll((c) => {\n if (!found && (c.get(\"tagName\") === tag || c.get(\"type\") === tag)) found = c;\n });\n return found;\n };\n const bodyWidth = (): string => {\n const body = findByTag(\"mj-body\");\n return String((body?.getAttributes?.() as Record<string, unknown>)?.width ?? \"600px\");\n };\n const snapshot = (): EditorState => ({\n device: editor.getDevice(),\n canUndo: editor.UndoManager.hasUndo(),\n canRedo: editor.UndoManager.hasRedo(),\n saveStatus: saveStatusRef.current,\n zoom: Math.round((editor.Canvas.getZoom?.() as number | undefined) ?? 100),\n contentWidth: bodyWidth(),\n });\n const notifyState = () => {\n const s = snapshot();\n stateListeners.current.forEach((l) => l(s));\n };\n const setSave = (s: SaveStatus) => {\n saveStatusRef.current = s;\n notifyState();\n };\n\n // Comment target for a component: its section + (if it's not the section\n // itself) the specific element. Assigns stable IDs (sec-/obj-) when needed.\n const targetOf = (c: Component | undefined): CommentTarget | null => {\n const section = closestSection(c);\n if (!section || !c) return null;\n const sectionId = addIdClass(section, \"sec\");\n if (isSection(c)) {\n return { sectionId, objectId: null, objectLabel: null };\n }\n const objectId = addIdClass(c, \"obj\");\n return { sectionId, objectId, objectLabel: objectLabel(c) };\n };\n\n // Gives the section a sec-<id> and adds a comment button to the toolbar of\n // every commentable element (section + text/button/image/column).\n const decorate = (c: Component) => {\n if (isSection(c)) addIdClass(c, \"sec\");\n if (!isCommentable(c)) return;\n const toolbar = [...((c.get(\"toolbar\") as { command?: string }[]) ?? [])];\n if (!toolbar.some((t) => t.command === \"open-comments\")) {\n toolbar.push({\n command: \"open-comments\",\n label: SPARKLES_SVG,\n attributes: { title: \"Comment on element / section\", \"data-role\": \"sparkles\" },\n } as never);\n c.set(\"toolbar\", toolbar as never);\n }\n };\n\n editor.Commands.add(\"open-comments\", {\n run(ed: Editor) {\n const target = targetOf(ed.getSelected() ?? undefined);\n if (target) setComposeTarget(target);\n },\n });\n\n setupRichText(editor);\n\n // Group the palette blocks into categories (the plugin ships them flat).\n const blockCategory = (label: string): string => {\n const l = label.toLowerCase();\n if (/(column|section)/.test(l)) return \"Layout\";\n if (/(text|image|button|divider|spacer|social)/.test(l)) return \"Basic\";\n return \"Advanced\";\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const bm = editor.BlockManager as any;\n bm.getAll().forEach(\n (b: { get: (k: string) => string; set: (k: string, v: unknown) => void }) => {\n b.set(\"category\", blockCategory(b.get(\"label\") || \"\"));\n },\n );\n bm.render();\n\n editor.on(\"component:add\", decorate);\n // Track selection origin: a mousedown inside the canvas iframe marks the\n // next selection as coming from the canvas (→ open Settings). Selecting via\n // the Layers tree leaves the source as \"layers\" (set by the sidebar).\n editor.on(\"load\", () => {\n const body = editor.Canvas.getBody();\n body?.addEventListener(\n \"mousedown\",\n () => {\n selectionSource.current = \"canvas\";\n },\n true,\n );\n });\n editor.on(\"component:selected\", (c: Component) => {\n if (selectionSource.current === \"canvas\") setSidebarView(\"settings\");\n selectionSource.current = \"other\";\n buildCrumbs(c);\n // mj-image has no src trait by default (src changes via the Asset\n // Manager) — add a URL field so the image can be set from the panel.\n if (c?.get(\"type\") === \"mj-image\" && !c.getTrait(\"src\")) {\n c.addTrait(\n {\n type: \"text\",\n name: \"src\",\n label: \"Image (URL)\",\n placeholder: \"https://…\",\n changeProp: true,\n } as never,\n { at: 0 },\n );\n }\n });\n editor.on(\"component:deselected\", () => setCrumbs([]));\n\n const save = async () => {\n if (loadingRef.current) return;\n setSave(\"saving\");\n try {\n await documents.save(docId, {\n mjml: editor.getHtml(),\n projectData: editor.getProjectData(),\n });\n setSave(\"saved\");\n } catch (e) {\n setSave(\"error\");\n toast.error(\"Failed to save changes.\");\n throw e;\n }\n };\n\n editor.on(\"update\", () => {\n notifyState();\n if (loadingRef.current) return;\n if (saveTimer.current) clearTimeout(saveTimer.current);\n saveTimer.current = setTimeout(() => void save().catch(console.error), 1200);\n });\n editor.on(\"change:device\", notifyState);\n\n const loadMjml = (mjml: string) => {\n // Mute autosave during loading and for a moment after — setComponents\n // can fire the \"update\" event asynchronously, which would otherwise\n // overwrite the agent's fresh change with the editor's normalized version.\n loadingRef.current = true;\n if (saveTimer.current) clearTimeout(saveTimer.current);\n editor.setComponents(mjml || STARTER_MJML);\n editor.getWrapper()?.find(\"mj-section\").forEach(decorate);\n for (const t of Object.keys(TYPE_LABEL)) {\n editor.getWrapper()?.find(t).forEach(decorate);\n }\n setTimeout(() => {\n loadingRef.current = false;\n }, 400);\n };\n\n // Highlight for the section the agent is editing. The canvas is an iframe with its\n // own document, so the host's stylesheet does not reach it and these rules have to be\n // injected. The colour is still read from the shared token rather than written out\n // again — the literal that used to live here was the pre-redesign violet and had\n // silently drifted from the brand for as long as the redesign had been in place.\n const ensureHighlightStyles = () => {\n const cdoc = editor.Canvas.getDocument();\n if (!cdoc || cdoc.getElementById(\"agent-edit-style\")) return;\n\n const accent =\n getComputedStyle(document.documentElement)\n .getPropertyValue(\"--mjml-editor-accent\")\n .trim() || \"#7c5cfc\";\n\n const style = cdoc.createElement(\"style\");\n style.id = \"agent-edit-style\";\n style.textContent = `\n @keyframes agentEditPulse {\n 0%, 100% { outline-color: color-mix(in srgb, ${accent} 25%, transparent); }\n 50% { outline-color: color-mix(in srgb, ${accent} 95%, transparent); }\n }\n .agent-editing {\n outline: 3px solid color-mix(in srgb, ${accent} 90%, transparent) !important;\n outline-offset: -3px;\n animation: agentEditPulse 1s ease-in-out infinite;\n transition: outline-color 0.2s;\n }`;\n cdoc.head.appendChild(style);\n };\n\n const sectionEl = (sectionId: string): HTMLElement | undefined => {\n const comp = editor\n .getWrapper()\n ?.find(\"mj-section\")\n .find((c) => sectionIdOf(c) === sectionId);\n return (comp?.getEl?.() as HTMLElement | undefined) ?? undefined;\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const compiledHtml = (): string => {\n try {\n // grapesjs-mjml compiles MJML → email HTML via this command ({ html, errors }).\n const res = editor.runCommand(\"mjml-code-to-html\") as { html?: string } | undefined;\n return res?.html ?? editor.getHtml();\n } catch {\n return editor.getHtml();\n }\n };\n\n try {\n const doc = await documents.get(docId);\n loadMjml(doc.mjml);\n } catch (e) {\n toast.error(\"Failed to load the document.\");\n console.error(e);\n } finally {\n setLoading(false);\n }\n\n onReady({\n getMjml: () => editor.getHtml(),\n getCompiledHtml: compiledHtml,\n flushSave: async () => {\n if (saveTimer.current) clearTimeout(saveTimer.current);\n await save();\n },\n reloadFromDb: async () => {\n const fresh = await documents.get(docId);\n if (fresh.mjml !== editor.getHtml()) loadMjml(fresh.mjml);\n },\n highlightSection: (sectionId, on) => {\n ensureHighlightStyles();\n const el = sectionEl(sectionId);\n if (!el) return;\n el.classList.toggle(\"agent-editing\", on);\n },\n getDevices: () =>\n editor.Devices.getDevices().map((d) => ({\n id: String(d.id),\n name: d.getName() || String(d.id),\n })),\n setDevice: (name) => {\n editor.setDevice(name);\n notifyState();\n },\n undo: () => {\n editor.UndoManager.undo();\n notifyState();\n },\n redo: () => {\n editor.UndoManager.redo();\n notifyState();\n },\n setZoom: (z) => {\n editor.Canvas.setZoom?.(Math.max(25, Math.min(200, z)));\n notifyState();\n },\n setContentWidth: (w) => {\n findByTag(\"mj-body\")?.addAttributes({ width: w });\n notifyState();\n },\n onEditorState: (cb) => {\n stateListeners.current.add(cb);\n cb(snapshot());\n return () => stateListeners.current.delete(cb);\n },\n });\n };\n\n return (\n <GjsEditor\n grapesjs={grapesjs}\n className=\"h-full\"\n options={{\n height: \"100%\",\n storageManager: false,\n fromElement: false,\n panels: { defaults: [] },\n blockManager: { appendTo: \"#gjs-blocks\" },\n styleManager: { appendTo: \"#gjs-styles\" },\n traitManager: { appendTo: \"#gjs-traits\" },\n layerManager: { appendTo: \"#gjs-layers\" },\n deviceManager: {\n devices: [\n { id: \"desktop\", name: \"Desktop\", width: \"\" },\n { id: \"mobile\", name: \"Mobile\", width: \"375px\", widthMedia: \"480px\" },\n ],\n },\n plugins: [grapesjsMJML],\n }}\n onEditor={onEditor}\n >\n <div className=\"flex h-full min-h-0 flex-1\">\n <LeftSidebar\n view={sidebarView}\n onViewChange={setSidebarView}\n markLayersSource={() => (selectionSource.current = \"layers\")}\n />\n <div className=\"flex min-w-0 flex-1 flex-col bg-surface-muted\">\n <div className=\"relative min-h-0 flex-1\">\n <Canvas className=\"h-full\" />\n {/* Canvas comment layer (pins + thread popovers) overlays the canvas. */}\n <CanvasComments\n docId={docId}\n composeTarget={composeTarget}\n onComposeConsumed={() => setComposeTarget(null)}\n refreshSignal={commentsRefresh}\n onOpenCountChange={onOpenCountChange}\n />\n {loading && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500\">\n <Spinner /> Loading editor…\n </div>\n )}\n </div>\n <Breadcrumb crumbs={crumbs} onSelect={(c) => editorRef.current?.select(c)} />\n </div>\n </div>\n </GjsEditor>\n );\n}\n\ntype SidebarView = \"blocks\" | \"settings\" | \"layers\";\n\n// Collapsible section — content ALWAYS mounted (appendTo target), hidden via `hidden`.\nfunction CollapseSection({\n title,\n defaultOpen = true,\n children,\n}: {\n title: string;\n defaultOpen?: boolean;\n children: ReactNode;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <div className=\"border-b border-panel-border\">\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n className=\"flex w-full items-center gap-1.5 px-3 py-2 text-xs font-semibold tracking-wide text-panel-muted-fg uppercase hover:text-panel-fg\"\n >\n <ChevronRight className={cn(\"size-3.5 transition-transform\", open && \"rotate-90\")} />\n {title}\n </button>\n <div className={cn(!open && \"hidden\")}>{children}</div>\n </div>\n );\n}\n\n// Left sidebar (dark): Blocks / Settings / Layers. All managers are always\n// mounted (appendTo targets for GrapesJS) — visibility toggled via `hidden`.\nfunction LeftSidebar({\n view,\n onViewChange,\n markLayersSource,\n}: {\n view: SidebarView;\n onViewChange: (v: SidebarView) => void;\n markLayersSource: () => void;\n}) {\n const editor = useEditorMaybe();\n const [layersExpanded, setLayersExpanded] = useState(false);\n const [blockSearch, setBlockSearch] = useState(\"\");\n\n const changeView = (v: SidebarView) => {\n if (v === \"settings\" && editor && !editor.getSelected()) {\n const wrapper = editor.getWrapper();\n const body = wrapper?.find(\"mj-body\")[0] ?? wrapper;\n if (body) editor.select(body);\n }\n onViewChange(v);\n };\n\n const toggleLayers = (expand: boolean) => {\n setLayersExpanded(expand);\n editor?.getWrapper()?.onAll((c) => c.set(\"open\", expand));\n };\n\n // Filter the GrapesJS-rendered blocks (DOM lives outside React) by label;\n // hide category groups that end up empty.\n const filterBlocks = (q: string) => {\n setBlockSearch(q);\n const root = document.getElementById(\"gjs-blocks\");\n if (!root) return;\n const term = q.trim().toLowerCase();\n root.querySelectorAll<HTMLElement>(\".gjs-block\").forEach((b) => {\n const label = (b.querySelector(\".gjs-block-label\")?.textContent ?? b.textContent ?? \"\")\n .toLowerCase()\n .trim();\n b.style.display = !term || label.includes(term) ? \"\" : \"none\";\n });\n root.querySelectorAll<HTMLElement>(\".gjs-block-category\").forEach((cat) => {\n const anyVisible = [...cat.querySelectorAll<HTMLElement>(\".gjs-block\")].some(\n (b) => b.style.display !== \"none\",\n );\n cat.style.display = anyVisible ? \"\" : \"none\";\n });\n };\n\n return (\n <div className=\"editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg\">\n <div className=\"flex border-b border-panel-border\">\n {(\n [\n [\"blocks\", \"Blocks\"],\n [\"settings\", \"Settings\"],\n [\"layers\", \"Layers\"],\n ] as const\n ).map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => changeView(value)}\n className={cn(\n \"relative flex-1 px-3 py-2.5 text-sm transition-colors\",\n \"after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors\",\n view === value\n ? \"font-medium text-panel-fg after:bg-brand\"\n : \"text-panel-muted-fg after:bg-transparent hover:text-panel-fg\",\n )}\n >\n {label}\n </button>\n ))}\n </div>\n\n {/* Blocks: search + categorized block palette */}\n <div className={cn(\"flex min-h-0 flex-1 flex-col\", view !== \"blocks\" && \"hidden\")}>\n <div className=\"relative border-b border-panel-border p-2\">\n <Search className=\"pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg\" />\n <Input\n value={blockSearch}\n onChange={(e) => filterBlocks(e.target.value)}\n placeholder=\"Search blocks…\"\n className=\"h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg\"\n />\n <kbd className=\"pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 rounded border border-panel-border bg-panel px-1 py-0.5 font-mono text-[10px] text-panel-muted-fg\">\n ⌘K\n </kbd>\n </div>\n <div id=\"gjs-blocks\" className=\"min-h-0 flex-1 overflow-y-auto\" />\n </div>\n\n {/* Settings: Attributes + Style (collapsible, stacked) */}\n <div className={cn(\"min-h-0 flex-1 overflow-y-auto\", view !== \"settings\" && \"hidden\")}>\n <CollapseSection title=\"Attributes\">\n <div id=\"gjs-traits\" />\n </CollapseSection>\n <CollapseSection title=\"Style\">\n <div id=\"gjs-styles\" />\n </CollapseSection>\n </div>\n\n {/* Layers — with an expand/collapse-all switch. A pointer-down here marks\n the next selection as coming from the tree (→ do NOT open Settings). */}\n <div\n className={cn(\"flex min-h-0 flex-1 flex-col\", view !== \"layers\" && \"hidden\")}\n onMouseDownCapture={markLayersSource}\n >\n <label className=\"flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg\">\n <span>Expand all</span>\n <Switch\n checked={layersExpanded}\n onCheckedChange={toggleLayers}\n className=\"data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border\"\n />\n </label>\n <div id=\"gjs-layers\" className=\"min-h-0 flex-1 overflow-y-auto\" />\n </div>\n </div>\n );\n}\n\n// Breadcrumb under the canvas: the selected component's ancestry (clickable).\nfunction Breadcrumb({\n crumbs,\n onSelect,\n}: {\n crumbs: Component[];\n onSelect: (c: Component) => void;\n}) {\n return (\n <div className=\"flex h-9 shrink-0 items-center gap-0.5 border-t border-border bg-surface px-3 text-xs text-muted-foreground\">\n {crumbs.length === 0 ? (\n <span className=\"text-muted-foreground/60\">Select an element…</span>\n ) : (\n crumbs.map((c, i) => (\n <span key={i} className=\"flex items-center gap-0.5\">\n {i > 0 && <ChevronRight className=\"size-3 opacity-40\" />}\n <button\n type=\"button\"\n onClick={() => onSelect(c)}\n className={cn(\n \"rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground\",\n i === crumbs.length - 1 && \"font-medium text-brand\",\n )}\n >\n {crumbName(c)}\n </button>\n </span>\n ))\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Switch as SwitchPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Switch({\n className,\n size = \"default\",\n ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root> & {\n size?: \"sm\" | \"default\";\n}) {\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n data-size={size}\n className={cn(\n \"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground\"\n />\n </SwitchPrimitive.Root>\n );\n}\n\nexport { Switch };\n"],"mappings":"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","names":["jsx"]}
@@ -43,20 +43,8 @@ function useCommentStore() {
43
43
  return useStores().comments;
44
44
  }
45
45
 
46
- // src/starter.ts
47
- var STARTER_MJML = `<mjml>
48
- <mj-body background-color="#f4f4f5">
49
- <mj-section background-color="#ffffff" padding="32px 24px">
50
- <mj-column>
51
- <mj-text font-size="22px" font-weight="bold">New email</mj-text>
52
- <mj-text color="#555555">
53
- Describe the email you need in the chat on the right \u2014 the agent
54
- will design the sections, content and images.
55
- </mj-text>
56
- </mj-column>
57
- </mj-section>
58
- </mj-body>
59
- </mjml>`;
46
+ // src/index.ts
47
+ import { STARTER_MJML } from "@mjml-agent-editor/core";
60
48
 
61
49
  // src/components/editor/EditorHeader.tsx
62
50
  import { useEffect, useState } from "react";
@@ -743,6 +731,7 @@ import { DefaultChatTransport } from "ai";
743
731
  import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
744
732
  import { toast as toast2 } from "sonner";
745
733
  import {
734
+ Brain,
746
735
  Check,
747
736
  Loader2,
748
737
  TriangleAlert,
@@ -1115,6 +1104,31 @@ var SECTION_ARG = {
1115
1104
  remove_section: "section_id",
1116
1105
  insert_section: "after_section_id"
1117
1106
  };
1107
+ function toBlocks(parts) {
1108
+ const blocks = [];
1109
+ for (const part of parts) {
1110
+ if (part.type.startsWith("tool-")) {
1111
+ const last = blocks[blocks.length - 1];
1112
+ if (last?.kind === "tools") last.tools.push(part);
1113
+ else blocks.push({ kind: "tools", tools: [part] });
1114
+ continue;
1115
+ }
1116
+ if (part.type === "reasoning") {
1117
+ const text = part.text;
1118
+ if (!text.trim()) continue;
1119
+ const last = blocks[blocks.length - 1];
1120
+ if (last?.kind === "reasoning")
1121
+ blocks[blocks.length - 1] = { kind: "reasoning", text: `${last.text}${text}` };
1122
+ else blocks.push({ kind: "reasoning", text });
1123
+ continue;
1124
+ }
1125
+ if (part.type === "text") {
1126
+ if (!part.text.trim()) continue;
1127
+ blocks.push({ kind: "text", text: part.text });
1128
+ }
1129
+ }
1130
+ return blocks;
1131
+ }
1118
1132
  function ToolMarker({ tool }) {
1119
1133
  const name = tool.type.slice(5);
1120
1134
  const done = tool.state === "output-available";
@@ -1140,7 +1154,7 @@ function ToolActivity({ tools }) {
1140
1154
  );
1141
1155
  const failedCount = finished.filter((t) => t.state === "output-error").length;
1142
1156
  return /* @__PURE__ */ jsxs4("div", { className: "space-y-1 rounded-lg border border-panel-border bg-panel-elevated/60 p-2", children: [
1143
- finished.length > 0 && /* @__PURE__ */ jsxs4(Collapsible, { defaultOpen: true, children: [
1157
+ finished.length > 0 && /* @__PURE__ */ jsxs4(Collapsible, { children: [
1144
1158
  /* @__PURE__ */ jsxs4(CollapsibleTrigger, { className: "group/col flex w-full items-center gap-1.5 text-left text-xs font-medium text-panel-fg hover:text-panel-fg", children: [
1145
1159
  /* @__PURE__ */ jsx14(ChevronRight, { className: "size-3.5 shrink-0 transition-transform group-data-[state=open]/col:rotate-90" }),
1146
1160
  /* @__PURE__ */ jsx14("span", { children: failedCount > 0 ? `Completed steps (${finished.length}, ${failedCount} failed)` : `Completed steps (${finished.length})` })
@@ -1150,9 +1164,20 @@ function ToolActivity({ tools }) {
1150
1164
  running.map((t, i) => /* @__PURE__ */ jsx14(ToolMarker, { tool: t }, `r${i}`))
1151
1165
  ] });
1152
1166
  }
1167
+ function ReasoningBlock({ text }) {
1168
+ return /* @__PURE__ */ jsx14("div", { className: "rounded-lg border border-panel-border bg-panel-elevated/60 p-2", children: /* @__PURE__ */ jsxs4(Collapsible, { children: [
1169
+ /* @__PURE__ */ jsxs4(CollapsibleTrigger, { className: "group/col flex w-full items-center gap-1.5 text-left text-xs font-medium text-panel-muted-fg hover:text-panel-fg", children: [
1170
+ /* @__PURE__ */ jsx14(ChevronRight, { className: "size-3.5 shrink-0 transition-transform group-data-[state=open]/col:rotate-90" }),
1171
+ /* @__PURE__ */ jsx14(Brain, { className: "size-3.5 shrink-0" }),
1172
+ /* @__PURE__ */ jsx14("span", { children: "Reasoning" })
1173
+ ] }),
1174
+ /* @__PURE__ */ jsx14(CollapsibleContent, { className: "mt-1.5 pl-1 text-xs whitespace-pre-wrap text-panel-muted-fg", children: text })
1175
+ ] }) });
1176
+ }
1153
1177
  function ChatPanel({
1154
1178
  docId,
1155
1179
  initialMessages,
1180
+ transport,
1156
1181
  onBeforeSend,
1157
1182
  onAgentFinish,
1158
1183
  onLiveUpdate,
@@ -1173,7 +1198,7 @@ function ChatPanel({
1173
1198
  };
1174
1199
  const { messages, sendMessage, status, stop } = useChat({
1175
1200
  messages: initialMessages,
1176
- transport: new DefaultChatTransport({
1201
+ transport: transport ?? new DefaultChatTransport({
1177
1202
  // Same-origin: the agent is a route handler in this app, not a separate service.
1178
1203
  api: "/api/chat",
1179
1204
  body: { docId }
@@ -1248,12 +1273,8 @@ function ChatPanel({
1248
1273
  ] }) }) : messages.map((message, mi) => {
1249
1274
  const isUser = message.role === "user";
1250
1275
  const isLast = mi === messages.length - 1;
1251
- const toolParts = message.parts.filter(
1252
- (p) => p.type.startsWith("tool-")
1253
- );
1254
- const textParts = message.parts.filter(
1255
- (p) => p.type === "text" && p.text.trim() !== ""
1256
- );
1276
+ const blocks = toBlocks(message.parts);
1277
+ const hasText = blocks.some((block) => block.kind === "text");
1257
1278
  return /* @__PURE__ */ jsx14(MessageScrollerItem, { messageId: message.id, children: /* @__PURE__ */ jsxs4("div", { className: cn("flex w-full gap-2", isUser && "flex-row-reverse"), children: [
1258
1279
  !isUser && /* @__PURE__ */ jsx14("span", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-brand/15 text-brand", children: /* @__PURE__ */ jsx14(Sparkles, { className: "size-3.5" }) }),
1259
1280
  /* @__PURE__ */ jsxs4(
@@ -1265,28 +1286,35 @@ function ChatPanel({
1265
1286
  ),
1266
1287
  children: [
1267
1288
  /* @__PURE__ */ jsx14(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs4(MessageContent, { children: [
1268
- toolParts.length > 0 && /* @__PURE__ */ jsx14(ToolActivity, { tools: toolParts }),
1269
- textParts.map((part, i) => /* @__PURE__ */ jsx14(
1270
- Bubble,
1271
- {
1272
- variant: isUser ? "default" : "muted",
1273
- align: isUser ? "end" : "start",
1274
- className: cn(
1275
- isUser ? "[&_[data-slot=bubble-content]]:bg-brand [&_[data-slot=bubble-content]]:text-brand-fg" : "[&_[data-slot=bubble-content]]:border [&_[data-slot=bubble-content]]:border-panel-border [&_[data-slot=bubble-content]]:bg-panel-elevated [&_[data-slot=bubble-content]]:text-panel-fg [&_code]:!bg-white/12 [&_pre]:!bg-white/10 [&_a]:text-brand"
1276
- ),
1277
- children: /* @__PURE__ */ jsx14(
1278
- BubbleContent,
1279
- {
1280
- className: isUser ? "whitespace-pre-wrap" : void 0,
1281
- children: isUser && part.type === "text" ? part.text : part.type === "text" ? /* @__PURE__ */ jsx14(Markdown, { children: part.text }) : null
1282
- }
1283
- )
1284
- },
1285
- i
1286
- )),
1287
- isLast && !isUser && busy && !toolRunning && textParts.length === 0 && /* @__PURE__ */ jsx14(BusyMarker, {})
1289
+ blocks.map((block, i) => {
1290
+ if (block.kind === "tools") {
1291
+ return /* @__PURE__ */ jsx14(ToolActivity, { tools: block.tools }, i);
1292
+ }
1293
+ if (block.kind === "reasoning") {
1294
+ return /* @__PURE__ */ jsx14(ReasoningBlock, { text: block.text }, i);
1295
+ }
1296
+ return /* @__PURE__ */ jsx14(
1297
+ Bubble,
1298
+ {
1299
+ variant: isUser ? "default" : "muted",
1300
+ align: isUser ? "end" : "start",
1301
+ className: cn(
1302
+ isUser ? "[&_[data-slot=bubble-content]]:bg-brand [&_[data-slot=bubble-content]]:text-brand-fg" : "[&_[data-slot=bubble-content]]:border [&_[data-slot=bubble-content]]:border-panel-border [&_[data-slot=bubble-content]]:bg-panel-elevated [&_[data-slot=bubble-content]]:text-panel-fg [&_code]:!bg-white/12 [&_pre]:!bg-white/10 [&_a]:text-brand"
1303
+ ),
1304
+ children: /* @__PURE__ */ jsx14(
1305
+ BubbleContent,
1306
+ {
1307
+ className: isUser ? "whitespace-pre-wrap" : void 0,
1308
+ children: isUser ? block.text : /* @__PURE__ */ jsx14(Markdown, { children: block.text })
1309
+ }
1310
+ )
1311
+ },
1312
+ i
1313
+ );
1314
+ }),
1315
+ isLast && !isUser && busy && !toolRunning && !hasText && /* @__PURE__ */ jsx14(BusyMarker, {})
1288
1316
  ] }) }),
1289
- (textParts.length > 0 || toolParts.length > 0) && /* @__PURE__ */ jsx14("span", { className: "px-1 text-[10px] text-panel-muted-fg", children: timeFor(message.id) })
1317
+ blocks.length > 0 && /* @__PURE__ */ jsx14("span", { className: "px-1 text-[10px] text-panel-muted-fg", children: timeFor(message.id) })
1290
1318
  ]
1291
1319
  }
1292
1320
  )
@@ -1642,10 +1670,10 @@ export {
1642
1670
  EditorStoreProvider,
1643
1671
  useDocumentStore,
1644
1672
  useCommentStore,
1645
- STARTER_MJML,
1646
1673
  EditorHeader,
1647
1674
  Markdown,
1648
1675
  ChatPanel,
1649
- CanvasComments
1676
+ CanvasComments,
1677
+ STARTER_MJML
1650
1678
  };
1651
- //# sourceMappingURL=chunk-IO4AKLYJ.js.map
1679
+ //# sourceMappingURL=chunk-YAKRVIM2.js.map