@mjml-agent-editor/editor 0.1.0 → 0.1.3
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.
- package/README.md +24 -0
- package/dist/canvas.cjs +4 -15
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.js +2 -2
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-LSF33PSW.js → chunk-74EUKHXJ.js} +9 -18
- package/dist/chunk-74EUKHXJ.js.map +1 -0
- package/dist/index.cjs +7 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -12
- package/dist/index.d.ts +25 -12
- package/dist/index.js +1 -1
- package/package.json +6 -5
- package/dist/chunk-LSF33PSW.js.map +0 -1
package/README.md
CHANGED
|
@@ -86,6 +86,30 @@ satisfying the two interfaces works, including an in-memory object.
|
|
|
86
86
|
|
|
87
87
|
Forgetting the provider throws with an explanation rather than failing on a null read.
|
|
88
88
|
|
|
89
|
+
## Reaching the agent
|
|
90
|
+
|
|
91
|
+
`ChatPanel` posts to `/api/chat` on the same origin by default, which is what
|
|
92
|
+
`createChatHandler` from `@mjml-agent-editor/agent-node` mounts. Pass a `transport` to send
|
|
93
|
+
turns somewhere else — a different path, another service, or nowhere at all:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { DefaultChatTransport } from "ai";
|
|
97
|
+
|
|
98
|
+
const transport = useMemo(
|
|
99
|
+
() => new DefaultChatTransport({ api: "https://agent.example.com/chat", body: { docId } }),
|
|
100
|
+
[docId],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
<ChatPanel docId={docId} transport={transport} {...handlers} />;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Any `ChatTransport<UIMessage>` works, so a transport that emits a recorded stream of
|
|
107
|
+
`UIMessageChunk`s drives the whole panel — streaming text, tool markers, section highlights
|
|
108
|
+
— without a single request. That is how the demo's scripted showcase runs with no agent
|
|
109
|
+
behind it.
|
|
110
|
+
|
|
111
|
+
Memoize it. A transport holding state restarts if it is rebuilt on every render.
|
|
112
|
+
|
|
89
113
|
## A note on field names
|
|
90
114
|
|
|
91
115
|
The ports are camelCase (`sectionId`, `objectId`, `projectData`); Postgres columns are
|
package/dist/canvas.cjs
CHANGED
|
@@ -40,6 +40,7 @@ var import_grapesjs = __toESM(require("grapesjs"), 1);
|
|
|
40
40
|
var import_react4 = __toESM(require("@grapesjs/react"), 1);
|
|
41
41
|
var import_grapesjs_mjml = __toESM(require("grapesjs-mjml"), 1);
|
|
42
42
|
var import_react5 = require("react");
|
|
43
|
+
var import_core2 = require("@mjml-agent-editor/core");
|
|
43
44
|
|
|
44
45
|
// src/stores.tsx
|
|
45
46
|
var import_react = require("react");
|
|
@@ -61,20 +62,8 @@ function useCommentStore() {
|
|
|
61
62
|
return useStores().comments;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
// src/
|
|
65
|
-
var
|
|
66
|
-
<mj-body background-color="#f4f4f5">
|
|
67
|
-
<mj-section background-color="#ffffff" padding="32px 24px">
|
|
68
|
-
<mj-column>
|
|
69
|
-
<mj-text font-size="22px" font-weight="bold">New email</mj-text>
|
|
70
|
-
<mj-text color="#555555">
|
|
71
|
-
Describe the email you need in the chat on the right \u2014 the agent
|
|
72
|
-
will design the sections, content and images.
|
|
73
|
-
</mj-text>
|
|
74
|
-
</mj-column>
|
|
75
|
-
</mj-section>
|
|
76
|
-
</mj-body>
|
|
77
|
-
</mjml>`;
|
|
65
|
+
// src/index.ts
|
|
66
|
+
var import_core = require("@mjml-agent-editor/core");
|
|
78
67
|
|
|
79
68
|
// src/lib/utils.ts
|
|
80
69
|
var import_clsx = require("clsx");
|
|
@@ -799,7 +788,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
|
|
|
799
788
|
const loadMjml = (mjml) => {
|
|
800
789
|
loadingRef.current = true;
|
|
801
790
|
if (saveTimer.current) clearTimeout(saveTimer.current);
|
|
802
|
-
editor.setComponents(mjml || STARTER_MJML);
|
|
791
|
+
editor.setComponents(mjml || import_core2.STARTER_MJML);
|
|
803
792
|
editor.getWrapper()?.find("mj-section").forEach(decorate);
|
|
804
793
|
for (const t of Object.keys(TYPE_LABEL)) {
|
|
805
794
|
editor.getWrapper()?.find(t).forEach(decorate);
|
package/dist/canvas.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/canvas.ts","../src/components/editor/EmailEditor.tsx","../src/stores.tsx","../src/starter.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, 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","/**\n * The document a brand-new email starts from.\n *\n * Kept in the package so the editor has something valid to render when a document row\n * exists but its MJML is empty, and overridable because the copy is host-specific — it is\n * the first thing a user of the template will want to change.\n */\nexport const STARTER_MJML = `<mjml>\n <mj-body background-color=\"#f4f4f5\">\n <mj-section background-color=\"#ffffff\" padding=\"32px 24px\">\n <mj-column>\n <mj-text font-size=\"22px\" font-weight=\"bold\">New email</mj-text>\n <mj-text color=\"#555555\">\n Describe the email you need in the chat on the right — the agent\n will design the sections, content and images.\n </mj-text>\n </mj-column>\n </mj-section>\n </mj-body>\n</mjml>`;\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_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/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"]}
|
package/dist/canvas.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
CanvasComments,
|
|
4
|
-
STARTER_MJML,
|
|
5
4
|
useDocumentStore
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-74EUKHXJ.js";
|
|
7
6
|
import {
|
|
8
7
|
Input,
|
|
9
8
|
Spinner,
|
|
@@ -15,6 +14,7 @@ import grapesjs from "grapesjs";
|
|
|
15
14
|
import GjsEditor, { Canvas, useEditorMaybe } from "@grapesjs/react";
|
|
16
15
|
import grapesjsMJML from "grapesjs-mjml";
|
|
17
16
|
import { useRef, useState } from "react";
|
|
17
|
+
import { STARTER_MJML } from "@mjml-agent-editor/core";
|
|
18
18
|
import { toast } from "sonner";
|
|
19
19
|
import { ChevronRight } from "lucide-react";
|
|
20
20
|
|