@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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/canvas.cjs +1099 -0
- package/dist/canvas.cjs.map +1 -0
- package/dist/canvas.d.cts +49 -0
- package/dist/canvas.d.ts +49 -0
- package/dist/canvas.js +629 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chunk-LSF33PSW.js +1649 -0
- package/dist/chunk-LSF33PSW.js.map +1 -0
- package/dist/chunk-V6RR4VRD.js +340 -0
- package/dist/chunk-V6RR4VRD.js.map +1 -0
- package/dist/index.cjs +1935 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/ui.cjs +384 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +47 -0
- package/dist/ui.d.ts +47 -0
- package/dist/ui.js +42 -0
- package/dist/ui.js.map +1 -0
- package/package.json +92 -0
- package/src/components/editor/editor-theme.css +411 -0
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
|