@mjml-agent-editor/editor 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,1935 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ CanvasComments: () => CanvasComments,
35
+ ChatPanel: () => ChatPanel,
36
+ EditorHeader: () => EditorHeader,
37
+ EditorStoreProvider: () => EditorStoreProvider,
38
+ Markdown: () => Markdown,
39
+ STARTER_MJML: () => STARTER_MJML,
40
+ useCommentStore: () => useCommentStore,
41
+ useDocumentStore: () => useDocumentStore
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/stores.tsx
46
+ var import_react = require("react");
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ var StoreContext = (0, import_react.createContext)(null);
49
+ function EditorStoreProvider({
50
+ stores,
51
+ children
52
+ }) {
53
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StoreContext.Provider, { value: stores, children });
54
+ }
55
+ function useStores() {
56
+ const stores = (0, import_react.useContext)(StoreContext);
57
+ if (!stores) {
58
+ throw new Error(
59
+ "The editor needs an <EditorStoreProvider>. Wrap the editor and pass a DocumentStore and a CommentStore \u2014 see packages/editor/README.md."
60
+ );
61
+ }
62
+ return stores;
63
+ }
64
+ function useDocumentStore() {
65
+ return useStores().documents;
66
+ }
67
+ function useCommentStore() {
68
+ return useStores().comments;
69
+ }
70
+
71
+ // src/starter.ts
72
+ var STARTER_MJML = `<mjml>
73
+ <mj-body background-color="#f4f4f5">
74
+ <mj-section background-color="#ffffff" padding="32px 24px">
75
+ <mj-column>
76
+ <mj-text font-size="22px" font-weight="bold">New email</mj-text>
77
+ <mj-text color="#555555">
78
+ Describe the email you need in the chat on the right \u2014 the agent
79
+ will design the sections, content and images.
80
+ </mj-text>
81
+ </mj-column>
82
+ </mj-section>
83
+ </mj-body>
84
+ </mjml>`;
85
+
86
+ // src/components/editor/EditorHeader.tsx
87
+ var import_react2 = require("react");
88
+ var import_sonner = require("sonner");
89
+ var import_lucide_react3 = require("lucide-react");
90
+
91
+ // src/lib/utils.ts
92
+ var import_clsx = require("clsx");
93
+ var import_tailwind_merge = require("tailwind-merge");
94
+ function cn(...inputs) {
95
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
96
+ }
97
+
98
+ // src/components/ui/button.tsx
99
+ var React = require("react");
100
+ var import_class_variance_authority = require("class-variance-authority");
101
+ var import_radix_ui = require("radix-ui");
102
+ var import_jsx_runtime2 = require("react/jsx-runtime");
103
+ var buttonVariants = (0, import_class_variance_authority.cva)(
104
+ "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",
105
+ {
106
+ variants: {
107
+ variant: {
108
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
109
+ outline: "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",
110
+ secondary: "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",
111
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
112
+ destructive: "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",
113
+ link: "text-primary underline-offset-4 hover:underline"
114
+ },
115
+ size: {
116
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
117
+ 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",
118
+ 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",
119
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
120
+ icon: "size-8",
121
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
122
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
123
+ "icon-lg": "size-9"
124
+ }
125
+ },
126
+ defaultVariants: {
127
+ variant: "default",
128
+ size: "default"
129
+ }
130
+ }
131
+ );
132
+ function Button({
133
+ className,
134
+ variant = "default",
135
+ size = "default",
136
+ asChild = false,
137
+ ...props
138
+ }) {
139
+ const Comp = asChild ? import_radix_ui.Slot.Root : "button";
140
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
141
+ Comp,
142
+ {
143
+ "data-slot": "button",
144
+ "data-variant": variant,
145
+ "data-size": size,
146
+ className: cn(buttonVariants({ variant, size, className })),
147
+ ...props
148
+ }
149
+ );
150
+ }
151
+
152
+ // src/components/ui/input.tsx
153
+ var React2 = require("react");
154
+ var import_jsx_runtime3 = require("react/jsx-runtime");
155
+ function Input({ className, type, ...props }) {
156
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
157
+ "input",
158
+ {
159
+ type,
160
+ "data-slot": "input",
161
+ className: cn(
162
+ "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",
163
+ className
164
+ ),
165
+ ...props
166
+ }
167
+ );
168
+ }
169
+
170
+ // src/components/ui/toggle-group.tsx
171
+ var React4 = __toESM(require("react"), 1);
172
+ var import_class_variance_authority3 = require("class-variance-authority");
173
+ var import_radix_ui3 = require("radix-ui");
174
+
175
+ // src/components/ui/toggle.tsx
176
+ var React3 = require("react");
177
+ var import_class_variance_authority2 = require("class-variance-authority");
178
+ var import_radix_ui2 = require("radix-ui");
179
+ var import_jsx_runtime4 = require("react/jsx-runtime");
180
+ var toggleVariants = (0, import_class_variance_authority2.cva)(
181
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
182
+ {
183
+ variants: {
184
+ variant: {
185
+ default: "bg-transparent",
186
+ outline: "border border-input bg-transparent hover:bg-muted"
187
+ },
188
+ size: {
189
+ default: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
190
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
191
+ lg: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2"
192
+ }
193
+ },
194
+ defaultVariants: {
195
+ variant: "default",
196
+ size: "default"
197
+ }
198
+ }
199
+ );
200
+
201
+ // src/components/ui/toggle-group.tsx
202
+ var import_jsx_runtime5 = require("react/jsx-runtime");
203
+ var ToggleGroupContext = React4.createContext({
204
+ size: "default",
205
+ variant: "default",
206
+ spacing: 2,
207
+ orientation: "horizontal"
208
+ });
209
+ function ToggleGroup({
210
+ className,
211
+ variant,
212
+ size,
213
+ spacing = 2,
214
+ orientation = "horizontal",
215
+ children,
216
+ ...props
217
+ }) {
218
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
219
+ import_radix_ui3.ToggleGroup.Root,
220
+ {
221
+ "data-slot": "toggle-group",
222
+ "data-variant": variant,
223
+ "data-size": size,
224
+ "data-spacing": spacing,
225
+ "data-orientation": orientation,
226
+ style: { "--gap": spacing },
227
+ className: cn(
228
+ "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch",
229
+ className
230
+ ),
231
+ ...props,
232
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ToggleGroupContext.Provider, { value: { variant, size, spacing, orientation }, children })
233
+ }
234
+ );
235
+ }
236
+ function ToggleGroupItem({
237
+ className,
238
+ children,
239
+ variant = "default",
240
+ size = "default",
241
+ ...props
242
+ }) {
243
+ const context = React4.useContext(ToggleGroupContext);
244
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
245
+ import_radix_ui3.ToggleGroup.Item,
246
+ {
247
+ "data-slot": "toggle-group-item",
248
+ "data-variant": context.variant || variant,
249
+ "data-size": context.size || size,
250
+ "data-spacing": context.spacing,
251
+ className: cn(
252
+ "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
253
+ toggleVariants({
254
+ variant: context.variant || variant,
255
+ size: context.size || size
256
+ }),
257
+ className
258
+ ),
259
+ ...props,
260
+ children
261
+ }
262
+ );
263
+ }
264
+
265
+ // src/components/ui/tooltip.tsx
266
+ var React5 = require("react");
267
+ var import_radix_ui4 = require("radix-ui");
268
+ var import_jsx_runtime6 = require("react/jsx-runtime");
269
+ function Tooltip({ ...props }) {
270
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui4.Tooltip.Root, { "data-slot": "tooltip", ...props });
271
+ }
272
+ function TooltipTrigger({ ...props }) {
273
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui4.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
274
+ }
275
+ function TooltipContent({
276
+ className,
277
+ sideOffset = 0,
278
+ children,
279
+ ...props
280
+ }) {
281
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui4.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
282
+ import_radix_ui4.Tooltip.Content,
283
+ {
284
+ "data-slot": "tooltip-content",
285
+ sideOffset,
286
+ className: cn(
287
+ "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
288
+ className
289
+ ),
290
+ ...props,
291
+ children: [
292
+ children,
293
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui4.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
294
+ ]
295
+ }
296
+ ) });
297
+ }
298
+
299
+ // src/components/ui/badge.tsx
300
+ var React6 = require("react");
301
+ var import_class_variance_authority4 = require("class-variance-authority");
302
+ var import_radix_ui5 = require("radix-ui");
303
+ var import_jsx_runtime7 = require("react/jsx-runtime");
304
+ var badgeVariants = (0, import_class_variance_authority4.cva)(
305
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
306
+ {
307
+ variants: {
308
+ variant: {
309
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
310
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
311
+ destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
312
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
313
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
314
+ link: "text-primary underline-offset-4 hover:underline"
315
+ }
316
+ },
317
+ defaultVariants: {
318
+ variant: "default"
319
+ }
320
+ }
321
+ );
322
+ function Badge({
323
+ className,
324
+ variant = "default",
325
+ asChild = false,
326
+ ...props
327
+ }) {
328
+ const Comp = asChild ? import_radix_ui5.Slot.Root : "span";
329
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
330
+ Comp,
331
+ {
332
+ "data-slot": "badge",
333
+ "data-variant": variant,
334
+ className: cn(badgeVariants({ variant }), className),
335
+ ...props
336
+ }
337
+ );
338
+ }
339
+
340
+ // src/components/ui/spinner.tsx
341
+ var import_lucide_react = require("lucide-react");
342
+ var import_jsx_runtime8 = require("react/jsx-runtime");
343
+ function Spinner({ className, ...props }) {
344
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
345
+ import_lucide_react.Loader2Icon,
346
+ {
347
+ "data-slot": "spinner",
348
+ role: "status",
349
+ "aria-label": "Loading",
350
+ className: cn("size-4 animate-spin", className),
351
+ ...props
352
+ }
353
+ );
354
+ }
355
+
356
+ // src/components/ui/separator.tsx
357
+ var React7 = require("react");
358
+ var import_radix_ui6 = require("radix-ui");
359
+ var import_jsx_runtime9 = require("react/jsx-runtime");
360
+ function Separator({
361
+ className,
362
+ orientation = "horizontal",
363
+ decorative = true,
364
+ ...props
365
+ }) {
366
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
367
+ import_radix_ui6.Separator.Root,
368
+ {
369
+ "data-slot": "separator",
370
+ decorative,
371
+ orientation,
372
+ className: cn(
373
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
374
+ className
375
+ ),
376
+ ...props
377
+ }
378
+ );
379
+ }
380
+
381
+ // src/components/ui/popover.tsx
382
+ var React8 = require("react");
383
+ var import_radix_ui7 = require("radix-ui");
384
+ var import_jsx_runtime10 = require("react/jsx-runtime");
385
+ function Popover({ ...props }) {
386
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui7.Popover.Root, { "data-slot": "popover", ...props });
387
+ }
388
+ function PopoverTrigger({ ...props }) {
389
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui7.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
390
+ }
391
+ function PopoverAnchor({ ...props }) {
392
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui7.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
393
+ }
394
+ function PopoverContent({
395
+ className,
396
+ align = "center",
397
+ sideOffset = 4,
398
+ ...props
399
+ }) {
400
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui7.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
401
+ import_radix_ui7.Popover.Content,
402
+ {
403
+ "data-slot": "popover-content",
404
+ align,
405
+ sideOffset,
406
+ className: cn(
407
+ "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",
408
+ className
409
+ ),
410
+ ...props
411
+ }
412
+ ) });
413
+ }
414
+
415
+ // src/components/ui/tabs.tsx
416
+ var React9 = require("react");
417
+ var import_class_variance_authority5 = require("class-variance-authority");
418
+ var import_radix_ui8 = require("radix-ui");
419
+ var import_jsx_runtime11 = require("react/jsx-runtime");
420
+ function Tabs({
421
+ className,
422
+ orientation = "horizontal",
423
+ ...props
424
+ }) {
425
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
426
+ import_radix_ui8.Tabs.Root,
427
+ {
428
+ "data-slot": "tabs",
429
+ "data-orientation": orientation,
430
+ className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className),
431
+ ...props
432
+ }
433
+ );
434
+ }
435
+ var tabsListVariants = (0, import_class_variance_authority5.cva)(
436
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
437
+ {
438
+ variants: {
439
+ variant: {
440
+ default: "bg-muted",
441
+ line: "gap-1 bg-transparent"
442
+ }
443
+ },
444
+ defaultVariants: {
445
+ variant: "default"
446
+ }
447
+ }
448
+ );
449
+ function TabsList({
450
+ className,
451
+ variant = "default",
452
+ ...props
453
+ }) {
454
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
455
+ import_radix_ui8.Tabs.List,
456
+ {
457
+ "data-slot": "tabs-list",
458
+ "data-variant": variant,
459
+ className: cn(tabsListVariants({ variant }), className),
460
+ ...props
461
+ }
462
+ );
463
+ }
464
+ function TabsTrigger({ className, ...props }) {
465
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
466
+ import_radix_ui8.Tabs.Trigger,
467
+ {
468
+ "data-slot": "tabs-trigger",
469
+ className: cn(
470
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
471
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
472
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
473
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
474
+ className
475
+ ),
476
+ ...props
477
+ }
478
+ );
479
+ }
480
+ function TabsContent({ className, ...props }) {
481
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
482
+ import_radix_ui8.Tabs.Content,
483
+ {
484
+ "data-slot": "tabs-content",
485
+ className: cn("flex-1 text-sm outline-none", className),
486
+ ...props
487
+ }
488
+ );
489
+ }
490
+
491
+ // src/components/ui/dialog.tsx
492
+ var React10 = require("react");
493
+ var import_radix_ui9 = require("radix-ui");
494
+ var import_lucide_react2 = require("lucide-react");
495
+ var import_jsx_runtime12 = require("react/jsx-runtime");
496
+ function Dialog({ ...props }) {
497
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui9.Dialog.Root, { "data-slot": "dialog", ...props });
498
+ }
499
+ function DialogTrigger({ ...props }) {
500
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui9.Dialog.Trigger, { "data-slot": "dialog-trigger", ...props });
501
+ }
502
+ function DialogPortal({ ...props }) {
503
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui9.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
504
+ }
505
+ function DialogClose({ ...props }) {
506
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui9.Dialog.Close, { "data-slot": "dialog-close", ...props });
507
+ }
508
+ function DialogOverlay({
509
+ className,
510
+ ...props
511
+ }) {
512
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
513
+ import_radix_ui9.Dialog.Overlay,
514
+ {
515
+ "data-slot": "dialog-overlay",
516
+ className: cn(
517
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
518
+ className
519
+ ),
520
+ ...props
521
+ }
522
+ );
523
+ }
524
+ function DialogContent({
525
+ className,
526
+ children,
527
+ showCloseButton = true,
528
+ ...props
529
+ }) {
530
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DialogOverlay, {}),
532
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
533
+ import_radix_ui9.Dialog.Content,
534
+ {
535
+ "data-slot": "dialog-content",
536
+ className: cn(
537
+ "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
538
+ className
539
+ ),
540
+ ...props,
541
+ children: [
542
+ children,
543
+ showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui9.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Button, { variant: "ghost", className: "absolute top-2 right-2", size: "icon-sm", children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react2.XIcon, {}),
545
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "sr-only", children: "Close" })
546
+ ] }) })
547
+ ]
548
+ }
549
+ )
550
+ ] });
551
+ }
552
+ function DialogHeader({ className, ...props }) {
553
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props });
554
+ }
555
+ function DialogTitle({ className, ...props }) {
556
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
557
+ import_radix_ui9.Dialog.Title,
558
+ {
559
+ "data-slot": "dialog-title",
560
+ className: cn("font-heading text-base leading-none font-medium", className),
561
+ ...props
562
+ }
563
+ );
564
+ }
565
+
566
+ // src/components/editor/EditorHeader.tsx
567
+ var import_jsx_runtime13 = require("react/jsx-runtime");
568
+ var SAVE_LABEL = {
569
+ idle: "",
570
+ saving: "Saving\u2026",
571
+ saved: "Saved",
572
+ error: "Save failed"
573
+ };
574
+ var WIDTHS = ["600px", "640px", "680px", "720px"];
575
+ var DEVICE_ICON = {
576
+ Desktop: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Monitor, { className: "size-3.5" }),
577
+ Mobile: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Smartphone, { className: "size-3.5" })
578
+ };
579
+ function SaveBadge({ status }) {
580
+ if (status === "idle") return null;
581
+ if (status === "saving")
582
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-xs text-panel-muted-fg", children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner, { className: "size-3" }),
584
+ " ",
585
+ SAVE_LABEL.saving
586
+ ] });
587
+ if (status === "error") return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Badge, { variant: "destructive", children: SAVE_LABEL.error });
588
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-xs text-emerald-400", children: [
589
+ "\u2713 ",
590
+ SAVE_LABEL.saved
591
+ ] });
592
+ }
593
+ function slugify(name) {
594
+ return name.trim().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").toLowerCase() || "email";
595
+ }
596
+ function download(filename, content, mime) {
597
+ const blob = new Blob([content], { type: mime });
598
+ const url = URL.createObjectURL(blob);
599
+ const a = document.createElement("a");
600
+ a.href = url;
601
+ a.download = filename;
602
+ a.click();
603
+ URL.revokeObjectURL(url);
604
+ }
605
+ var darkGhost = "text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
606
+ function EditorHeader({
607
+ docId,
608
+ api,
609
+ openCount,
610
+ homeLink
611
+ }) {
612
+ const documents = useDocumentStore();
613
+ const [name, setName] = (0, import_react2.useState)("");
614
+ const [state, setState] = (0, import_react2.useState)({
615
+ device: "Desktop",
616
+ canUndo: false,
617
+ canRedo: false,
618
+ saveStatus: "idle",
619
+ zoom: 100,
620
+ contentWidth: "600px"
621
+ });
622
+ const [code, setCode] = (0, import_react2.useState)({ mjml: "", html: "" });
623
+ const [preview, setPreview] = (0, import_react2.useState)("");
624
+ (0, import_react2.useEffect)(() => {
625
+ documents.get(docId).then((d) => setName(d.name)).catch(() => {
626
+ });
627
+ }, [docId, documents]);
628
+ (0, import_react2.useEffect)(() => {
629
+ if (!api) return;
630
+ return api.onEditorState(setState);
631
+ }, [api]);
632
+ const commitName = async () => {
633
+ const n = name.trim() || "Untitled";
634
+ setName(n);
635
+ try {
636
+ await documents.save(docId, { name: n });
637
+ } catch {
638
+ import_sonner.toast.error("Failed to rename.");
639
+ }
640
+ };
641
+ const devices = api?.getDevices() ?? [];
642
+ const base = slugify(name);
643
+ const openCode = () => setCode({ mjml: api?.getMjml() ?? "", html: api?.getCompiledHtml() ?? "" });
644
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b border-panel-border bg-panel px-3 text-panel-fg", children: [
645
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-brand to-indigo-400 text-brand-fg shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Mail, { className: "size-4" }) }),
646
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "hidden text-sm font-semibold sm:inline", children: "MJML Editor" }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Separator, { orientation: "vertical", className: "mx-1 !h-5 bg-panel-border" }),
648
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
649
+ Input,
650
+ {
651
+ value: name,
652
+ onChange: (e) => setName(e.target.value),
653
+ onBlur: () => void commitName(),
654
+ onKeyDown: (e) => e.key === "Enter" && e.currentTarget.blur(),
655
+ className: "h-8 w-44 border-transparent bg-transparent text-sm font-medium text-panel-fg hover:border-panel-border focus-visible:border-panel-border",
656
+ "aria-label": "Email name"
657
+ }
658
+ ),
659
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Separator, { orientation: "vertical", className: "mx-1 !h-5 bg-panel-border" }),
660
+ devices.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
661
+ ToggleGroup,
662
+ {
663
+ type: "single",
664
+ size: "sm",
665
+ value: state.device,
666
+ onValueChange: (v) => v && api?.setDevice(v),
667
+ className: "rounded-md border border-panel-border",
668
+ children: devices.map((d) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
669
+ ToggleGroupItem,
670
+ {
671
+ value: d.name,
672
+ className: "gap-1.5 px-2.5 text-panel-muted-fg data-[state=on]:bg-panel-elevated data-[state=on]:text-panel-fg",
673
+ children: [
674
+ DEVICE_ICON[d.name],
675
+ d.name
676
+ ]
677
+ },
678
+ d.id
679
+ ))
680
+ }
681
+ ),
682
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Tooltip, { children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
684
+ Button,
685
+ {
686
+ variant: "ghost",
687
+ size: "icon",
688
+ className: darkGhost,
689
+ disabled: !state.canUndo,
690
+ onClick: () => api?.undo(),
691
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Undo2, {})
692
+ }
693
+ ) }),
694
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipContent, { children: "Undo" })
695
+ ] }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Tooltip, { children: [
697
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
698
+ Button,
699
+ {
700
+ variant: "ghost",
701
+ size: "icon",
702
+ className: darkGhost,
703
+ disabled: !state.canRedo,
704
+ onClick: () => api?.redo(),
705
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Redo2, {})
706
+ }
707
+ ) }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TooltipContent, { children: "Redo" })
709
+ ] }),
710
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Separator, { orientation: "vertical", className: "mx-1 !h-5 bg-panel-border max-xl:hidden" }),
711
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Popover, { children: [
712
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
713
+ Button,
714
+ {
715
+ variant: "ghost",
716
+ size: "sm",
717
+ className: cn(darkGhost, "gap-1.5 rounded-md border border-panel-border max-xl:hidden"),
718
+ children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-panel-muted-fg", children: "Content width" }),
720
+ state.contentWidth,
721
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.ChevronDown, { className: "size-3.5" })
722
+ ]
723
+ }
724
+ ) }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverContent, { align: "start", className: "w-40 p-1", children: WIDTHS.map((w) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
726
+ "button",
727
+ {
728
+ onClick: () => api?.setContentWidth(w),
729
+ className: cn(
730
+ "flex w-full items-center justify-between rounded px-2 py-1.5 text-sm hover:bg-muted",
731
+ state.contentWidth === w && "font-medium text-brand"
732
+ ),
733
+ children: w
734
+ },
735
+ w
736
+ )) })
737
+ ] }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-0.5 rounded-md border border-panel-border px-0.5 max-xl:hidden", children: [
739
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
740
+ Button,
741
+ {
742
+ variant: "ghost",
743
+ size: "icon",
744
+ className: cn(darkGhost, "size-7"),
745
+ onClick: () => api?.setZoom(state.zoom - 10),
746
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Minus, { className: "size-3.5" })
747
+ }
748
+ ),
749
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "w-10 text-center text-xs tabular-nums text-panel-muted-fg", children: [
750
+ state.zoom,
751
+ "%"
752
+ ] }),
753
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
754
+ Button,
755
+ {
756
+ variant: "ghost",
757
+ size: "icon",
758
+ className: cn(darkGhost, "size-7"),
759
+ onClick: () => api?.setZoom(state.zoom + 10),
760
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Plus, { className: "size-3.5" })
761
+ }
762
+ )
763
+ ] }),
764
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-auto flex items-center gap-2", children: [
765
+ openCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-xs text-panel-muted-fg", children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.MessageSquare, { className: "size-3.5" }),
767
+ " ",
768
+ openCount
769
+ ] }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SaveBadge, { status: state.saveStatus }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Dialog, { children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
773
+ Button,
774
+ {
775
+ variant: "ghost",
776
+ size: "sm",
777
+ className: cn(darkGhost, "gap-1.5"),
778
+ onClick: () => setPreview(api?.getCompiledHtml() ?? ""),
779
+ children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Eye, {}),
781
+ " Preview"
782
+ ]
783
+ }
784
+ ) }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
786
+ DialogContent,
787
+ {
788
+ showCloseButton: false,
789
+ className: "flex !max-w-none h-screen w-screen flex-col gap-0 rounded-none border-0 bg-zinc-100 p-0 sm:rounded-none",
790
+ children: [
791
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex h-12 shrink-0 items-center justify-between border-b bg-white px-4", children: [
792
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogTitle, { className: "text-sm", children: [
793
+ "Preview \u2014 ",
794
+ name
795
+ ] }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
797
+ devices.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
798
+ ToggleGroup,
799
+ {
800
+ type: "single",
801
+ size: "sm",
802
+ value: state.device,
803
+ onValueChange: (v) => v && api?.setDevice(v),
804
+ variant: "outline",
805
+ children: devices.map((d) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToggleGroupItem, { value: d.name, className: "px-2.5", children: d.name }, d.id))
806
+ }
807
+ ),
808
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
809
+ Button,
810
+ {
811
+ variant: "outline",
812
+ size: "sm",
813
+ onClick: () => download(`${base}.html`, preview, "text/html"),
814
+ children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Download, {}),
816
+ " Export HTML"
817
+ ]
818
+ }
819
+ ),
820
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { variant: "ghost", size: "icon", "aria-label": "Close preview", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.X, {}) }) })
821
+ ] })
822
+ ] }),
823
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "min-h-0 flex-1 overflow-auto p-6", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
824
+ "iframe",
825
+ {
826
+ title: "Email preview",
827
+ srcDoc: preview,
828
+ className: cn(
829
+ "mx-auto block h-full min-h-[80vh] rounded-md border bg-white shadow-sm",
830
+ state.device === "Mobile" ? "w-[375px]" : "w-full max-w-[720px]"
831
+ )
832
+ }
833
+ ) })
834
+ ]
835
+ }
836
+ )
837
+ ] }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Dialog, { children: [
839
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
840
+ Button,
841
+ {
842
+ variant: "ghost",
843
+ size: "sm",
844
+ className: cn(darkGhost, "gap-1.5"),
845
+ onClick: openCode,
846
+ children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Code2, {}),
848
+ " Code"
849
+ ]
850
+ }
851
+ ) }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogContent, { className: "flex !max-w-4xl flex-col", children: [
853
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogTitle, { children: "Source code" }) }),
854
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Tabs, { defaultValue: "mjml", className: "min-h-0 flex-1", children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TabsList, { children: [
856
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TabsTrigger, { value: "mjml", children: "MJML" }),
857
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TabsTrigger, { value: "html", children: "HTML" })
858
+ ] }),
859
+ ["mjml", "html"].map((kind) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TabsContent, { value: kind, className: "mt-2", children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "max-h-[62vh] overflow-auto rounded-md border bg-muted/40 p-3 text-xs leading-relaxed break-all whitespace-pre-wrap", children: code[kind] }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mt-2 flex justify-end gap-2", children: [
862
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
863
+ Button,
864
+ {
865
+ variant: "outline",
866
+ size: "sm",
867
+ onClick: () => download(
868
+ `${base}.${kind}`,
869
+ code[kind],
870
+ kind === "html" ? "text/html" : "text/plain"
871
+ ),
872
+ children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Download, {}),
874
+ " Download .",
875
+ kind
876
+ ]
877
+ }
878
+ ),
879
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
880
+ Button,
881
+ {
882
+ variant: "secondary",
883
+ size: "sm",
884
+ onClick: () => {
885
+ void navigator.clipboard?.writeText(code[kind]);
886
+ import_sonner.toast.success(`${kind.toUpperCase()} copied.`);
887
+ },
888
+ children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Copy, {}),
890
+ " Copy"
891
+ ]
892
+ }
893
+ )
894
+ ] })
895
+ ] }, kind))
896
+ ] })
897
+ ] })
898
+ ] }),
899
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Popover, { children: [
900
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Button, { size: "sm", className: "gap-1.5 bg-brand text-brand-fg hover:bg-brand/90", children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Download, {}),
902
+ " Export",
903
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.ChevronDown, { className: "size-3.5" })
904
+ ] }) }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(PopoverContent, { align: "end", className: "w-52 p-1", children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
907
+ "button",
908
+ {
909
+ onClick: () => download(`${base}.html`, api?.getCompiledHtml() ?? "", "text/html"),
910
+ className: "flex w-full flex-col rounded px-2 py-1.5 text-left text-sm hover:bg-muted",
911
+ children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium", children: "Export HTML" }),
913
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
914
+ base,
915
+ ".html"
916
+ ] })
917
+ ]
918
+ }
919
+ ),
920
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
921
+ "button",
922
+ {
923
+ onClick: () => download(`${base}.mjml`, api?.getMjml() ?? "", "text/plain"),
924
+ className: "flex w-full flex-col rounded px-2 py-1.5 text-left text-sm hover:bg-muted",
925
+ children: [
926
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium", children: "Export MJML" }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
928
+ base,
929
+ ".mjml"
930
+ ] })
931
+ ]
932
+ }
933
+ )
934
+ ] })
935
+ ] }),
936
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Popover, { children: [
937
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { variant: "ghost", size: "icon", className: darkGhost, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.MoreVertical, {}) }) }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverContent, { align: "end", className: "w-44 p-1", children: homeLink })
939
+ ] })
940
+ ] })
941
+ ] });
942
+ }
943
+
944
+ // src/components/chat/ChatPanel.tsx
945
+ var import_react4 = require("@ai-sdk/react");
946
+ var import_ai = require("ai");
947
+ var import_react5 = require("react");
948
+ var import_sonner2 = require("sonner");
949
+ var import_lucide_react5 = require("lucide-react");
950
+
951
+ // src/components/ui/textarea.tsx
952
+ var React11 = require("react");
953
+ var import_jsx_runtime14 = require("react/jsx-runtime");
954
+ function Textarea({ className, ...props }) {
955
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
956
+ "textarea",
957
+ {
958
+ "data-slot": "textarea",
959
+ className: cn(
960
+ "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",
961
+ className
962
+ ),
963
+ ...props
964
+ }
965
+ );
966
+ }
967
+
968
+ // src/components/ui/empty.tsx
969
+ var import_class_variance_authority6 = require("class-variance-authority");
970
+ var import_jsx_runtime15 = require("react/jsx-runtime");
971
+ function Empty({ className, ...props }) {
972
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
973
+ "div",
974
+ {
975
+ "data-slot": "empty",
976
+ className: cn(
977
+ "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
978
+ className
979
+ ),
980
+ ...props
981
+ }
982
+ );
983
+ }
984
+ function EmptyHeader({ className, ...props }) {
985
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
986
+ "div",
987
+ {
988
+ "data-slot": "empty-header",
989
+ className: cn("flex max-w-sm flex-col items-center gap-2", className),
990
+ ...props
991
+ }
992
+ );
993
+ }
994
+ var emptyMediaVariants = (0, import_class_variance_authority6.cva)(
995
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
996
+ {
997
+ variants: {
998
+ variant: {
999
+ default: "bg-transparent",
1000
+ icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4"
1001
+ }
1002
+ },
1003
+ defaultVariants: {
1004
+ variant: "default"
1005
+ }
1006
+ }
1007
+ );
1008
+ function EmptyMedia({
1009
+ className,
1010
+ variant = "default",
1011
+ ...props
1012
+ }) {
1013
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1014
+ "div",
1015
+ {
1016
+ "data-slot": "empty-icon",
1017
+ "data-variant": variant,
1018
+ className: cn(emptyMediaVariants({ variant, className })),
1019
+ ...props
1020
+ }
1021
+ );
1022
+ }
1023
+ function EmptyTitle({ className, ...props }) {
1024
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1025
+ "div",
1026
+ {
1027
+ "data-slot": "empty-title",
1028
+ className: cn("font-heading text-sm font-medium tracking-tight", className),
1029
+ ...props
1030
+ }
1031
+ );
1032
+ }
1033
+ function EmptyDescription({ className, ...props }) {
1034
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1035
+ "div",
1036
+ {
1037
+ "data-slot": "empty-description",
1038
+ className: cn(
1039
+ "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
1040
+ className
1041
+ ),
1042
+ ...props
1043
+ }
1044
+ );
1045
+ }
1046
+
1047
+ // src/components/ui/message.tsx
1048
+ var React12 = require("react");
1049
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1050
+ function Message({
1051
+ className,
1052
+ align = "start",
1053
+ ...props
1054
+ }) {
1055
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1056
+ "div",
1057
+ {
1058
+ "data-slot": "message",
1059
+ "data-align": align,
1060
+ className: cn(
1061
+ "group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
1062
+ className
1063
+ ),
1064
+ ...props
1065
+ }
1066
+ );
1067
+ }
1068
+ function MessageContent({ className, ...props }) {
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1070
+ "div",
1071
+ {
1072
+ "data-slot": "message-content",
1073
+ className: cn(
1074
+ "flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
1075
+ className
1076
+ ),
1077
+ ...props
1078
+ }
1079
+ );
1080
+ }
1081
+
1082
+ // src/components/ui/bubble.tsx
1083
+ var React13 = require("react");
1084
+ var import_class_variance_authority7 = require("class-variance-authority");
1085
+ var import_radix_ui10 = require("radix-ui");
1086
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1087
+ var bubbleVariants = (0, import_class_variance_authority7.cva)(
1088
+ "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
1089
+ {
1090
+ variants: {
1091
+ variant: {
1092
+ default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
1093
+ secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
1094
+ muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
1095
+ tinted: "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
1096
+ outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
1097
+ ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
1098
+ destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
1099
+ }
1100
+ },
1101
+ defaultVariants: {
1102
+ variant: "default"
1103
+ }
1104
+ }
1105
+ );
1106
+ function Bubble({
1107
+ variant = "default",
1108
+ align = "start",
1109
+ className,
1110
+ ...props
1111
+ }) {
1112
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1113
+ "div",
1114
+ {
1115
+ "data-slot": "bubble",
1116
+ "data-variant": variant,
1117
+ "data-align": align,
1118
+ className: cn(bubbleVariants({ variant }), className),
1119
+ ...props
1120
+ }
1121
+ );
1122
+ }
1123
+ function BubbleContent({
1124
+ asChild = false,
1125
+ className,
1126
+ ...props
1127
+ }) {
1128
+ const Comp = asChild ? import_radix_ui10.Slot.Root : "div";
1129
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1130
+ Comp,
1131
+ {
1132
+ "data-slot": "bubble-content",
1133
+ className: cn(
1134
+ "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
1135
+ className
1136
+ ),
1137
+ ...props
1138
+ }
1139
+ );
1140
+ }
1141
+ var bubbleReactionsVariants = (0, import_class_variance_authority7.cva)(
1142
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
1143
+ {
1144
+ variants: {
1145
+ side: {
1146
+ top: "top-0 -translate-y-3/4",
1147
+ bottom: "bottom-0 translate-y-3/4"
1148
+ },
1149
+ align: {
1150
+ start: "left-3",
1151
+ end: "right-3"
1152
+ }
1153
+ },
1154
+ defaultVariants: {
1155
+ side: "bottom",
1156
+ align: "end"
1157
+ }
1158
+ }
1159
+ );
1160
+
1161
+ // src/components/chat/Markdown.tsx
1162
+ var import_react3 = require("react");
1163
+ var import_react_markdown = __toESM(require("react-markdown"), 1);
1164
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
1165
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1166
+ var components = {
1167
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1168
+ a: ({ node, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("a", { target: "_blank", rel: "noopener noreferrer", ...props })
1169
+ };
1170
+ function MarkdownImpl({ children }) {
1171
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1172
+ "div",
1173
+ {
1174
+ className: cn(
1175
+ "text-sm leading-relaxed break-words",
1176
+ "[&>:first-child]:mt-0 [&>:last-child]:mb-0",
1177
+ "[&_p]:my-1.5",
1178
+ "[&_ul]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5",
1179
+ "[&_li]:my-0.5 [&_li>ul]:my-0.5 [&_li>ol]:my-0.5",
1180
+ "[&_strong]:font-semibold [&_em]:italic",
1181
+ "[&_a]:underline [&_a]:underline-offset-2",
1182
+ "[&_h1]:mt-2 [&_h1]:mb-1 [&_h1]:text-base [&_h1]:font-semibold",
1183
+ "[&_h2]:mt-2 [&_h2]:mb-1 [&_h2]:text-sm [&_h2]:font-semibold",
1184
+ "[&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-semibold",
1185
+ "[&_code]:rounded [&_code]:bg-black/5 [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em]",
1186
+ "[&_pre]:my-1.5 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-black/5 [&_pre]:p-2 [&_pre]:text-xs",
1187
+ "[&_pre_code]:bg-transparent [&_pre_code]:p-0",
1188
+ "[&_blockquote]:my-1.5 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground",
1189
+ "[&_hr]:my-2 [&_hr]:border-border",
1190
+ "[&_table]:my-1.5 [&_table]:w-full [&_table]:border-collapse [&_table]:text-xs",
1191
+ "[&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold",
1192
+ "[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1"
1193
+ ),
1194
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], components, children })
1195
+ }
1196
+ );
1197
+ }
1198
+ var Markdown = (0, import_react3.memo)(MarkdownImpl);
1199
+
1200
+ // src/components/ui/marker.tsx
1201
+ var React14 = require("react");
1202
+ var import_class_variance_authority8 = require("class-variance-authority");
1203
+ var import_radix_ui11 = require("radix-ui");
1204
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1205
+ var markerVariants = (0, import_class_variance_authority8.cva)(
1206
+ "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
1207
+ {
1208
+ variants: {
1209
+ variant: {
1210
+ default: "",
1211
+ separator: "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
1212
+ border: "border-b border-border pb-2"
1213
+ }
1214
+ }
1215
+ }
1216
+ );
1217
+ function Marker({
1218
+ className,
1219
+ variant = "default",
1220
+ asChild = false,
1221
+ ...props
1222
+ }) {
1223
+ const Comp = asChild ? import_radix_ui11.Slot.Root : "div";
1224
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1225
+ Comp,
1226
+ {
1227
+ "data-slot": "marker",
1228
+ "data-variant": variant,
1229
+ className: cn(markerVariants({ variant, className })),
1230
+ ...props
1231
+ }
1232
+ );
1233
+ }
1234
+ function MarkerIcon({ className, ...props }) {
1235
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1236
+ "span",
1237
+ {
1238
+ "data-slot": "marker-icon",
1239
+ "aria-hidden": "true",
1240
+ className: cn("size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
1241
+ ...props
1242
+ }
1243
+ );
1244
+ }
1245
+ function MarkerContent({ className, ...props }) {
1246
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1247
+ "span",
1248
+ {
1249
+ "data-slot": "marker-content",
1250
+ className: cn(
1251
+ "min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
1252
+ className
1253
+ ),
1254
+ ...props
1255
+ }
1256
+ );
1257
+ }
1258
+
1259
+ // src/components/ui/collapsible.tsx
1260
+ var import_radix_ui12 = require("radix-ui");
1261
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1262
+ function Collapsible({ ...props }) {
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui12.Collapsible.Root, { "data-slot": "collapsible", ...props });
1264
+ }
1265
+ function CollapsibleTrigger({
1266
+ ...props
1267
+ }) {
1268
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui12.Collapsible.CollapsibleTrigger, { "data-slot": "collapsible-trigger", ...props });
1269
+ }
1270
+ function CollapsibleContent({
1271
+ ...props
1272
+ }) {
1273
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_radix_ui12.Collapsible.CollapsibleContent, { "data-slot": "collapsible-content", ...props });
1274
+ }
1275
+
1276
+ // src/components/ui/message-scroller.tsx
1277
+ var React15 = require("react");
1278
+ var import_message_scroller = require("@shadcn/react/message-scroller");
1279
+ var import_lucide_react4 = require("lucide-react");
1280
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1281
+ function MessageScrollerProvider(props) {
1282
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_message_scroller.MessageScroller.Provider, { ...props });
1283
+ }
1284
+ function MessageScroller({
1285
+ className,
1286
+ ...props
1287
+ }) {
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1289
+ import_message_scroller.MessageScroller.Root,
1290
+ {
1291
+ "data-slot": "message-scroller",
1292
+ className: cn(
1293
+ "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
1294
+ className
1295
+ ),
1296
+ ...props
1297
+ }
1298
+ );
1299
+ }
1300
+ function MessageScrollerViewport({
1301
+ className,
1302
+ ...props
1303
+ }) {
1304
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1305
+ import_message_scroller.MessageScroller.Viewport,
1306
+ {
1307
+ "data-slot": "message-scroller-viewport",
1308
+ className: cn(
1309
+ "size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent",
1310
+ className
1311
+ ),
1312
+ ...props
1313
+ }
1314
+ );
1315
+ }
1316
+ function MessageScrollerContent({
1317
+ className,
1318
+ ...props
1319
+ }) {
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1321
+ import_message_scroller.MessageScroller.Content,
1322
+ {
1323
+ "data-slot": "message-scroller-content",
1324
+ className: cn("flex h-max min-h-full flex-col gap-6", className),
1325
+ ...props
1326
+ }
1327
+ );
1328
+ }
1329
+ function MessageScrollerItem({
1330
+ className,
1331
+ scrollAnchor = false,
1332
+ ...props
1333
+ }) {
1334
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1335
+ import_message_scroller.MessageScroller.Item,
1336
+ {
1337
+ "data-slot": "message-scroller-item",
1338
+ scrollAnchor,
1339
+ className: cn(
1340
+ "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
1341
+ className
1342
+ ),
1343
+ ...props
1344
+ }
1345
+ );
1346
+ }
1347
+ function MessageScrollerButton({
1348
+ direction = "end",
1349
+ className,
1350
+ children,
1351
+ render,
1352
+ variant = "secondary",
1353
+ size = "icon-sm",
1354
+ ...props
1355
+ }) {
1356
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1357
+ import_message_scroller.MessageScroller.Button,
1358
+ {
1359
+ "data-slot": "message-scroller-button",
1360
+ "data-direction": direction,
1361
+ "data-variant": variant,
1362
+ "data-size": size,
1363
+ direction,
1364
+ className: cn(
1365
+ "absolute inset-s-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
1366
+ className
1367
+ ),
1368
+ render: render ?? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { variant, size }),
1369
+ ...props,
1370
+ children: children ?? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react4.ArrowDownIcon, {}),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
1373
+ ] })
1374
+ }
1375
+ );
1376
+ }
1377
+
1378
+ // src/components/chat/ChatPanel.tsx
1379
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1380
+ var TOOL_LABELS = {
1381
+ get_document: "Reading document",
1382
+ get_section: "Reading section",
1383
+ set_document: "Saving whole email",
1384
+ set_section: "Replacing section",
1385
+ insert_section: "Adding section",
1386
+ remove_section: "Removing section",
1387
+ generate_image: "Generating image",
1388
+ list_open_comments: "Reading comments",
1389
+ resolve_comment: "Resolving comment"
1390
+ };
1391
+ var APPLY_COMMENTS_PROMPT = "Apply changes based on all open section comments. After each successful change mark the comment as resolved and summarize what you changed.";
1392
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
1393
+ "set_document",
1394
+ "set_section",
1395
+ "insert_section",
1396
+ "remove_section",
1397
+ "resolve_comment"
1398
+ ]);
1399
+ var SECTION_ARG = {
1400
+ set_section: "section_id",
1401
+ remove_section: "section_id",
1402
+ insert_section: "after_section_id"
1403
+ };
1404
+ function ToolMarker({ tool }) {
1405
+ const name = tool.type.slice(5);
1406
+ const done = tool.state === "output-available";
1407
+ const failed = tool.state === "output-error";
1408
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Marker, { className: cn(failed ? "text-destructive" : "text-panel-fg"), children: [
1409
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MarkerIcon, { children: failed ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.TriangleAlert, {}) : done ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Check, { className: "text-emerald-400" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Loader2, { className: "animate-spin" }) }),
1410
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(MarkerContent, { children: [
1411
+ TOOL_LABELS[name] ?? name,
1412
+ failed && tool.errorText ? `: ${tool.errorText}` : ""
1413
+ ] })
1414
+ ] });
1415
+ }
1416
+ function BusyMarker() {
1417
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Marker, { className: "text-panel-fg", children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MarkerIcon, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Loader2, { className: "animate-spin" }) }),
1419
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MarkerContent, { children: "Agent is working\u2026" })
1420
+ ] });
1421
+ }
1422
+ function ToolActivity({ tools }) {
1423
+ const running = tools.filter((t) => t.state !== "output-available" && t.state !== "output-error");
1424
+ const finished = tools.filter(
1425
+ (t) => t.state === "output-available" || t.state === "output-error"
1426
+ );
1427
+ const failedCount = finished.filter((t) => t.state === "output-error").length;
1428
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1 rounded-lg border border-panel-border bg-panel-elevated/60 p-2", children: [
1429
+ finished.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Collapsible, { defaultOpen: true, children: [
1430
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CollapsibleTrigger, { className: "group/col flex w-full items-center gap-1.5 text-left text-xs font-medium text-panel-fg hover:text-panel-fg", children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.ChevronRight, { className: "size-3.5 shrink-0 transition-transform group-data-[state=open]/col:rotate-90" }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: failedCount > 0 ? `Completed steps (${finished.length}, ${failedCount} failed)` : `Completed steps (${finished.length})` })
1433
+ ] }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CollapsibleContent, { className: "mt-1 space-y-0.5 pl-1", children: finished.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolMarker, { tool: t }, `f${i}`)) })
1435
+ ] }),
1436
+ running.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolMarker, { tool: t }, `r${i}`))
1437
+ ] });
1438
+ }
1439
+ function ChatPanel({
1440
+ docId,
1441
+ onBeforeSend,
1442
+ onAgentFinish,
1443
+ onLiveUpdate,
1444
+ onSectionEditStart,
1445
+ onSectionEditEnd
1446
+ }) {
1447
+ const [input, setInput] = (0, import_react5.useState)("");
1448
+ const taRef = (0, import_react5.useRef)(null);
1449
+ const times = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1450
+ const timeFor = (id) => {
1451
+ if (!times.current.has(id)) {
1452
+ times.current.set(
1453
+ id,
1454
+ (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
1455
+ );
1456
+ }
1457
+ return times.current.get(id);
1458
+ };
1459
+ const { messages, sendMessage, status, stop } = (0, import_react4.useChat)({
1460
+ transport: new import_ai.DefaultChatTransport({
1461
+ // Same-origin: the agent is a route handler in this app, not a separate service.
1462
+ api: "/api/chat",
1463
+ body: { docId }
1464
+ }),
1465
+ onFinish: onAgentFinish,
1466
+ onError: (e) => {
1467
+ console.error("chat error:", e);
1468
+ import_sonner2.toast.error("Agent chat error.");
1469
+ }
1470
+ });
1471
+ const firedTools = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
1472
+ const highlightStarted = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1473
+ const highlightEnded = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
1474
+ (0, import_react5.useEffect)(() => {
1475
+ for (const message of messages) {
1476
+ if (message.role !== "assistant") continue;
1477
+ message.parts.forEach((part, i) => {
1478
+ if (!part.type.startsWith("tool-")) return;
1479
+ const tool = part;
1480
+ const name = tool.type.slice(5);
1481
+ const key = `${message.id}:${i}`;
1482
+ const done = tool.state === "output-available" || tool.state === "output-error";
1483
+ const argName = SECTION_ARG[name];
1484
+ if (argName && !done && !highlightStarted.current.has(key)) {
1485
+ const sid = tool.input?.[argName];
1486
+ if (typeof sid === "string" && sid) {
1487
+ highlightStarted.current.set(key, sid);
1488
+ onSectionEditStart(sid);
1489
+ }
1490
+ }
1491
+ if (!done) return;
1492
+ const startedSid = highlightStarted.current.get(key);
1493
+ if (startedSid && !highlightEnded.current.has(key)) {
1494
+ highlightEnded.current.add(key);
1495
+ onSectionEditEnd(startedSid);
1496
+ }
1497
+ if (MUTATING_TOOLS.has(name) && !firedTools.current.has(key)) {
1498
+ firedTools.current.add(key);
1499
+ onLiveUpdate();
1500
+ }
1501
+ });
1502
+ }
1503
+ }, [messages, onLiveUpdate, onSectionEditStart, onSectionEditEnd]);
1504
+ const busy = status === "submitted" || status === "streaming";
1505
+ const lastMsg = messages[messages.length - 1];
1506
+ const toolRunning = lastMsg?.role === "assistant" && lastMsg.parts.some((p) => {
1507
+ if (!p.type.startsWith("tool-")) return false;
1508
+ const s = p.state;
1509
+ return s !== "output-available" && s !== "output-error";
1510
+ });
1511
+ const grow = () => {
1512
+ const el = taRef.current;
1513
+ if (!el) return;
1514
+ el.style.height = "auto";
1515
+ el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
1516
+ };
1517
+ const send = async (text) => {
1518
+ const trimmed = text.trim();
1519
+ if (!trimmed || busy) return;
1520
+ await onBeforeSend().catch(console.error);
1521
+ void sendMessage({ text: trimmed });
1522
+ setInput("");
1523
+ if (taRef.current) taRef.current.style.height = "auto";
1524
+ };
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex h-full flex-col", children: [
1526
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(MessageScroller, { className: "flex-1", children: [
1527
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerViewport, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(MessageScrollerContent, { className: "p-3", children: [
1528
+ messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Empty, { className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(EmptyHeader, { children: [
1529
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmptyMedia, { variant: "icon", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.MessageSquare, {}) }),
1530
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmptyTitle, { children: "Start a conversation with the agent" }),
1531
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmptyDescription, { children: "Describe the email (goal, tone, content) and paste your data \u2014 the agent will design the sections and images." })
1532
+ ] }) }) : messages.map((message, mi) => {
1533
+ const isUser = message.role === "user";
1534
+ const isLast = mi === messages.length - 1;
1535
+ const toolParts = message.parts.filter(
1536
+ (p) => p.type.startsWith("tool-")
1537
+ );
1538
+ const textParts = message.parts.filter(
1539
+ (p) => p.type === "text" && p.text.trim() !== ""
1540
+ );
1541
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerItem, { messageId: message.id, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cn("flex w-full gap-2", isUser && "flex-row-reverse"), children: [
1542
+ !isUser && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-brand/15 text-brand", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Sparkles, { className: "size-3.5" }) }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1544
+ "div",
1545
+ {
1546
+ className: cn(
1547
+ "flex min-w-0 flex-1 flex-col gap-1",
1548
+ isUser ? "items-end" : "items-start"
1549
+ ),
1550
+ children: [
1551
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(MessageContent, { children: [
1552
+ toolParts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToolActivity, { tools: toolParts }),
1553
+ textParts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1554
+ Bubble,
1555
+ {
1556
+ variant: isUser ? "default" : "muted",
1557
+ align: isUser ? "end" : "start",
1558
+ className: cn(
1559
+ isUser ? "[&_[data-slot=bubble-content]]:bg-brand [&_[data-slot=bubble-content]]:text-brand-fg" : "[&_[data-slot=bubble-content]]:border [&_[data-slot=bubble-content]]:border-panel-border [&_[data-slot=bubble-content]]:bg-panel-elevated [&_[data-slot=bubble-content]]:text-panel-fg [&_code]:!bg-white/12 [&_pre]:!bg-white/10 [&_a]:text-brand"
1560
+ ),
1561
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1562
+ BubbleContent,
1563
+ {
1564
+ className: isUser ? "whitespace-pre-wrap" : void 0,
1565
+ children: isUser && part.type === "text" ? part.text : part.type === "text" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Markdown, { children: part.text }) : null
1566
+ }
1567
+ )
1568
+ },
1569
+ i
1570
+ )),
1571
+ isLast && !isUser && busy && !toolRunning && textParts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BusyMarker, {})
1572
+ ] }) }),
1573
+ (textParts.length > 0 || toolParts.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "px-1 text-[10px] text-panel-muted-fg", children: timeFor(message.id) })
1574
+ ]
1575
+ }
1576
+ )
1577
+ ] }) }, message.id);
1578
+ }),
1579
+ busy && !toolRunning && lastMsg?.role !== "assistant" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BusyMarker, {})
1580
+ ] }) }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerButton, { direction: "end" })
1582
+ ] }) }),
1583
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "border-t border-panel-border p-3", children: [
1584
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1585
+ Button,
1586
+ {
1587
+ className: "mb-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
1588
+ disabled: busy,
1589
+ onClick: () => void send(APPLY_COMMENTS_PROMPT),
1590
+ children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Sparkles, {}),
1592
+ " Apply changes from comments"
1593
+ ]
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1597
+ "form",
1598
+ {
1599
+ onSubmit: (e) => {
1600
+ e.preventDefault();
1601
+ void send(input);
1602
+ },
1603
+ className: "rounded-xl border border-panel-border bg-panel-elevated transition-colors focus-within:border-brand/60",
1604
+ children: [
1605
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1606
+ Textarea,
1607
+ {
1608
+ ref: taRef,
1609
+ rows: 2,
1610
+ className: "max-h-40 min-h-14 resize-none border-0 bg-transparent px-3 pt-2.5 text-panel-fg shadow-none placeholder:text-panel-muted-fg focus-visible:ring-0",
1611
+ placeholder: "Describe what you want to change\u2026 (Enter = send, Shift+Enter = new line)",
1612
+ value: input,
1613
+ onChange: (e) => {
1614
+ setInput(e.target.value);
1615
+ grow();
1616
+ },
1617
+ onKeyDown: (e) => {
1618
+ if (e.key === "Enter" && !e.shiftKey) {
1619
+ e.preventDefault();
1620
+ void send(input);
1621
+ }
1622
+ }
1623
+ }
1624
+ ),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center justify-end gap-2 px-2 pb-2", children: [
1626
+ busy && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1627
+ Button,
1628
+ {
1629
+ type: "button",
1630
+ variant: "outline",
1631
+ size: "sm",
1632
+ className: "border-panel-border bg-transparent text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
1633
+ onClick: () => void stop(),
1634
+ children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Square, {}),
1636
+ " Stop"
1637
+ ]
1638
+ }
1639
+ ),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1641
+ Button,
1642
+ {
1643
+ type: "submit",
1644
+ size: "sm",
1645
+ disabled: busy || !input.trim(),
1646
+ className: "bg-brand text-brand-fg hover:bg-brand/90",
1647
+ children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Send, {}),
1649
+ " Send"
1650
+ ]
1651
+ }
1652
+ )
1653
+ ] })
1654
+ ]
1655
+ }
1656
+ )
1657
+ ] })
1658
+ ] });
1659
+ }
1660
+
1661
+ // src/components/comments/CanvasComments.tsx
1662
+ var import_react6 = require("@grapesjs/react");
1663
+ var import_react7 = require("react");
1664
+ var import_lucide_react6 = require("lucide-react");
1665
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1666
+ var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
1667
+ var keyOfTarget = (t) => t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`;
1668
+ function CanvasComments({
1669
+ docId,
1670
+ composeTarget,
1671
+ onComposeConsumed,
1672
+ refreshSignal,
1673
+ onOpenCountChange
1674
+ }) {
1675
+ const commentStore = useCommentStore();
1676
+ const editor = (0, import_react6.useEditorMaybe)();
1677
+ const containerRef = (0, import_react7.useRef)(null);
1678
+ const [comments, setComments] = (0, import_react7.useState)([]);
1679
+ const [positions, setPositions] = (0, import_react7.useState)({});
1680
+ const [activeKey, setActiveKey] = (0, import_react7.useState)(null);
1681
+ const [composeInfo, setComposeInfo] = (0, import_react7.useState)(null);
1682
+ const [body, setBody] = (0, import_react7.useState)("");
1683
+ const reload = (0, import_react7.useCallback)(() => {
1684
+ commentStore.list(docId).then(setComments).catch(console.error);
1685
+ }, [docId]);
1686
+ (0, import_react7.useEffect)(() => {
1687
+ reload();
1688
+ }, [reload, refreshSignal]);
1689
+ const open = (0, import_react7.useMemo)(() => comments.filter((c) => c.status === "open"), [comments]);
1690
+ (0, import_react7.useEffect)(() => {
1691
+ onOpenCountChange(open.length);
1692
+ }, [open.length, onOpenCountChange]);
1693
+ const pins = (0, import_react7.useMemo)(() => {
1694
+ const map = /* @__PURE__ */ new Map();
1695
+ for (const c of open) {
1696
+ const k = keyOf(c);
1697
+ const arr = map.get(k) ?? [];
1698
+ arr.push(c);
1699
+ map.set(k, arr);
1700
+ }
1701
+ return [...map.entries()].map(([key, list]) => ({ key, list }));
1702
+ }, [open]);
1703
+ const openRef = (0, import_react7.useRef)(open);
1704
+ openRef.current = open;
1705
+ const activeRef = (0, import_react7.useRef)(activeKey);
1706
+ activeRef.current = activeKey;
1707
+ const positionsRef = (0, import_react7.useRef)({});
1708
+ const recompute = (0, import_react7.useCallback)(() => {
1709
+ const ed = editor;
1710
+ const container = containerRef.current;
1711
+ if (!ed || !container) return;
1712
+ const frame = ed.Canvas.getFrameEl?.();
1713
+ const cdoc = ed.Canvas.getDocument?.();
1714
+ if (!frame || !cdoc) return;
1715
+ const cRect = container.getBoundingClientRect();
1716
+ const fRect = frame.getBoundingClientRect();
1717
+ const zoom = frame.offsetWidth ? fRect.width / frame.offsetWidth : 1;
1718
+ const keys = new Set(openRef.current.map(keyOf));
1719
+ if (activeRef.current) keys.add(activeRef.current);
1720
+ const next = {};
1721
+ keys.forEach((k) => {
1722
+ const el = cdoc.querySelector(`.${k}`);
1723
+ if (!el) return;
1724
+ const r = el.getBoundingClientRect();
1725
+ next[k] = {
1726
+ top: fRect.top - cRect.top + r.top * zoom + 4,
1727
+ left: fRect.left - cRect.left + (r.left + r.width) * zoom + 8
1728
+ };
1729
+ });
1730
+ if (JSON.stringify(positionsRef.current) !== JSON.stringify(next)) {
1731
+ positionsRef.current = next;
1732
+ setPositions(next);
1733
+ }
1734
+ }, [editor]);
1735
+ (0, import_react7.useEffect)(() => {
1736
+ const ed = editor;
1737
+ if (!ed) return;
1738
+ const evts = [
1739
+ "canvas:update",
1740
+ "update",
1741
+ "change:device",
1742
+ "load",
1743
+ "component:update",
1744
+ "component:mount"
1745
+ ];
1746
+ evts.forEach((e) => ed.on(e, recompute));
1747
+ const win = ed.Canvas.getWindow?.();
1748
+ win?.addEventListener("scroll", recompute, true);
1749
+ window.addEventListener("resize", recompute);
1750
+ const id = window.setInterval(recompute, 500);
1751
+ return () => {
1752
+ evts.forEach((e) => ed.off(e, recompute));
1753
+ win?.removeEventListener("scroll", recompute, true);
1754
+ window.removeEventListener("resize", recompute);
1755
+ window.clearInterval(id);
1756
+ };
1757
+ }, [editor, recompute]);
1758
+ (0, import_react7.useEffect)(() => {
1759
+ recompute();
1760
+ }, [comments, activeKey, recompute]);
1761
+ (0, import_react7.useEffect)(() => {
1762
+ if (!composeTarget) return;
1763
+ setComposeInfo(composeTarget);
1764
+ setActiveKey(keyOfTarget(composeTarget));
1765
+ setBody("");
1766
+ requestAnimationFrame(recompute);
1767
+ }, [composeTarget, recompute]);
1768
+ const activeComments = activeKey ? open.filter((c) => keyOf(c) === activeKey) : [];
1769
+ const activeTarget = (() => {
1770
+ if (!activeKey) return null;
1771
+ const c = activeComments[0];
1772
+ if (c) return { sectionId: c.sectionId, objectId: c.objectId, objectLabel: c.objectLabel };
1773
+ if (composeInfo && keyOfTarget(composeInfo) === activeKey) return composeInfo;
1774
+ return null;
1775
+ })();
1776
+ const activePos = activeKey ? positions[activeKey] : void 0;
1777
+ const lastAnchorPos = (0, import_react7.useRef)({ top: 0, left: 0 });
1778
+ if (activePos) lastAnchorPos.current = activePos;
1779
+ const anchorPos = activePos ?? lastAnchorPos.current;
1780
+ const closePopover = (0, import_react7.useCallback)(() => {
1781
+ setActiveKey(null);
1782
+ setBody("");
1783
+ if (composeInfo) {
1784
+ setComposeInfo(null);
1785
+ onComposeConsumed();
1786
+ }
1787
+ }, [composeInfo, onComposeConsumed]);
1788
+ (0, import_react7.useEffect)(() => {
1789
+ if (!activeKey || !editor) return;
1790
+ const doc = editor.Canvas.getDocument?.();
1791
+ if (!doc) return;
1792
+ const onDown = () => closePopover();
1793
+ doc.addEventListener("mousedown", onDown, true);
1794
+ return () => doc.removeEventListener("mousedown", onDown, true);
1795
+ }, [activeKey, editor, closePopover]);
1796
+ const submit = async () => {
1797
+ if (!activeTarget || !body.trim()) return;
1798
+ try {
1799
+ await commentStore.add(docId, activeTarget, body.trim());
1800
+ setBody("");
1801
+ if (composeInfo) {
1802
+ setComposeInfo(null);
1803
+ onComposeConsumed();
1804
+ }
1805
+ reload();
1806
+ } catch (e) {
1807
+ console.error(e);
1808
+ }
1809
+ };
1810
+ const resolve = async (id) => {
1811
+ try {
1812
+ await commentStore.resolve(id);
1813
+ reload();
1814
+ } catch (e) {
1815
+ console.error(e);
1816
+ }
1817
+ };
1818
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { ref: containerRef, className: "pointer-events-none absolute inset-0 z-20 overflow-hidden", children: [
1819
+ pins.map(({ key, list }) => {
1820
+ const pos = positions[key];
1821
+ if (!pos) return null;
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1823
+ "button",
1824
+ {
1825
+ type: "button",
1826
+ onClick: () => {
1827
+ setActiveKey((k) => k === key ? null : key);
1828
+ setBody("");
1829
+ },
1830
+ style: { top: pos.top, left: pos.left },
1831
+ 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",
1832
+ title: `${list.length} comment(s)`,
1833
+ children: list.length
1834
+ },
1835
+ key
1836
+ );
1837
+ }),
1838
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Popover, { open: !!activeKey && !!activePos, onOpenChange: (o) => !o && closePopover(), children: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1840
+ "div",
1841
+ {
1842
+ style: {
1843
+ position: "absolute",
1844
+ top: anchorPos.top,
1845
+ left: anchorPos.left,
1846
+ width: 1,
1847
+ height: 1
1848
+ }
1849
+ }
1850
+ ) }),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1852
+ PopoverContent,
1853
+ {
1854
+ side: "right",
1855
+ align: "start",
1856
+ className: "pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0",
1857
+ children: [
1858
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground", children: [
1859
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "min-w-0", children: activeTarget?.objectId ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1860
+ "Comment on",
1861
+ " ",
1862
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "font-medium text-foreground", children: activeTarget.objectLabel })
1863
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
1864
+ "Comment on the whole section",
1865
+ " ",
1866
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("code", { className: "font-mono", children: activeTarget?.sectionId })
1867
+ ] }) }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1869
+ "button",
1870
+ {
1871
+ type: "button",
1872
+ onClick: closePopover,
1873
+ "aria-label": "Close",
1874
+ className: "-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1875
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react6.X, { className: "size-4" })
1876
+ }
1877
+ )
1878
+ ] }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "max-h-64 space-y-2 overflow-y-auto p-3", children: [
1880
+ activeComments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-xs text-muted-foreground", children: "No comments yet \u2014 add the first one." }),
1881
+ activeComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2 text-sm", children: [
1882
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "whitespace-pre-wrap", children: c.body }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1884
+ "button",
1885
+ {
1886
+ onClick: () => void resolve(c.id),
1887
+ className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
1888
+ children: [
1889
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react6.Check, { className: "size-3" }),
1890
+ " Resolve"
1891
+ ]
1892
+ }
1893
+ )
1894
+ ] }, c.id))
1895
+ ] }),
1896
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "border-t p-3", children: [
1897
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1898
+ Textarea,
1899
+ {
1900
+ autoFocus: true,
1901
+ value: body,
1902
+ onChange: (e) => setBody(e.target.value),
1903
+ placeholder: "Add a comment\u2026 (\u2318/Ctrl+Enter to send)",
1904
+ className: "min-h-14 resize-none text-sm",
1905
+ onKeyDown: (e) => {
1906
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
1907
+ e.preventDefault();
1908
+ void submit();
1909
+ }
1910
+ }
1911
+ }
1912
+ ),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Button, { size: "sm", disabled: !body.trim(), onClick: () => void submit(), children: [
1914
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react6.MessageSquarePlus, {}),
1915
+ " Add"
1916
+ ] }) })
1917
+ ] })
1918
+ ]
1919
+ }
1920
+ )
1921
+ ] })
1922
+ ] });
1923
+ }
1924
+ // Annotate the CommonJS export names for ESM import in node:
1925
+ 0 && (module.exports = {
1926
+ CanvasComments,
1927
+ ChatPanel,
1928
+ EditorHeader,
1929
+ EditorStoreProvider,
1930
+ Markdown,
1931
+ STARTER_MJML,
1932
+ useCommentStore,
1933
+ useDocumentStore
1934
+ });
1935
+ //# sourceMappingURL=index.cjs.map