@mjml-agent-editor/editor 0.1.4 → 0.1.6

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/canvas.ts","../src/components/editor/EmailEditor.tsx","../src/stores.tsx","../src/index.ts","../src/lib/utils.ts","../src/components/ui/button.tsx","../src/components/ui/input.tsx","../src/components/ui/spinner.tsx","../src/components/ui/popover.tsx","../src/components/ui/textarea.tsx","../src/components/comments/CanvasComments.tsx","../src/components/ui/switch.tsx"],"sourcesContent":["/**\n * The GrapesJS canvas, isolated in its own entry point.\n *\n * GrapesJS touches `window` when it is imported, so this module can only be loaded in the\n * browser. Hosts should reach it through a client-only dynamic import — in Next.js that\n * is `dynamic(() => import(\"@mjml-agent-editor/editor/canvas\"), { ssr: false })`.\n */\n\nexport { default as EmailEditor } from \"./components/editor/EmailEditor.js\";\nexport type { EditorApi, EditorState, SaveStatus } from \"./components/editor/EmailEditor.js\";\n","\"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\n/**\n * How the editor reaches its data.\n *\n * In the spike every component imported `@/lib/documents`, which imported a Supabase\n * client — so adopting the editor meant adopting Supabase, and the components could not\n * be tested without a database. They now read whatever the host provides through this\n * context, and the host is free to back it with Supabase, plain Postgres, an HTTP API or\n * an in-memory object.\n *\n * The shapes are the same `DocumentStore` and `CommentStore` the agent uses\n * (`@mjml-agent-editor/core`), so one adapter serves both sides.\n */\n\nimport type { CommentStore, DocumentStore } from \"@mjml-agent-editor/core\";\nimport { createContext, useContext, type ReactNode } from \"react\";\n\nexport interface EditorStores {\n readonly documents: DocumentStore;\n readonly comments: CommentStore;\n}\n\nconst StoreContext = createContext<EditorStores | null>(null);\n\nexport function EditorStoreProvider({\n stores,\n children,\n}: {\n stores: EditorStores;\n children: ReactNode;\n}) {\n return <StoreContext.Provider value={stores}>{children}</StoreContext.Provider>;\n}\n\nfunction useStores(): EditorStores {\n const stores = useContext(StoreContext);\n if (!stores) {\n throw new Error(\n \"The editor needs an <EditorStoreProvider>. Wrap the editor and pass a DocumentStore \" +\n \"and a CommentStore — see packages/editor/README.md.\",\n );\n }\n return stores;\n}\n\nexport function useDocumentStore(): DocumentStore {\n return useStores().documents;\n}\n\nexport function useCommentStore(): CommentStore {\n return useStores().comments;\n}\n","export {\n EditorStoreProvider,\n useCommentStore,\n useDocumentStore,\n type EditorStores,\n} from \"./stores.js\";\n\n/**\n * Re-exported from `core`, where it moved so a server could import it. Kept here because\n * removing an export is a breaking change for no gain — but a route handler should reach\n * for `@mjml-agent-editor/core`, since everything in this entry point is client-only.\n */\nexport { STARTER_MJML } from \"@mjml-agent-editor/core\";\n\nexport { default as EditorHeader } from \"./components/editor/EditorHeader.js\";\nexport { default as ChatPanel } from \"./components/chat/ChatPanel.js\";\nexport { default as CanvasComments } from \"./components/comments/CanvasComments.js\";\nexport { Markdown } from \"./components/chat/Markdown.js\";\n\n// Types only — erased at build time, so this does not pull the canvas in.\nexport type { EditorApi, EditorState, SaveStatus } from \"./components/editor/EmailEditor.js\";\n\nexport type {\n CommentStore,\n CommentTarget,\n DocumentPatch,\n DocumentStore,\n EmailDocument,\n SectionComment,\n} from \"@mjml-agent-editor/core\";\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n outline:\n \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n ghost:\n \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n destructive:\n \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default:\n \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n xs: \"h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n sm: \"h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n icon: \"size-8\",\n \"icon-xs\":\n \"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3\",\n \"icon-sm\":\n \"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg\",\n \"icon-lg\": \"size-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant = \"default\",\n size = \"default\",\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n return (\n <input\n type={type}\n data-slot=\"input\"\n className={cn(\n \"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Input };\n","import { cn } from \"../../lib/utils\";\nimport { Loader2Icon } from \"lucide-react\";\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n return (\n <Loader2Icon\n data-slot=\"spinner\"\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\"size-4 animate-spin\", className)}\n {...props}\n />\n );\n}\n\nexport { Spinner };\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = \"center\",\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden\",\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };\n","import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n","\"use client\";\n\nimport { useEditorMaybe } from \"@grapesjs/react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Check, MessageSquarePlus, X } from \"lucide-react\";\n\nimport { useCommentStore, type CommentTarget, type SectionComment } from \"../../index.js\";\nimport { Popover, PopoverAnchor, PopoverContent } from \"../ui/popover\";\nimport { Button } from \"../ui/button\";\nimport { Textarea } from \"../ui/textarea\";\n\ntype Props = {\n docId: string;\n /** Sparkles-button target → opens the compose popover for that element. */\n composeTarget: CommentTarget | null;\n onComposeConsumed: () => void;\n /** Bump to refetch comments (after agent turns). */\n refreshSignal: number;\n onOpenCountChange: (n: number) => void;\n};\n\ntype Pos = { top: number; left: number };\n\n// Anchor key = the stable css-class the comment is attached to.\nconst keyOf = (c: Pick<SectionComment, \"objectId\" | \"sectionId\">) =>\n c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;\nconst keyOfTarget = (t: CommentTarget) => (t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`);\n\nexport default function CanvasComments({\n docId,\n composeTarget,\n onComposeConsumed,\n refreshSignal,\n onOpenCountChange,\n}: Props) {\n // Injected by the host rather than imported, so comments carry no database.\n const commentStore = useCommentStore();\n const editor = useEditorMaybe();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [comments, setComments] = useState<SectionComment[]>([]);\n const [positions, setPositions] = useState<Record<string, Pos>>({});\n const [activeKey, setActiveKey] = useState<string | null>(null);\n const [composeInfo, setComposeInfo] = useState<CommentTarget | null>(null);\n const [body, setBody] = useState(\"\");\n\n const reload = useCallback(() => {\n commentStore.list(docId).then(setComments).catch(console.error);\n }, [docId]);\n\n useEffect(() => {\n reload();\n }, [reload, refreshSignal]);\n\n const open = useMemo(() => comments.filter((c) => c.status === \"open\"), [comments]);\n\n useEffect(() => {\n onOpenCountChange(open.length);\n }, [open.length, onOpenCountChange]);\n\n // One pin per commented anchor (element or section), with its open comments.\n const pins = useMemo(() => {\n const map = new Map<string, SectionComment[]>();\n for (const c of open) {\n const k = keyOf(c);\n const arr = map.get(k) ?? [];\n arr.push(c);\n map.set(k, arr);\n }\n return [...map.entries()].map(([key, list]) => ({ key, list }));\n }, [open]);\n\n // Refs so the event-driven recompute reads fresh values without re-subscribing.\n const openRef = useRef(open);\n openRef.current = open;\n const activeRef = useRef(activeKey);\n activeRef.current = activeKey;\n const positionsRef = useRef<Record<string, Pos>>({});\n\n // Positions of pins/popover, computed from the elements' rects in the canvas iframe.\n const recompute = useCallback(() => {\n const ed = editor;\n const container = containerRef.current;\n if (!ed || !container) return;\n const frame = ed.Canvas.getFrameEl?.();\n const cdoc = ed.Canvas.getDocument?.();\n if (!frame || !cdoc) return;\n const cRect = container.getBoundingClientRect();\n const fRect = frame.getBoundingClientRect();\n // The iframe is scaled by GrapesJS zoom (transform), so its on-screen rect\n // (fRect) is scaled while elements INSIDE it report unscaled coordinates.\n // Map an inner coordinate to screen by this zoom factor.\n const zoom = frame.offsetWidth ? fRect.width / frame.offsetWidth : 1;\n const keys = new Set<string>(openRef.current.map(keyOf));\n if (activeRef.current) keys.add(activeRef.current);\n const next: Record<string, Pos> = {};\n keys.forEach((k) => {\n const el = cdoc.querySelector(`.${k}`) as HTMLElement | null;\n if (!el) return;\n const r = el.getBoundingClientRect();\n // Element's top-right corner mapped from iframe coords to screen (× zoom),\n // then a small constant offset so the pin sits just past the right edge.\n next[k] = {\n top: fRect.top - cRect.top + r.top * zoom + 4,\n left: fRect.left - cRect.left + (r.left + r.width) * zoom + 8,\n };\n });\n if (JSON.stringify(positionsRef.current) !== JSON.stringify(next)) {\n positionsRef.current = next;\n setPositions(next);\n }\n }, [editor]);\n\n // Recompute on canvas activity (edits, scroll, device change, resize) + a\n // low-frequency safety net for layout shifts the events miss.\n useEffect(() => {\n const ed = editor;\n if (!ed) return;\n const evts = [\n \"canvas:update\",\n \"update\",\n \"change:device\",\n \"load\",\n \"component:update\",\n \"component:mount\",\n ];\n evts.forEach((e) => ed.on(e, recompute));\n const win = ed.Canvas.getWindow?.();\n win?.addEventListener(\"scroll\", recompute, true);\n window.addEventListener(\"resize\", recompute);\n const id = window.setInterval(recompute, 500);\n return () => {\n evts.forEach((e) => ed.off(e, recompute));\n win?.removeEventListener(\"scroll\", recompute, true);\n window.removeEventListener(\"resize\", recompute);\n window.clearInterval(id);\n };\n }, [editor, recompute]);\n\n useEffect(() => {\n recompute();\n }, [comments, activeKey, recompute]);\n\n // Sparkles button → open the compose popover anchored to that element.\n useEffect(() => {\n if (!composeTarget) return;\n setComposeInfo(composeTarget);\n setActiveKey(keyOfTarget(composeTarget));\n setBody(\"\");\n requestAnimationFrame(recompute);\n }, [composeTarget, recompute]);\n\n const activeComments = activeKey ? open.filter((c) => keyOf(c) === activeKey) : [];\n const activeTarget: CommentTarget | null = (() => {\n if (!activeKey) return null;\n const c = activeComments[0];\n if (c) return { sectionId: c.sectionId, objectId: c.objectId, objectLabel: c.objectLabel };\n if (composeInfo && keyOfTarget(composeInfo) === activeKey) return composeInfo;\n return null;\n })();\n const activePos = activeKey ? positions[activeKey] : undefined;\n // Keep the last real anchor position through the popover's close animation.\n // On close activeKey→null makes activePos undefined; without this the anchor\n // would snap to (0,0) and the still-animating popover would flash there.\n const lastAnchorPos = useRef<Pos>({ top: 0, left: 0 });\n if (activePos) lastAnchorPos.current = activePos;\n const anchorPos = activePos ?? lastAnchorPos.current;\n\n const closePopover = useCallback(() => {\n setActiveKey(null);\n setBody(\"\");\n if (composeInfo) {\n setComposeInfo(null);\n onComposeConsumed();\n }\n }, [composeInfo, onComposeConsumed]);\n\n // Close the popover on a click inside the canvas iframe (Radix's outside-click\n // detection lives in the top document and misses iframe clicks).\n useEffect(() => {\n if (!activeKey || !editor) return;\n const doc = editor.Canvas.getDocument?.();\n if (!doc) return;\n const onDown = () => closePopover();\n doc.addEventListener(\"mousedown\", onDown, true);\n return () => doc.removeEventListener(\"mousedown\", onDown, true);\n }, [activeKey, editor, closePopover]);\n\n const submit = async () => {\n if (!activeTarget || !body.trim()) return;\n try {\n await commentStore.add(docId, activeTarget, body.trim());\n setBody(\"\");\n if (composeInfo) {\n setComposeInfo(null);\n onComposeConsumed();\n }\n reload();\n } catch (e) {\n console.error(e);\n }\n };\n\n const resolve = async (id: string) => {\n try {\n await commentStore.resolve(id);\n reload();\n } catch (e) {\n console.error(e);\n }\n };\n\n return (\n <div ref={containerRef} className=\"pointer-events-none absolute inset-0 z-20 overflow-hidden\">\n {pins.map(({ key, list }) => {\n const pos = positions[key];\n if (!pos) return null;\n return (\n <button\n key={key}\n type=\"button\"\n onClick={() => {\n setActiveKey((k) => (k === key ? null : key));\n setBody(\"\");\n }}\n style={{ top: pos.top, left: pos.left }}\n className=\"pointer-events-auto absolute flex h-6 min-w-6 items-center justify-center rounded-full rounded-bl-none bg-brand px-1.5 text-xs font-semibold text-brand-fg shadow-md ring-2 ring-white transition-transform hover:scale-110\"\n title={`${list.length} comment(s)`}\n >\n {list.length}\n </button>\n );\n })}\n\n <Popover open={!!activeKey && !!activePos} onOpenChange={(o) => !o && closePopover()}>\n <PopoverAnchor asChild>\n <div\n style={{\n position: \"absolute\",\n top: anchorPos.top,\n left: anchorPos.left,\n width: 1,\n height: 1,\n }}\n />\n </PopoverAnchor>\n <PopoverContent\n side=\"right\"\n align=\"start\"\n // Disable the close animation: while it plays, Radix keeps the content\n // mounted but floating-ui drops its positioning for a frame, flashing\n // the popover at (0,0). No exit animation → instant unmount, no flash.\n className=\"pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0\"\n >\n <div className=\"flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground\">\n <span className=\"min-w-0\">\n {activeTarget?.objectId ? (\n <>\n Comment on{\" \"}\n <span className=\"font-medium text-foreground\">{activeTarget.objectLabel}</span>\n </>\n ) : (\n <>\n Comment on the whole section{\" \"}\n <code className=\"font-mono\">{activeTarget?.sectionId}</code>\n </>\n )}\n </span>\n <button\n type=\"button\"\n onClick={closePopover}\n aria-label=\"Close\"\n className=\"-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"max-h-64 space-y-2 overflow-y-auto p-3\">\n {activeComments.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">No comments yet — add the first one.</p>\n )}\n {activeComments.map((c) => (\n <div key={c.id} className=\"rounded-md border bg-muted/40 p-2 text-sm\">\n <p className=\"whitespace-pre-wrap\">{c.body}</p>\n <button\n onClick={() => void resolve(c.id)}\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Check className=\"size-3\" /> Resolve\n </button>\n </div>\n ))}\n </div>\n <div className=\"border-t p-3\">\n <Textarea\n autoFocus\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Add a comment… (⌘/Ctrl+Enter to send)\"\n className=\"min-h-14 resize-none text-sm\"\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n <div className=\"mt-2 flex justify-end\">\n <Button size=\"sm\" disabled={!body.trim()} onClick={() => void submit()}>\n <MessageSquarePlus /> Add\n </Button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\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":["import_react","import_core","import_jsx_runtime","React","import_jsx_runtime","import_jsx_runtime","React","import_radix_ui","import_jsx_runtime","PopoverPrimitive","PopoverPrimitive","React","import_jsx_runtime","import_react","import_lucide_react","import_jsx_runtime","import_lucide_react","React","import_radix_ui","import_jsx_runtime","SwitchPrimitive","import_lucide_react","import_jsx_runtime","GjsEditor","grapesjs","grapesjsMJML"]}
1
+ {"version":3,"sources":["../src/canvas.ts","../src/components/editor/EmailEditor.tsx","../src/stores.tsx","../src/labels.ts","../src/index.ts","../src/lib/utils.ts","../src/components/ui/button.tsx","../src/components/ui/input.tsx","../src/components/ui/spinner.tsx","../src/components/ui/popover.tsx","../src/components/ui/textarea.tsx","../src/components/comments/CanvasComments.tsx","../src/components/ui/switch.tsx"],"sourcesContent":["/**\n * The GrapesJS canvas, isolated in its own entry point.\n *\n * GrapesJS touches `window` when it is imported, so this module can only be loaded in the\n * browser. Hosts should reach it through a client-only dynamic import — in Next.js that\n * is `dynamic(() => import(\"@mjml-agent-editor/editor/canvas\"), { ssr: false })`.\n */\n\nexport { default as EmailEditor } from \"./components/editor/EmailEditor.js\";\nexport type { EditorApi, EditorState, SaveStatus } from \"./components/editor/EmailEditor.js\";\n","\"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, useLabels, type CommentTarget, type EditorLabels } 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, labels: EditorLabels) {\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=\"${labels.rteFont}\"><option value=\"\">${labels.rteFont}</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=\"${labels.rteSize}\"><option value=\"\">${labels.rteSize}</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=\"${labels.rteTextColor}\" 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=\"${labels.rteBackgroundColor}\" 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 const labels = useLabels();\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, labels);\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(labels.documentSaveFailed);\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(labels.documentLoadFailed);\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 labels = useLabels();\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={labels.searchBlocks}\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={labels.attributes}>\n <div id=\"gjs-traits\" />\n </CollapseSection>\n <CollapseSection title={labels.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>{labels.expandAll}</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 const labels = useLabels();\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\">{labels.selectAnElement}</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\n/**\n * How the editor reaches its data.\n *\n * In the spike every component imported `@/lib/documents`, which imported a Supabase\n * client — so adopting the editor meant adopting Supabase, and the components could not\n * be tested without a database. They now read whatever the host provides through this\n * context, and the host is free to back it with Supabase, plain Postgres, an HTTP API or\n * an in-memory object.\n *\n * The shapes are the same `DocumentStore` and `CommentStore` the agent uses\n * (`@mjml-agent-editor/core`), so one adapter serves both sides.\n */\n\nimport type { CommentStore, DocumentStore } from \"@mjml-agent-editor/core\";\nimport { createContext, useContext, useMemo, type ReactNode } from \"react\";\n\nimport { mergeLabels, type EditorLabels } from \"./labels.js\";\n\nexport interface EditorStores {\n readonly documents: DocumentStore;\n readonly comments: CommentStore;\n}\n\nconst StoreContext = createContext<EditorStores | null>(null);\nconst LabelContext = createContext<EditorLabels | null>(null);\n\nexport function EditorStoreProvider({\n stores,\n labels,\n children,\n}: {\n stores: EditorStores;\n /**\n * Overrides for the copy the editor renders. Omitted, everything is English.\n *\n * It rides on this provider rather than a second one because the host already has to\n * wrap the editor in exactly one place, and a component that needs a store almost always\n * needs a word too.\n */\n labels?: Partial<EditorLabels>;\n children: ReactNode;\n}) {\n const merged = useMemo(() => mergeLabels(labels), [labels]);\n return (\n <StoreContext.Provider value={stores}>\n <LabelContext.Provider value={merged}>{children}</LabelContext.Provider>\n </StoreContext.Provider>\n );\n}\n\n/**\n * Falls back to the defaults instead of throwing, unlike the stores.\n *\n * A missing store means the editor cannot work at all and should say so loudly; missing copy\n * means English, which is a perfectly good outcome and keeps the components renderable in\n * isolation.\n */\nexport function useLabels(): EditorLabels {\n return useContext(LabelContext) ?? mergeLabels();\n}\n\nfunction useStores(): EditorStores {\n const stores = useContext(StoreContext);\n if (!stores) {\n throw new Error(\n \"The editor needs an <EditorStoreProvider>. Wrap the editor and pass a DocumentStore \" +\n \"and a CommentStore — see packages/editor/README.md.\",\n );\n }\n return stores;\n}\n\nexport function useDocumentStore(): DocumentStore {\n return useStores().documents;\n}\n\nexport function useCommentStore(): CommentStore {\n return useStores().comments;\n}\n","/**\n * Every word the editor puts on screen.\n *\n * It was all inline English, which quietly made the package unusable to anyone building in\n * another language: adopting it meant forking it to translate a button. The defaults below\n * keep existing hosts working unchanged, and a host overrides only the keys it cares about.\n *\n * Flat and shallow-merged on purpose. A nested dictionary reads better and merges worse —\n * overriding one key inside a group would silently drop its siblings. `toolLabels` is the\n * single nested map, and it is merged key by key for exactly that reason.\n *\n * Values that interpolate are functions rather than templates with placeholders, so the\n * translator controls word order instead of the format string.\n */\n\nexport interface EditorLabels {\n // Header\n readonly appName: string;\n readonly emailNameAria: string;\n readonly renameFailed: string;\n readonly saving: string;\n readonly saved: string;\n readonly saveFailed: string;\n readonly undo: string;\n readonly redo: string;\n readonly contentWidth: string;\n readonly preview: string;\n readonly closePreview: string;\n readonly previewFrameTitle: string;\n readonly sourceCode: string;\n readonly exportHtml: string;\n readonly exportMjml: string;\n /** `kind` is already upper-cased (\"MJML\", \"HTML\"). */\n readonly copied: (kind: string) => string;\n\n // Canvas\n readonly searchBlocks: string;\n readonly expandAll: string;\n readonly selectAnElement: string;\n readonly attributes: string;\n readonly style: string;\n readonly rteFont: string;\n readonly rteSize: string;\n readonly rteTextColor: string;\n readonly rteBackgroundColor: string;\n readonly documentLoadFailed: string;\n readonly documentSaveFailed: string;\n\n // Comments\n readonly commentOnSection: string;\n readonly closeComments: string;\n readonly noCommentsYet: string;\n readonly resolveComment: string;\n readonly addComment: string;\n readonly addCommentPlaceholder: string;\n\n // Chat\n readonly chatEmptyTitle: string;\n readonly chatEmptyDescription: string;\n readonly agentWorking: string;\n readonly reasoning: string;\n readonly completedSteps: (count: number, failed: number) => string;\n readonly applyComments: string;\n readonly chatPlaceholder: string;\n readonly stop: string;\n readonly send: string;\n readonly chatError: string;\n /** Keyed by tool name; merged key by key rather than replaced wholesale. */\n readonly toolLabels: Readonly<Record<string, string>>;\n}\n\nexport const DEFAULT_LABELS: EditorLabels = {\n appName: \"MJML Editor\",\n emailNameAria: \"Email name\",\n renameFailed: \"Failed to rename.\",\n saving: \"Saving…\",\n saved: \"Saved\",\n saveFailed: \"Save failed\",\n undo: \"Undo\",\n redo: \"Redo\",\n contentWidth: \"Content width\",\n preview: \"Preview\",\n closePreview: \"Close preview\",\n previewFrameTitle: \"Email preview\",\n sourceCode: \"Source code\",\n exportHtml: \"Export HTML\",\n exportMjml: \"Export MJML\",\n copied: (kind) => `${kind} copied.`,\n\n searchBlocks: \"Search blocks…\",\n expandAll: \"Expand all\",\n selectAnElement: \"Select an element…\",\n attributes: \"Attributes\",\n style: \"Style\",\n rteFont: \"Font\",\n rteSize: \"Size\",\n rteTextColor: \"Text color\",\n rteBackgroundColor: \"Background color\",\n documentLoadFailed: \"Failed to load the document.\",\n documentSaveFailed: \"Failed to save changes.\",\n\n commentOnSection: \"Comment on the whole section\",\n closeComments: \"Close\",\n noCommentsYet: \"No comments yet — add the first one.\",\n resolveComment: \"Resolve\",\n addComment: \"Add\",\n addCommentPlaceholder: \"Add a comment… (⌘/Ctrl+Enter to send)\",\n\n chatEmptyTitle: \"Start a conversation with the agent\",\n chatEmptyDescription:\n \"Describe the email (goal, tone, content) and paste your data — the agent will design the sections and images.\",\n agentWorking: \"Agent is working…\",\n reasoning: \"Reasoning\",\n completedSteps: (count, failed) =>\n failed > 0 ? `Completed steps (${count}, ${failed} failed)` : `Completed steps (${count})`,\n applyComments: \"Apply changes from comments\",\n chatPlaceholder: \"Describe what you want to change… (Enter = send, Shift+Enter = new line)\",\n stop: \"Stop\",\n send: \"Send\",\n chatError: \"Agent chat error.\",\n\n toolLabels: {\n get_document: \"Reading document\",\n get_section: \"Reading section\",\n set_document: \"Saving whole email\",\n set_section: \"Replacing section\",\n insert_section: \"Adding section\",\n remove_section: \"Removing section\",\n generate_image: \"Generating image\",\n list_open_comments: \"Reading comments\",\n resolve_comment: \"Resolving comment\",\n },\n};\n\n/**\n * Overrides on top of the defaults. `toolLabels` merges rather than replaces, so a host\n * renaming one step does not lose the other eight.\n */\nexport function mergeLabels(overrides?: Partial<EditorLabels>): EditorLabels {\n if (!overrides) return DEFAULT_LABELS;\n return {\n ...DEFAULT_LABELS,\n ...overrides,\n toolLabels: { ...DEFAULT_LABELS.toolLabels, ...overrides.toolLabels },\n };\n}\n","export {\n EditorStoreProvider,\n useCommentStore,\n useDocumentStore,\n useLabels,\n type EditorStores,\n} from \"./stores.js\";\n\nexport { DEFAULT_LABELS, mergeLabels, type EditorLabels } from \"./labels.js\";\n\n/**\n * Re-exported from `core`, where it moved so a server could import it. Kept here because\n * removing an export is a breaking change for no gain — but a route handler should reach\n * for `@mjml-agent-editor/core`, since everything in this entry point is client-only.\n */\nexport { STARTER_MJML } from \"@mjml-agent-editor/core\";\n\nexport { default as EditorHeader } from \"./components/editor/EditorHeader.js\";\nexport { default as ChatPanel } from \"./components/chat/ChatPanel.js\";\nexport { default as CanvasComments } from \"./components/comments/CanvasComments.js\";\nexport { Markdown } from \"./components/chat/Markdown.js\";\n\n// Types only — erased at build time, so this does not pull the canvas in.\nexport type { EditorApi, EditorState, SaveStatus } from \"./components/editor/EmailEditor.js\";\n\nexport type {\n CommentStore,\n CommentTarget,\n DocumentPatch,\n DocumentStore,\n EmailDocument,\n SectionComment,\n} from \"@mjml-agent-editor/core\";\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n outline:\n \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n ghost:\n \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n destructive:\n \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default:\n \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n xs: \"h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n sm: \"h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n icon: \"size-8\",\n \"icon-xs\":\n \"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3\",\n \"icon-sm\":\n \"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg\",\n \"icon-lg\": \"size-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant = \"default\",\n size = \"default\",\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n return (\n <input\n type={type}\n data-slot=\"input\"\n className={cn(\n \"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Input };\n","import { cn } from \"../../lib/utils\";\nimport { Loader2Icon } from \"lucide-react\";\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n return (\n <Loader2Icon\n data-slot=\"spinner\"\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\"size-4 animate-spin\", className)}\n {...props}\n />\n );\n}\n\nexport { Spinner };\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = \"center\",\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden\",\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };\n","import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n","\"use client\";\n\nimport { useEditorMaybe } from \"@grapesjs/react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Check, MessageSquarePlus, X } from \"lucide-react\";\n\nimport {\n useLabels,\n useCommentStore,\n type CommentTarget,\n type SectionComment,\n} from \"../../index.js\";\nimport { Popover, PopoverAnchor, PopoverContent } from \"../ui/popover\";\nimport { Button } from \"../ui/button\";\nimport { Textarea } from \"../ui/textarea\";\n\ntype Props = {\n docId: string;\n /** Sparkles-button target → opens the compose popover for that element. */\n composeTarget: CommentTarget | null;\n onComposeConsumed: () => void;\n /** Bump to refetch comments (after agent turns). */\n refreshSignal: number;\n onOpenCountChange: (n: number) => void;\n};\n\ntype Pos = { top: number; left: number };\n\n// Anchor key = the stable css-class the comment is attached to.\nconst keyOf = (c: Pick<SectionComment, \"objectId\" | \"sectionId\">) =>\n c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;\nconst keyOfTarget = (t: CommentTarget) => (t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`);\n\nexport default function CanvasComments({\n docId,\n composeTarget,\n onComposeConsumed,\n refreshSignal,\n onOpenCountChange,\n}: Props) {\n // Injected by the host rather than imported, so comments carry no database.\n const commentStore = useCommentStore();\n const labels = useLabels();\n const editor = useEditorMaybe();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [comments, setComments] = useState<SectionComment[]>([]);\n const [positions, setPositions] = useState<Record<string, Pos>>({});\n const [activeKey, setActiveKey] = useState<string | null>(null);\n const [composeInfo, setComposeInfo] = useState<CommentTarget | null>(null);\n const [body, setBody] = useState(\"\");\n\n const reload = useCallback(() => {\n commentStore.list(docId).then(setComments).catch(console.error);\n }, [docId]);\n\n useEffect(() => {\n reload();\n }, [reload, refreshSignal]);\n\n const open = useMemo(() => comments.filter((c) => c.status === \"open\"), [comments]);\n\n useEffect(() => {\n onOpenCountChange(open.length);\n }, [open.length, onOpenCountChange]);\n\n // One pin per commented anchor (element or section), with its open comments.\n const pins = useMemo(() => {\n const map = new Map<string, SectionComment[]>();\n for (const c of open) {\n const k = keyOf(c);\n const arr = map.get(k) ?? [];\n arr.push(c);\n map.set(k, arr);\n }\n return [...map.entries()].map(([key, list]) => ({ key, list }));\n }, [open]);\n\n // Refs so the event-driven recompute reads fresh values without re-subscribing.\n const openRef = useRef(open);\n openRef.current = open;\n const activeRef = useRef(activeKey);\n activeRef.current = activeKey;\n const positionsRef = useRef<Record<string, Pos>>({});\n\n // Positions of pins/popover, computed from the elements' rects in the canvas iframe.\n const recompute = useCallback(() => {\n const ed = editor;\n const container = containerRef.current;\n if (!ed || !container) return;\n const frame = ed.Canvas.getFrameEl?.();\n const cdoc = ed.Canvas.getDocument?.();\n if (!frame || !cdoc) return;\n const cRect = container.getBoundingClientRect();\n const fRect = frame.getBoundingClientRect();\n // The iframe is scaled by GrapesJS zoom (transform), so its on-screen rect\n // (fRect) is scaled while elements INSIDE it report unscaled coordinates.\n // Map an inner coordinate to screen by this zoom factor.\n const zoom = frame.offsetWidth ? fRect.width / frame.offsetWidth : 1;\n const keys = new Set<string>(openRef.current.map(keyOf));\n if (activeRef.current) keys.add(activeRef.current);\n const next: Record<string, Pos> = {};\n keys.forEach((k) => {\n const el = cdoc.querySelector(`.${k}`) as HTMLElement | null;\n if (!el) return;\n const r = el.getBoundingClientRect();\n // Element's top-right corner mapped from iframe coords to screen (× zoom),\n // then a small constant offset so the pin sits just past the right edge.\n next[k] = {\n top: fRect.top - cRect.top + r.top * zoom + 4,\n left: fRect.left - cRect.left + (r.left + r.width) * zoom + 8,\n };\n });\n if (JSON.stringify(positionsRef.current) !== JSON.stringify(next)) {\n positionsRef.current = next;\n setPositions(next);\n }\n }, [editor]);\n\n // Recompute on canvas activity (edits, scroll, device change, resize) + a\n // low-frequency safety net for layout shifts the events miss.\n useEffect(() => {\n const ed = editor;\n if (!ed) return;\n const evts = [\n \"canvas:update\",\n \"update\",\n \"change:device\",\n \"load\",\n \"component:update\",\n \"component:mount\",\n ];\n evts.forEach((e) => ed.on(e, recompute));\n const win = ed.Canvas.getWindow?.();\n win?.addEventListener(\"scroll\", recompute, true);\n window.addEventListener(\"resize\", recompute);\n const id = window.setInterval(recompute, 500);\n return () => {\n evts.forEach((e) => ed.off(e, recompute));\n win?.removeEventListener(\"scroll\", recompute, true);\n window.removeEventListener(\"resize\", recompute);\n window.clearInterval(id);\n };\n }, [editor, recompute]);\n\n useEffect(() => {\n recompute();\n }, [comments, activeKey, recompute]);\n\n // Sparkles button → open the compose popover anchored to that element.\n useEffect(() => {\n if (!composeTarget) return;\n setComposeInfo(composeTarget);\n setActiveKey(keyOfTarget(composeTarget));\n setBody(\"\");\n requestAnimationFrame(recompute);\n }, [composeTarget, recompute]);\n\n const activeComments = activeKey ? open.filter((c) => keyOf(c) === activeKey) : [];\n const activeTarget: CommentTarget | null = (() => {\n if (!activeKey) return null;\n const c = activeComments[0];\n if (c) return { sectionId: c.sectionId, objectId: c.objectId, objectLabel: c.objectLabel };\n if (composeInfo && keyOfTarget(composeInfo) === activeKey) return composeInfo;\n return null;\n })();\n const activePos = activeKey ? positions[activeKey] : undefined;\n // Keep the last real anchor position through the popover's close animation.\n // On close activeKey→null makes activePos undefined; without this the anchor\n // would snap to (0,0) and the still-animating popover would flash there.\n const lastAnchorPos = useRef<Pos>({ top: 0, left: 0 });\n if (activePos) lastAnchorPos.current = activePos;\n const anchorPos = activePos ?? lastAnchorPos.current;\n\n const closePopover = useCallback(() => {\n setActiveKey(null);\n setBody(\"\");\n if (composeInfo) {\n setComposeInfo(null);\n onComposeConsumed();\n }\n }, [composeInfo, onComposeConsumed]);\n\n // Close the popover on a click inside the canvas iframe (Radix's outside-click\n // detection lives in the top document and misses iframe clicks).\n useEffect(() => {\n if (!activeKey || !editor) return;\n const doc = editor.Canvas.getDocument?.();\n if (!doc) return;\n const onDown = () => closePopover();\n doc.addEventListener(\"mousedown\", onDown, true);\n return () => doc.removeEventListener(\"mousedown\", onDown, true);\n }, [activeKey, editor, closePopover]);\n\n const submit = async () => {\n if (!activeTarget || !body.trim()) return;\n try {\n await commentStore.add(docId, activeTarget, body.trim());\n setBody(\"\");\n if (composeInfo) {\n setComposeInfo(null);\n onComposeConsumed();\n }\n reload();\n } catch (e) {\n console.error(e);\n }\n };\n\n const resolve = async (id: string) => {\n try {\n await commentStore.resolve(id);\n reload();\n } catch (e) {\n console.error(e);\n }\n };\n\n return (\n <div ref={containerRef} className=\"pointer-events-none absolute inset-0 z-20 overflow-hidden\">\n {pins.map(({ key, list }) => {\n const pos = positions[key];\n if (!pos) return null;\n return (\n <button\n key={key}\n type=\"button\"\n onClick={() => {\n setActiveKey((k) => (k === key ? null : key));\n setBody(\"\");\n }}\n style={{ top: pos.top, left: pos.left }}\n className=\"pointer-events-auto absolute flex h-6 min-w-6 items-center justify-center rounded-full rounded-bl-none bg-brand px-1.5 text-xs font-semibold text-brand-fg shadow-md ring-2 ring-white transition-transform hover:scale-110\"\n title={`${list.length} comment(s)`}\n >\n {list.length}\n </button>\n );\n })}\n\n <Popover open={!!activeKey && !!activePos} onOpenChange={(o) => !o && closePopover()}>\n <PopoverAnchor asChild>\n <div\n style={{\n position: \"absolute\",\n top: anchorPos.top,\n left: anchorPos.left,\n width: 1,\n height: 1,\n }}\n />\n </PopoverAnchor>\n <PopoverContent\n side=\"right\"\n align=\"start\"\n // Disable the close animation: while it plays, Radix keeps the content\n // mounted but floating-ui drops its positioning for a frame, flashing\n // the popover at (0,0). No exit animation → instant unmount, no flash.\n className=\"pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0\"\n >\n <div className=\"flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground\">\n <span className=\"min-w-0\">\n {activeTarget?.objectId ? (\n <>\n Comment on{\" \"}\n <span className=\"font-medium text-foreground\">{activeTarget.objectLabel}</span>\n </>\n ) : (\n <>\n {labels.commentOnSection}{\" \"}\n <code className=\"font-mono\">{activeTarget?.sectionId}</code>\n </>\n )}\n </span>\n <button\n type=\"button\"\n onClick={closePopover}\n aria-label={labels.closeComments}\n className=\"-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"max-h-64 space-y-2 overflow-y-auto p-3\">\n {activeComments.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">{labels.noCommentsYet}</p>\n )}\n {activeComments.map((c) => (\n <div key={c.id} className=\"rounded-md border bg-muted/40 p-2 text-sm\">\n <p className=\"whitespace-pre-wrap\">{c.body}</p>\n <button\n onClick={() => void resolve(c.id)}\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Check className=\"size-3\" /> {labels.resolveComment}\n </button>\n </div>\n ))}\n </div>\n <div className=\"border-t p-3\">\n <Textarea\n autoFocus\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder={labels.addCommentPlaceholder}\n className=\"min-h-14 resize-none text-sm\"\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n <div className=\"mt-2 flex justify-end\">\n <Button size=\"sm\" disabled={!body.trim()} onClick={() => void submit()}>\n <MessageSquarePlus /> {labels.addComment}\n </Button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\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":["import_react","import_core","import_jsx_runtime","React","import_jsx_runtime","import_jsx_runtime","React","import_radix_ui","import_jsx_runtime","PopoverPrimitive","PopoverPrimitive","React","import_jsx_runtime","import_react","import_lucide_react","import_jsx_runtime","import_lucide_react","React","import_radix_ui","import_jsx_runtime","SwitchPrimitive","import_lucide_react","import_jsx_runtime","GjsEditor","grapesjs","grapesjsMJML"]}
package/dist/canvas.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import {
3
3
  CanvasComments,
4
- useDocumentStore
5
- } from "./chunk-YAKRVIM2.js";
4
+ useDocumentStore,
5
+ useLabels
6
+ } from "./chunk-XAEUCHL4.js";
6
7
  import {
7
8
  Input,
8
9
  Spinner,
@@ -145,13 +146,13 @@ function wrapSelectionStyle(el, style) {
145
146
  } catch {
146
147
  }
147
148
  }
148
- function setupRichText(editor) {
149
+ function setupRichText(editor, labels) {
149
150
  const rte = editor.RichTextEditor;
150
151
  if (!rte || rte.__customActions) return;
151
152
  rte.__customActions = true;
152
153
  const val = (action, sel) => action.btn?.querySelector(sel)?.value ?? "";
153
154
  rte.add("fontName", {
154
- icon: `<select class="gjs-rte-select" title="Font"><option value="">Font</option>${RTE_FONTS.map(
155
+ icon: `<select class="gjs-rte-select" title="${labels.rteFont}"><option value="">${labels.rteFont}</option>${RTE_FONTS.map(
155
156
  (f) => `<option value="${f}">${f}</option>`
156
157
  ).join("")}</select>`,
157
158
  event: "change",
@@ -161,7 +162,7 @@ function setupRichText(editor) {
161
162
  }
162
163
  });
163
164
  rte.add("fontSize", {
164
- icon: `<select class="gjs-rte-select" title="Size"><option value="">Size</option>${RTE_SIZES.map(
165
+ icon: `<select class="gjs-rte-select" title="${labels.rteSize}"><option value="">${labels.rteSize}</option>${RTE_SIZES.map(
165
166
  (s) => `<option value="${s}px">${s}</option>`
166
167
  ).join("")}</select>`,
167
168
  event: "change",
@@ -171,18 +172,19 @@ function setupRichText(editor) {
171
172
  }
172
173
  });
173
174
  rte.add("forecolor", {
174
- icon: `<input type="color" class="gjs-rte-color" title="Text color" value="#000000">`,
175
+ icon: `<input type="color" class="gjs-rte-color" title="${labels.rteTextColor}" value="#000000">`,
175
176
  event: "change",
176
177
  result: (r, action) => r.exec("foreColor", val(action, "input"))
177
178
  });
178
179
  rte.add("hilitecolor", {
179
- icon: `<input type="color" class="gjs-rte-color" title="Background color" value="#ffff00">`,
180
+ icon: `<input type="color" class="gjs-rte-color" title="${labels.rteBackgroundColor}" value="#ffff00">`,
180
181
  event: "change",
181
182
  result: (r, action) => r.exec("hiliteColor", val(action, "input"))
182
183
  });
183
184
  }
184
185
  function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
185
186
  const documents = useDocumentStore();
187
+ const labels = useLabels();
186
188
  const loadingRef = useRef(false);
187
189
  const saveTimer = useRef(null);
188
190
  const saveStatusRef = useRef("idle");
@@ -262,7 +264,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
262
264
  if (target) setComposeTarget(target);
263
265
  }
264
266
  });
265
- setupRichText(editor);
267
+ setupRichText(editor, labels);
266
268
  const blockCategory = (label) => {
267
269
  const l = label.toLowerCase();
268
270
  if (/(column|section)/.test(l)) return "Layout";
@@ -316,7 +318,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
316
318
  setSave("saved");
317
319
  } catch (e) {
318
320
  setSave("error");
319
- toast.error("Failed to save changes.");
321
+ toast.error(labels.documentSaveFailed);
320
322
  throw e;
321
323
  }
322
324
  };
@@ -374,7 +376,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
374
376
  const doc = await documents.get(docId);
375
377
  loadMjml(doc.mjml);
376
378
  } catch (e) {
377
- toast.error("Failed to load the document.");
379
+ toast.error(labels.documentLoadFailed);
378
380
  console.error(e);
379
381
  } finally {
380
382
  setLoading(false);
@@ -511,6 +513,7 @@ function LeftSidebar({
511
513
  markLayersSource
512
514
  }) {
513
515
  const editor = useEditorMaybe();
516
+ const labels = useLabels();
514
517
  const [layersExpanded, setLayersExpanded] = useState(false);
515
518
  const [blockSearch, setBlockSearch] = useState("");
516
519
  const changeView = (v) => {
@@ -568,7 +571,7 @@ function LeftSidebar({
568
571
  {
569
572
  value: blockSearch,
570
573
  onChange: (e) => filterBlocks(e.target.value),
571
- placeholder: "Search blocks\u2026",
574
+ placeholder: labels.searchBlocks,
572
575
  className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
573
576
  }
574
577
  ),
@@ -577,8 +580,8 @@ function LeftSidebar({
577
580
  /* @__PURE__ */ jsx2("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
578
581
  ] }),
579
582
  /* @__PURE__ */ jsxs("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: [
580
- /* @__PURE__ */ jsx2(CollapseSection, { title: "Attributes", children: /* @__PURE__ */ jsx2("div", { id: "gjs-traits" }) }),
581
- /* @__PURE__ */ jsx2(CollapseSection, { title: "Style", children: /* @__PURE__ */ jsx2("div", { id: "gjs-styles" }) })
583
+ /* @__PURE__ */ jsx2(CollapseSection, { title: labels.attributes, children: /* @__PURE__ */ jsx2("div", { id: "gjs-traits" }) }),
584
+ /* @__PURE__ */ jsx2(CollapseSection, { title: labels.style, children: /* @__PURE__ */ jsx2("div", { id: "gjs-styles" }) })
582
585
  ] }),
583
586
  /* @__PURE__ */ jsxs(
584
587
  "div",
@@ -587,7 +590,7 @@ function LeftSidebar({
587
590
  onMouseDownCapture: markLayersSource,
588
591
  children: [
589
592
  /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg", children: [
590
- /* @__PURE__ */ jsx2("span", { children: "Expand all" }),
593
+ /* @__PURE__ */ jsx2("span", { children: labels.expandAll }),
591
594
  /* @__PURE__ */ jsx2(
592
595
  Switch,
593
596
  {
@@ -607,7 +610,8 @@ function Breadcrumb({
607
610
  crumbs,
608
611
  onSelect
609
612
  }) {
610
- return /* @__PURE__ */ jsx2("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", children: crumbs.length === 0 ? /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground/60", children: "Select an element\u2026" }) : crumbs.map((c, i) => /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5", children: [
613
+ const labels = useLabels();
614
+ return /* @__PURE__ */ jsx2("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", children: crumbs.length === 0 ? /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5", children: [
611
615
  i > 0 && /* @__PURE__ */ jsx2(ChevronRight, { className: "size-3 opacity-40" }),
612
616
  /* @__PURE__ */ jsx2(
613
617
  "button",