@easybits.cloud/html-tailwind-generator 0.1.5 → 0.2.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/{src/images/enrichImages.ts → dist/chunk-5HSVOF2J.js} +65 -53
- package/dist/chunk-5HSVOF2J.js.map +1 -0
- package/{src/iframeScript.ts → dist/chunk-5TYGSZAF.js} +259 -10
- package/dist/chunk-5TYGSZAF.js.map +1 -0
- package/{src/refine.ts → dist/chunk-GMJR2GXL.js} +30 -60
- package/dist/chunk-GMJR2GXL.js.map +1 -0
- package/dist/chunk-LQ65H4AO.js +41 -0
- package/dist/chunk-LQ65H4AO.js.map +1 -0
- package/{src/generate.ts → dist/chunk-PK26CWDO.js} +67 -108
- package/dist/chunk-PK26CWDO.js.map +1 -0
- package/dist/chunk-RTGCZUNJ.js +1 -0
- package/dist/chunk-RTGCZUNJ.js.map +1 -0
- package/dist/chunk-XM3D3TTJ.js +852 -0
- package/dist/chunk-XM3D3TTJ.js.map +1 -0
- package/dist/components.d.ts +57 -0
- package/dist/components.js +14 -0
- package/dist/components.js.map +1 -0
- package/dist/deploy.d.ts +39 -0
- package/dist/deploy.js +10 -0
- package/dist/deploy.js.map +1 -0
- package/dist/generate.d.ts +41 -0
- package/dist/generate.js +14 -0
- package/dist/generate.js.map +1 -0
- package/dist/images.d.ts +30 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/refine.d.ts +32 -0
- package/dist/refine.js +10 -0
- package/dist/refine.js.map +1 -0
- package/dist/themes-DOoj19c8.d.ts +35 -0
- package/dist/types-Flpl4wDs.d.ts +31 -0
- package/package.json +53 -50
- package/src/buildHtml.ts +0 -78
- package/src/components/Canvas.tsx +0 -162
- package/src/components/CodeEditor.tsx +0 -239
- package/src/components/FloatingToolbar.tsx +0 -350
- package/src/components/SectionList.tsx +0 -217
- package/src/components/index.ts +0 -4
- package/src/deploy.ts +0 -73
- package/src/images/dalleImages.ts +0 -29
- package/src/images/index.ts +0 -3
- package/src/images/pexels.ts +0 -27
- package/src/index.ts +0 -58
- package/src/themes.ts +0 -204
- package/src/types.ts +0 -30
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LANDING_THEMES,
|
|
3
|
+
buildPreviewHtml
|
|
4
|
+
} from "./chunk-5TYGSZAF.js";
|
|
5
|
+
|
|
6
|
+
// src/components/Canvas.tsx
|
|
7
|
+
import { useRef, useEffect, useCallback, useState, forwardRef, useImperativeHandle } from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var Canvas = forwardRef(function Canvas2({ sections, theme, onMessage, iframeRectRef }, ref) {
|
|
10
|
+
const iframeRef = useRef(null);
|
|
11
|
+
const [ready, setReady] = useState(false);
|
|
12
|
+
const knownSectionsRef = useRef(/* @__PURE__ */ new Map());
|
|
13
|
+
const initializedRef = useRef(false);
|
|
14
|
+
const postToIframe = useCallback((msg) => {
|
|
15
|
+
iframeRef.current?.contentWindow?.postMessage(msg, "*");
|
|
16
|
+
}, []);
|
|
17
|
+
useImperativeHandle(ref, () => ({
|
|
18
|
+
scrollToSection(id) {
|
|
19
|
+
postToIframe({ action: "scroll-to-section", id });
|
|
20
|
+
},
|
|
21
|
+
postMessage(msg) {
|
|
22
|
+
postToIframe(msg);
|
|
23
|
+
}
|
|
24
|
+
}), [postToIframe]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const iframe = iframeRef.current;
|
|
27
|
+
if (!iframe || initializedRef.current) return;
|
|
28
|
+
initializedRef.current = true;
|
|
29
|
+
const html2 = buildPreviewHtml([]);
|
|
30
|
+
const doc = iframe.contentDocument;
|
|
31
|
+
if (!doc) return;
|
|
32
|
+
doc.open();
|
|
33
|
+
doc.write(html2);
|
|
34
|
+
doc.close();
|
|
35
|
+
}, []);
|
|
36
|
+
const handleReady = useCallback(() => {
|
|
37
|
+
setReady(true);
|
|
38
|
+
const sorted = [...sections].sort((a, b) => a.order - b.order);
|
|
39
|
+
for (const s of sorted) {
|
|
40
|
+
postToIframe({ action: "add-section", id: s.id, html: s.html });
|
|
41
|
+
knownSectionsRef.current.set(s.id, s.html);
|
|
42
|
+
}
|
|
43
|
+
}, [sections, postToIframe]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!ready) return;
|
|
46
|
+
const known = knownSectionsRef.current;
|
|
47
|
+
const currentIds = new Set(sections.map((s) => s.id));
|
|
48
|
+
const sorted = [...sections].sort((a, b) => a.order - b.order);
|
|
49
|
+
for (const s of sorted) {
|
|
50
|
+
if (!known.has(s.id)) {
|
|
51
|
+
postToIframe({ action: "add-section", id: s.id, html: s.html });
|
|
52
|
+
known.set(s.id, s.html);
|
|
53
|
+
} else if (known.get(s.id) !== s.html) {
|
|
54
|
+
postToIframe({ action: "update-section", id: s.id, html: s.html });
|
|
55
|
+
known.set(s.id, s.html);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const id of known.keys()) {
|
|
59
|
+
if (!currentIds.has(id)) {
|
|
60
|
+
postToIframe({ action: "remove-section", id });
|
|
61
|
+
known.delete(id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const knownOrder = [...known.keys()];
|
|
65
|
+
const desiredOrder = sorted.map((s) => s.id);
|
|
66
|
+
if (JSON.stringify(knownOrder) !== JSON.stringify(desiredOrder)) {
|
|
67
|
+
postToIframe({ action: "reorder-sections", order: desiredOrder });
|
|
68
|
+
}
|
|
69
|
+
}, [sections, ready, postToIframe]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!ready) return;
|
|
72
|
+
postToIframe({ action: "set-theme", theme: theme || "default" });
|
|
73
|
+
}, [theme, ready, postToIframe]);
|
|
74
|
+
const updateRect = useCallback(() => {
|
|
75
|
+
if (iframeRef.current) {
|
|
76
|
+
iframeRectRef.current = iframeRef.current.getBoundingClientRect();
|
|
77
|
+
}
|
|
78
|
+
}, [iframeRectRef]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
updateRect();
|
|
81
|
+
window.addEventListener("resize", updateRect);
|
|
82
|
+
window.addEventListener("scroll", updateRect, true);
|
|
83
|
+
return () => {
|
|
84
|
+
window.removeEventListener("resize", updateRect);
|
|
85
|
+
window.removeEventListener("scroll", updateRect, true);
|
|
86
|
+
};
|
|
87
|
+
}, [updateRect]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
function handleMessage(e) {
|
|
90
|
+
const data = e.data;
|
|
91
|
+
if (!data || typeof data.type !== "string") return;
|
|
92
|
+
if (data.type === "ready") {
|
|
93
|
+
handleReady();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (["element-selected", "text-edited", "element-deselected", "section-html-updated"].includes(
|
|
97
|
+
data.type
|
|
98
|
+
)) {
|
|
99
|
+
updateRect();
|
|
100
|
+
onMessage(data);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
window.addEventListener("message", handleMessage);
|
|
104
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
105
|
+
}, [onMessage, updateRect, handleReady]);
|
|
106
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 bg-gray-100 rounded-xl overflow-hidden border-2 border-gray-200 relative", children: [
|
|
107
|
+
/* @__PURE__ */ jsx(
|
|
108
|
+
"iframe",
|
|
109
|
+
{
|
|
110
|
+
ref: iframeRef,
|
|
111
|
+
title: "Landing preview",
|
|
112
|
+
className: "w-full h-full border-0",
|
|
113
|
+
sandbox: "allow-scripts allow-same-origin",
|
|
114
|
+
style: { minHeight: "calc(100vh - 120px)" }
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
!ready && sections.length > 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80", children: /* @__PURE__ */ jsx("span", { className: "w-6 h-6 border-2 border-gray-400 border-t-gray-800 rounded-full animate-spin" }) })
|
|
118
|
+
] });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// src/components/SectionList.tsx
|
|
122
|
+
import { useRef as useRef2, useState as useState2 } from "react";
|
|
123
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
124
|
+
function SectionList({
|
|
125
|
+
sections,
|
|
126
|
+
selectedSectionId,
|
|
127
|
+
theme,
|
|
128
|
+
customColors,
|
|
129
|
+
onThemeChange,
|
|
130
|
+
onCustomColorChange,
|
|
131
|
+
onSelect,
|
|
132
|
+
onOpenCode,
|
|
133
|
+
onReorder,
|
|
134
|
+
onDelete,
|
|
135
|
+
onRename,
|
|
136
|
+
onAdd
|
|
137
|
+
}) {
|
|
138
|
+
const sorted = [...sections].sort((a, b) => a.order - b.order);
|
|
139
|
+
const colorInputRef = useRef2(null);
|
|
140
|
+
const [editingId, setEditingId] = useState2(null);
|
|
141
|
+
const [editingLabel, setEditingLabel] = useState2("");
|
|
142
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-56 shrink-0 flex flex-col bg-white border-r-2 border-gray-200 overflow-y-auto", children: [
|
|
143
|
+
/* @__PURE__ */ jsxs2("div", { className: "p-3 border-b border-gray-200", children: [
|
|
144
|
+
/* @__PURE__ */ jsx2("h3", { className: "text-xs font-black uppercase tracking-wider text-gray-500 mb-2", children: "Tema" }),
|
|
145
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-1.5 flex-wrap", children: [
|
|
146
|
+
LANDING_THEMES.map((t) => /* @__PURE__ */ jsx2(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
onClick: () => onThemeChange(t.id),
|
|
150
|
+
title: t.label,
|
|
151
|
+
className: `w-6 h-6 rounded-full border-2 transition-all ${theme === t.id ? "border-black scale-110 shadow-sm" : "border-gray-300 hover:border-gray-400"}`,
|
|
152
|
+
style: { backgroundColor: t.colors.primary }
|
|
153
|
+
},
|
|
154
|
+
t.id
|
|
155
|
+
)),
|
|
156
|
+
/* @__PURE__ */ jsx2(
|
|
157
|
+
"button",
|
|
158
|
+
{
|
|
159
|
+
onClick: () => colorInputRef.current?.click(),
|
|
160
|
+
title: "Color personalizado",
|
|
161
|
+
className: `w-6 h-6 rounded-full border-2 transition-all relative overflow-hidden ${theme === "custom" ? "border-black scale-110 shadow-sm" : "border-gray-300 hover:border-gray-400"}`,
|
|
162
|
+
style: theme === "custom" && customColors?.primary ? { backgroundColor: customColors.primary } : void 0,
|
|
163
|
+
children: theme !== "custom" && /* @__PURE__ */ jsx2(
|
|
164
|
+
"span",
|
|
165
|
+
{
|
|
166
|
+
className: "absolute inset-0 rounded-full",
|
|
167
|
+
style: { background: "conic-gradient(#ef4444, #eab308, #22c55e, #3b82f6, #a855f7, #ef4444)" }
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
/* @__PURE__ */ jsx2(
|
|
173
|
+
"input",
|
|
174
|
+
{
|
|
175
|
+
ref: colorInputRef,
|
|
176
|
+
type: "color",
|
|
177
|
+
value: customColors?.primary || "#6366f1",
|
|
178
|
+
onChange: (e) => onCustomColorChange?.({ primary: e.target.value }),
|
|
179
|
+
className: "sr-only"
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
] }),
|
|
183
|
+
theme === "custom" && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
184
|
+
{ key: "primary", label: "Pri", fallback: "#6366f1" },
|
|
185
|
+
{ key: "secondary", label: "Sec", fallback: "#f59e0b" },
|
|
186
|
+
{ key: "accent", label: "Acc", fallback: "#06b6d4" },
|
|
187
|
+
{ key: "surface", label: "Sur", fallback: "#ffffff" }
|
|
188
|
+
].map((c) => /* @__PURE__ */ jsxs2("label", { className: "flex flex-col items-center gap-0.5 cursor-pointer", children: [
|
|
189
|
+
/* @__PURE__ */ jsx2(
|
|
190
|
+
"input",
|
|
191
|
+
{
|
|
192
|
+
type: "color",
|
|
193
|
+
value: customColors?.[c.key] || c.fallback,
|
|
194
|
+
onChange: (e) => onCustomColorChange?.({ [c.key]: e.target.value }),
|
|
195
|
+
className: "w-5 h-5 rounded border border-gray-300 cursor-pointer p-0 [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:border-none [&::-webkit-color-swatch]:rounded"
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[9px] font-bold text-gray-400 uppercase", children: c.label })
|
|
199
|
+
] }, c.key)) })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsx2("div", { className: "p-3 border-b border-gray-200", children: /* @__PURE__ */ jsx2("h3", { className: "text-xs font-black uppercase tracking-wider text-gray-500", children: "Secciones" }) }),
|
|
202
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-y-auto py-1", children: sorted.map((section, i) => /* @__PURE__ */ jsxs2(
|
|
203
|
+
"div",
|
|
204
|
+
{
|
|
205
|
+
onClick: () => onSelect(section.id),
|
|
206
|
+
className: `group flex items-center gap-2 px-3 py-2 cursor-pointer transition-colors ${selectedSectionId === section.id ? "bg-blue-50 border-l-2 border-blue-500" : "hover:bg-gray-50 border-l-2 border-transparent"}`,
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] font-mono text-gray-400 w-4 text-right", children: i + 1 }),
|
|
209
|
+
editingId === section.id ? /* @__PURE__ */ jsx2(
|
|
210
|
+
"input",
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
value: editingLabel,
|
|
214
|
+
onChange: (e) => setEditingLabel(e.target.value),
|
|
215
|
+
onBlur: () => {
|
|
216
|
+
if (editingLabel.trim()) onRename(section.id, editingLabel.trim());
|
|
217
|
+
setEditingId(null);
|
|
218
|
+
},
|
|
219
|
+
onKeyDown: (e) => {
|
|
220
|
+
if (e.key === "Enter") {
|
|
221
|
+
if (editingLabel.trim()) onRename(section.id, editingLabel.trim());
|
|
222
|
+
setEditingId(null);
|
|
223
|
+
} else if (e.key === "Escape") {
|
|
224
|
+
setEditingId(null);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
className: "text-sm font-bold flex-1 min-w-0 bg-transparent border-b border-blue-500 outline-none px-0 py-0",
|
|
228
|
+
autoFocus: true,
|
|
229
|
+
onClick: (e) => e.stopPropagation()
|
|
230
|
+
}
|
|
231
|
+
) : /* @__PURE__ */ jsx2(
|
|
232
|
+
"span",
|
|
233
|
+
{
|
|
234
|
+
className: "text-sm font-bold truncate flex-1",
|
|
235
|
+
onDoubleClick: (e) => {
|
|
236
|
+
e.stopPropagation();
|
|
237
|
+
setEditingId(section.id);
|
|
238
|
+
setEditingLabel(section.label);
|
|
239
|
+
},
|
|
240
|
+
children: section.label
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
/* @__PURE__ */ jsxs2("div", { className: "opacity-0 group-hover:opacity-100 flex gap-0.5", children: [
|
|
244
|
+
/* @__PURE__ */ jsx2(
|
|
245
|
+
"button",
|
|
246
|
+
{
|
|
247
|
+
onClick: (e) => {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
onOpenCode(section.id);
|
|
250
|
+
},
|
|
251
|
+
className: "w-5 h-5 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-200",
|
|
252
|
+
title: "Editar HTML",
|
|
253
|
+
children: /* @__PURE__ */ jsx2("svg", { className: "w-3 h-3", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z" }) })
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
i > 0 && /* @__PURE__ */ jsx2(
|
|
257
|
+
"button",
|
|
258
|
+
{
|
|
259
|
+
onClick: (e) => {
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
onReorder(i, i - 1);
|
|
262
|
+
},
|
|
263
|
+
className: "w-5 h-5 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-200 text-[10px]",
|
|
264
|
+
children: "\u2191"
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
i < sorted.length - 1 && /* @__PURE__ */ jsx2(
|
|
268
|
+
"button",
|
|
269
|
+
{
|
|
270
|
+
onClick: (e) => {
|
|
271
|
+
e.stopPropagation();
|
|
272
|
+
onReorder(i, i + 1);
|
|
273
|
+
},
|
|
274
|
+
className: "w-5 h-5 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 hover:bg-gray-200 text-[10px]",
|
|
275
|
+
children: "\u2193"
|
|
276
|
+
}
|
|
277
|
+
),
|
|
278
|
+
/* @__PURE__ */ jsx2(
|
|
279
|
+
"button",
|
|
280
|
+
{
|
|
281
|
+
onClick: (e) => {
|
|
282
|
+
e.stopPropagation();
|
|
283
|
+
onDelete(section.id);
|
|
284
|
+
},
|
|
285
|
+
className: "w-5 h-5 flex items-center justify-center rounded text-gray-400 hover:text-red-600 hover:bg-red-50 text-[10px]",
|
|
286
|
+
title: "Eliminar seccion",
|
|
287
|
+
children: "\u2715"
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
] })
|
|
291
|
+
]
|
|
292
|
+
},
|
|
293
|
+
section.id
|
|
294
|
+
)) }),
|
|
295
|
+
/* @__PURE__ */ jsx2("div", { className: "p-3 border-t border-gray-200", children: /* @__PURE__ */ jsx2(
|
|
296
|
+
"button",
|
|
297
|
+
{
|
|
298
|
+
onClick: onAdd,
|
|
299
|
+
className: "w-full text-center py-2 text-sm font-bold text-blue-600 hover:bg-blue-50 rounded-lg transition-colors",
|
|
300
|
+
children: "+ Agregar seccion"
|
|
301
|
+
}
|
|
302
|
+
) })
|
|
303
|
+
] });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/components/FloatingToolbar.tsx
|
|
307
|
+
import { useState as useState3, useRef as useRef3, useEffect as useEffect2 } from "react";
|
|
308
|
+
import { HiSparkles } from "react-icons/hi2";
|
|
309
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
310
|
+
var STYLE_PRESETS = [
|
|
311
|
+
{ label: "Minimal", icon: "\u25CB", instruction: "Redisena esta seccion con estetica minimal: mucho espacio en blanco, tipografia limpia, sin bordes ni sombras innecesarias. Manten el mismo contenido." },
|
|
312
|
+
{ label: "Cards", icon: "\u25A6", instruction: "Redisena esta seccion usando layout de cards en grid: cada item en su propia card con padding, sombra sutil y bordes redondeados. Manten el mismo contenido." },
|
|
313
|
+
{ label: "Bold", icon: "\u25A0", instruction: "Redisena esta seccion con estilo bold/brutalist: tipografia grande y gruesa, colores de alto contraste, bordes solidos, sin gradientes. Manten el mismo contenido." },
|
|
314
|
+
{ label: "Glass", icon: "\u25C7", instruction: "Redisena esta seccion con glassmorphism: fondos translucidos con backdrop-blur, bordes sutiles blancos, sombras suaves. Usa un fondo oscuro o con gradiente detras. Manten el mismo contenido." },
|
|
315
|
+
{ label: "Dark", icon: "\u25CF", instruction: "Redisena esta seccion con fondo oscuro (#111 o similar), texto claro, acentos de color vibrantes. Manten el mismo contenido." }
|
|
316
|
+
];
|
|
317
|
+
function FloatingToolbar({
|
|
318
|
+
selection,
|
|
319
|
+
iframeRect,
|
|
320
|
+
onRefine,
|
|
321
|
+
onMoveUp,
|
|
322
|
+
onMoveDown,
|
|
323
|
+
onDelete,
|
|
324
|
+
onClose,
|
|
325
|
+
onViewCode,
|
|
326
|
+
onUpdateAttribute,
|
|
327
|
+
isRefining
|
|
328
|
+
}) {
|
|
329
|
+
const [prompt, setPrompt] = useState3("");
|
|
330
|
+
const [showCode, setShowCode] = useState3(false);
|
|
331
|
+
const [refImage, setRefImage] = useState3(null);
|
|
332
|
+
const [refImageName, setRefImageName] = useState3(null);
|
|
333
|
+
const inputRef = useRef3(null);
|
|
334
|
+
const fileInputRef = useRef3(null);
|
|
335
|
+
const toolbarRef = useRef3(null);
|
|
336
|
+
const [imgSrc, setImgSrc] = useState3("");
|
|
337
|
+
const [imgAlt, setImgAlt] = useState3("");
|
|
338
|
+
const [linkHref, setLinkHref] = useState3("");
|
|
339
|
+
useEffect2(() => {
|
|
340
|
+
setPrompt("");
|
|
341
|
+
setShowCode(false);
|
|
342
|
+
setRefImage(null);
|
|
343
|
+
setRefImageName(null);
|
|
344
|
+
}, [selection?.sectionId]);
|
|
345
|
+
useEffect2(() => {
|
|
346
|
+
if (selection?.attrs) {
|
|
347
|
+
setImgSrc(selection.attrs.src || "");
|
|
348
|
+
setImgAlt(selection.attrs.alt || "");
|
|
349
|
+
setLinkHref(selection.attrs.href || "");
|
|
350
|
+
}
|
|
351
|
+
}, [selection?.attrs, selection?.elementPath]);
|
|
352
|
+
useEffect2(() => {
|
|
353
|
+
function handleKey(e) {
|
|
354
|
+
if (e.key === "Escape") onClose();
|
|
355
|
+
}
|
|
356
|
+
document.addEventListener("keydown", handleKey);
|
|
357
|
+
return () => document.removeEventListener("keydown", handleKey);
|
|
358
|
+
}, [onClose]);
|
|
359
|
+
if (!selection || !selection.rect || !iframeRect) return null;
|
|
360
|
+
const toolbarWidth = toolbarRef.current?.offsetWidth || 480;
|
|
361
|
+
const toolbarHeight = toolbarRef.current?.offsetHeight || 60;
|
|
362
|
+
const top = iframeRect.top + selection.rect.top + selection.rect.height + 8;
|
|
363
|
+
const left = iframeRect.left + selection.rect.left;
|
|
364
|
+
const clampedLeft = Math.max(8, Math.min(left, window.innerWidth - toolbarWidth - 8));
|
|
365
|
+
const showAbove = top + toolbarHeight + 8 > window.innerHeight;
|
|
366
|
+
const finalTop = Math.max(8, showAbove ? iframeRect.top + selection.rect.top - toolbarHeight - 8 : top);
|
|
367
|
+
function handleSubmit(e) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
if (!prompt.trim() || isRefining) return;
|
|
370
|
+
onRefine(prompt.trim(), refImage || void 0);
|
|
371
|
+
setPrompt("");
|
|
372
|
+
setRefImage(null);
|
|
373
|
+
setRefImageName(null);
|
|
374
|
+
}
|
|
375
|
+
function handleFileSelect(e) {
|
|
376
|
+
const file = e.target.files?.[0];
|
|
377
|
+
if (!file) return;
|
|
378
|
+
setRefImageName(file.name);
|
|
379
|
+
const reader = new FileReader();
|
|
380
|
+
reader.onload = () => {
|
|
381
|
+
setRefImage(reader.result);
|
|
382
|
+
};
|
|
383
|
+
reader.readAsDataURL(file);
|
|
384
|
+
e.target.value = "";
|
|
385
|
+
}
|
|
386
|
+
function handleSetAttr(attr, value) {
|
|
387
|
+
if (!selection?.sectionId || !selection?.elementPath || !onUpdateAttribute) return;
|
|
388
|
+
onUpdateAttribute(selection.sectionId, selection.elementPath, attr, value);
|
|
389
|
+
}
|
|
390
|
+
const isImg = selection.tagName === "IMG";
|
|
391
|
+
const isLink = selection.tagName === "A";
|
|
392
|
+
const hasAttrEditing = (isImg || isLink) && onUpdateAttribute;
|
|
393
|
+
return /* @__PURE__ */ jsxs3(
|
|
394
|
+
"div",
|
|
395
|
+
{
|
|
396
|
+
ref: toolbarRef,
|
|
397
|
+
className: "fixed z-50 flex flex-col gap-1.5 bg-gray-900 text-white rounded-xl shadow-2xl px-2 py-1.5 border border-gray-700",
|
|
398
|
+
style: { top: finalTop, left: clampedLeft, maxWidth: "min(480px, calc(100vw - 16px))" },
|
|
399
|
+
children: [
|
|
400
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
|
|
401
|
+
selection.tagName && /* @__PURE__ */ jsx3("span", { className: "px-2 py-0.5 rounded-md bg-blue-600 text-[10px] font-mono font-bold uppercase tracking-wider shrink-0", children: selection.tagName.toLowerCase() }),
|
|
402
|
+
/* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "flex items-center gap-1 flex-1", children: [
|
|
403
|
+
/* @__PURE__ */ jsx3(
|
|
404
|
+
"input",
|
|
405
|
+
{
|
|
406
|
+
ref: inputRef,
|
|
407
|
+
type: "text",
|
|
408
|
+
value: prompt,
|
|
409
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
410
|
+
placeholder: refImage ? "Instruccion + imagen..." : "Editar con AI...",
|
|
411
|
+
disabled: isRefining,
|
|
412
|
+
className: "bg-transparent text-sm text-white placeholder:text-gray-500 outline-none w-40 md:w-56 px-2 py-1"
|
|
413
|
+
}
|
|
414
|
+
),
|
|
415
|
+
/* @__PURE__ */ jsx3(
|
|
416
|
+
"button",
|
|
417
|
+
{
|
|
418
|
+
type: "button",
|
|
419
|
+
onClick: () => fileInputRef.current?.click(),
|
|
420
|
+
disabled: isRefining,
|
|
421
|
+
className: `w-7 h-7 flex items-center justify-center rounded-lg transition-colors shrink-0 ${refImage ? "bg-blue-600 text-white" : "hover:bg-gray-800 text-gray-400 hover:text-white"}`,
|
|
422
|
+
title: refImage ? `Imagen: ${refImageName}` : "Adjuntar imagen de referencia",
|
|
423
|
+
children: /* @__PURE__ */ jsx3("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx3("path", { fillRule: "evenodd", d: "M1 5.25A2.25 2.25 0 013.25 3h13.5A2.25 2.25 0 0119 5.25v9.5A2.25 2.25 0 0116.75 17H3.25A2.25 2.25 0 011 14.75v-9.5zm1.5 5.81V14.75c0 .414.336.75.75.75h13.5a.75.75 0 00.75-.75v-2.06l-2.22-2.22a.75.75 0 00-1.06 0L8.56 16.1l-3.28-3.28a.75.75 0 00-1.06 0l-1.72 1.72zm12-4.06a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z", clipRule: "evenodd" }) })
|
|
424
|
+
}
|
|
425
|
+
),
|
|
426
|
+
/* @__PURE__ */ jsx3(
|
|
427
|
+
"input",
|
|
428
|
+
{
|
|
429
|
+
ref: fileInputRef,
|
|
430
|
+
type: "file",
|
|
431
|
+
accept: "image/*",
|
|
432
|
+
onChange: handleFileSelect,
|
|
433
|
+
className: "hidden"
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
/* @__PURE__ */ jsx3(
|
|
437
|
+
"button",
|
|
438
|
+
{
|
|
439
|
+
type: "submit",
|
|
440
|
+
disabled: !prompt.trim() || isRefining,
|
|
441
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg bg-blue-500 hover:bg-blue-600 disabled:opacity-30 transition-colors",
|
|
442
|
+
children: isRefining ? /* @__PURE__ */ jsx3("span", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx3(HiSparkles, { className: "w-3.5 h-3.5" })
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
] }),
|
|
446
|
+
/* @__PURE__ */ jsx3("div", { className: "w-px h-5 bg-gray-700" }),
|
|
447
|
+
/* @__PURE__ */ jsx3(
|
|
448
|
+
"button",
|
|
449
|
+
{
|
|
450
|
+
onClick: () => {
|
|
451
|
+
const tag = selection.tagName?.toLowerCase();
|
|
452
|
+
const text = selection.text?.substring(0, 80);
|
|
453
|
+
const prompt2 = selection.isSectionRoot ? "Genera una variante completamente diferente de esta seccion. Manten el mismo contenido/informacion pero cambia radicalmente el layout, la estructura visual, y el estilo. Sorprendeme con un diseno creativo e inesperado." : `Modifica SOLO el elemento <${tag}> que contiene "${text}". Genera una variante visual diferente de ESE elemento (diferente estilo, layout, tipografia). NO modifiques ningun otro elemento de la seccion.`;
|
|
454
|
+
onRefine(prompt2, refImage || void 0);
|
|
455
|
+
},
|
|
456
|
+
disabled: isRefining,
|
|
457
|
+
className: "px-2.5 py-1 text-[11px] font-bold rounded-lg bg-blue-600 hover:bg-blue-500 disabled:opacity-30 transition-colors whitespace-nowrap shrink-0",
|
|
458
|
+
title: "Generar variante",
|
|
459
|
+
children: "\u2726 Variante"
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
selection.isSectionRoot && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
463
|
+
/* @__PURE__ */ jsx3("div", { className: "w-px h-5 bg-gray-700" }),
|
|
464
|
+
/* @__PURE__ */ jsx3(
|
|
465
|
+
"button",
|
|
466
|
+
{
|
|
467
|
+
onClick: onMoveUp,
|
|
468
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg hover:bg-gray-800 transition-colors text-xs",
|
|
469
|
+
title: "Mover arriba",
|
|
470
|
+
children: "\u2191"
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx3(
|
|
474
|
+
"button",
|
|
475
|
+
{
|
|
476
|
+
onClick: onMoveDown,
|
|
477
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg hover:bg-gray-800 transition-colors text-xs",
|
|
478
|
+
title: "Mover abajo",
|
|
479
|
+
children: "\u2193"
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }),
|
|
483
|
+
/* @__PURE__ */ jsx3(
|
|
484
|
+
"button",
|
|
485
|
+
{
|
|
486
|
+
onClick: onViewCode,
|
|
487
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg hover:bg-gray-800 transition-colors text-xs font-mono text-gray-400 hover:text-white",
|
|
488
|
+
title: "Ver codigo",
|
|
489
|
+
children: "</>"
|
|
490
|
+
}
|
|
491
|
+
),
|
|
492
|
+
selection.isSectionRoot && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
493
|
+
/* @__PURE__ */ jsx3("div", { className: "w-px h-5 bg-gray-700" }),
|
|
494
|
+
/* @__PURE__ */ jsx3(
|
|
495
|
+
"button",
|
|
496
|
+
{
|
|
497
|
+
onClick: onDelete,
|
|
498
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg hover:bg-red-900/50 text-red-400 transition-colors",
|
|
499
|
+
title: "Eliminar seccion",
|
|
500
|
+
children: /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
501
|
+
/* @__PURE__ */ jsx3("polyline", { points: "3 6 5 6 21 6" }),
|
|
502
|
+
/* @__PURE__ */ jsx3("path", { d: "M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" })
|
|
503
|
+
] })
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
] }),
|
|
507
|
+
/* @__PURE__ */ jsx3("div", { className: "w-px h-5 bg-gray-700" }),
|
|
508
|
+
/* @__PURE__ */ jsx3(
|
|
509
|
+
"button",
|
|
510
|
+
{
|
|
511
|
+
onClick: onClose,
|
|
512
|
+
className: "w-7 h-7 flex items-center justify-center rounded-lg hover:bg-gray-800 text-gray-400 hover:text-white transition-colors",
|
|
513
|
+
title: "Cerrar (ESC)",
|
|
514
|
+
children: "\u2715"
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
] }),
|
|
518
|
+
refImage && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 pt-0.5 pb-0.5 border-t border-gray-700/50", children: [
|
|
519
|
+
/* @__PURE__ */ jsx3("img", { src: refImage, alt: "Referencia", className: "w-10 h-10 rounded object-cover border border-gray-600" }),
|
|
520
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-gray-400 truncate flex-1", children: refImageName }),
|
|
521
|
+
/* @__PURE__ */ jsx3(
|
|
522
|
+
"button",
|
|
523
|
+
{
|
|
524
|
+
onClick: () => {
|
|
525
|
+
setRefImage(null);
|
|
526
|
+
setRefImageName(null);
|
|
527
|
+
},
|
|
528
|
+
className: "text-[10px] text-gray-500 hover:text-white px-1",
|
|
529
|
+
children: "\u2715"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
] }),
|
|
533
|
+
selection.isSectionRoot && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 pt-0.5 pb-0.5 border-t border-gray-700/50", children: [
|
|
534
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-gray-500 uppercase tracking-wider mr-1 shrink-0", children: "Estilo" }),
|
|
535
|
+
STYLE_PRESETS.map((preset) => /* @__PURE__ */ jsxs3(
|
|
536
|
+
"button",
|
|
537
|
+
{
|
|
538
|
+
onClick: () => onRefine(preset.instruction),
|
|
539
|
+
disabled: isRefining,
|
|
540
|
+
className: "px-2 py-0.5 text-[11px] font-medium rounded-md bg-gray-800 hover:bg-gray-700 disabled:opacity-30 transition-colors whitespace-nowrap",
|
|
541
|
+
title: preset.label,
|
|
542
|
+
children: [
|
|
543
|
+
/* @__PURE__ */ jsx3("span", { className: "mr-1", children: preset.icon }),
|
|
544
|
+
preset.label
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
preset.label
|
|
548
|
+
))
|
|
549
|
+
] }),
|
|
550
|
+
isImg && hasAttrEditing && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1 pt-0.5 pb-0.5 border-t border-gray-700/50", children: [
|
|
551
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
552
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-gray-500 uppercase tracking-wider w-8 shrink-0", children: "src" }),
|
|
553
|
+
/* @__PURE__ */ jsx3(
|
|
554
|
+
"input",
|
|
555
|
+
{
|
|
556
|
+
type: "text",
|
|
557
|
+
value: imgSrc,
|
|
558
|
+
onChange: (e) => setImgSrc(e.target.value),
|
|
559
|
+
onKeyDown: (e) => {
|
|
560
|
+
if (e.key === "Enter") handleSetAttr("src", imgSrc);
|
|
561
|
+
},
|
|
562
|
+
className: "flex-1 bg-gray-800 text-xs text-white rounded px-2 py-1 outline-none min-w-0",
|
|
563
|
+
placeholder: "URL de imagen..."
|
|
564
|
+
}
|
|
565
|
+
),
|
|
566
|
+
/* @__PURE__ */ jsx3(
|
|
567
|
+
"button",
|
|
568
|
+
{
|
|
569
|
+
onClick: () => handleSetAttr("src", imgSrc),
|
|
570
|
+
className: "px-2 py-1 text-[10px] font-bold rounded bg-blue-500 hover:bg-blue-600 transition-colors shrink-0",
|
|
571
|
+
children: "Set"
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
] }),
|
|
575
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
576
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-gray-500 uppercase tracking-wider w-8 shrink-0", children: "alt" }),
|
|
577
|
+
/* @__PURE__ */ jsx3(
|
|
578
|
+
"input",
|
|
579
|
+
{
|
|
580
|
+
type: "text",
|
|
581
|
+
value: imgAlt,
|
|
582
|
+
onChange: (e) => setImgAlt(e.target.value),
|
|
583
|
+
onKeyDown: (e) => {
|
|
584
|
+
if (e.key === "Enter") handleSetAttr("alt", imgAlt);
|
|
585
|
+
},
|
|
586
|
+
className: "flex-1 bg-gray-800 text-xs text-white rounded px-2 py-1 outline-none min-w-0",
|
|
587
|
+
placeholder: "Alt text..."
|
|
588
|
+
}
|
|
589
|
+
),
|
|
590
|
+
/* @__PURE__ */ jsx3(
|
|
591
|
+
"button",
|
|
592
|
+
{
|
|
593
|
+
onClick: () => handleSetAttr("alt", imgAlt),
|
|
594
|
+
className: "px-2 py-1 text-[10px] font-bold rounded bg-blue-500 hover:bg-blue-600 transition-colors shrink-0",
|
|
595
|
+
children: "Set"
|
|
596
|
+
}
|
|
597
|
+
)
|
|
598
|
+
] })
|
|
599
|
+
] }),
|
|
600
|
+
isLink && hasAttrEditing && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 pt-0.5 pb-0.5 border-t border-gray-700/50", children: [
|
|
601
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-gray-500 uppercase tracking-wider w-8 shrink-0", children: "href" }),
|
|
602
|
+
/* @__PURE__ */ jsx3(
|
|
603
|
+
"input",
|
|
604
|
+
{
|
|
605
|
+
type: "text",
|
|
606
|
+
value: linkHref,
|
|
607
|
+
onChange: (e) => setLinkHref(e.target.value),
|
|
608
|
+
onKeyDown: (e) => {
|
|
609
|
+
if (e.key === "Enter") handleSetAttr("href", linkHref);
|
|
610
|
+
},
|
|
611
|
+
className: "flex-1 bg-gray-800 text-xs text-white rounded px-2 py-1 outline-none min-w-0",
|
|
612
|
+
placeholder: "URL del enlace..."
|
|
613
|
+
}
|
|
614
|
+
),
|
|
615
|
+
/* @__PURE__ */ jsx3(
|
|
616
|
+
"button",
|
|
617
|
+
{
|
|
618
|
+
onClick: () => handleSetAttr("href", linkHref),
|
|
619
|
+
className: "px-2 py-1 text-[10px] font-bold rounded bg-blue-500 hover:bg-blue-600 transition-colors shrink-0",
|
|
620
|
+
children: "Set"
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
] })
|
|
624
|
+
]
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/components/CodeEditor.tsx
|
|
630
|
+
import { useEffect as useEffect3, useRef as useRef4, useCallback as useCallback2, useState as useState4 } from "react";
|
|
631
|
+
import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter, Decoration } from "@codemirror/view";
|
|
632
|
+
import { EditorState, StateField, StateEffect } from "@codemirror/state";
|
|
633
|
+
import { html } from "@codemirror/lang-html";
|
|
634
|
+
import { oneDark } from "@codemirror/theme-one-dark";
|
|
635
|
+
import { defaultKeymap, indentWithTab, history, historyKeymap } from "@codemirror/commands";
|
|
636
|
+
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
|
|
637
|
+
import { bracketMatching, foldGutter, foldKeymap } from "@codemirror/language";
|
|
638
|
+
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
|
|
639
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
640
|
+
function formatHtml(html2) {
|
|
641
|
+
let result = html2.replace(/>\s*</g, ">\n<");
|
|
642
|
+
const lines = result.split("\n");
|
|
643
|
+
const output = [];
|
|
644
|
+
let indent = 0;
|
|
645
|
+
for (const raw of lines) {
|
|
646
|
+
const line = raw.trim();
|
|
647
|
+
if (!line) continue;
|
|
648
|
+
const isClosing = /^<\//.test(line);
|
|
649
|
+
const isSelfClosing = /\/>$/.test(line) || /^<(img|br|hr|input|meta|link|col|area|base|embed|source|track|wbr)\b/i.test(line);
|
|
650
|
+
const hasInlineClose = /^<[^/][^>]*>.*<\//.test(line);
|
|
651
|
+
if (isClosing) indent = Math.max(0, indent - 1);
|
|
652
|
+
output.push(" ".repeat(indent) + line);
|
|
653
|
+
if (!isClosing && !isSelfClosing && !hasInlineClose && /^<[a-zA-Z]/.test(line)) {
|
|
654
|
+
indent++;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return output.join("\n");
|
|
658
|
+
}
|
|
659
|
+
var flashLineEffect = StateEffect.define();
|
|
660
|
+
var clearFlashEffect = StateEffect.define();
|
|
661
|
+
var flashLineDeco = Decoration.line({ class: "cm-flash-line" });
|
|
662
|
+
var flashLineField = StateField.define({
|
|
663
|
+
create: () => Decoration.none,
|
|
664
|
+
update(decos, tr) {
|
|
665
|
+
for (const e of tr.effects) {
|
|
666
|
+
if (e.is(flashLineEffect)) {
|
|
667
|
+
return Decoration.set([flashLineDeco.range(e.value.from)]);
|
|
668
|
+
}
|
|
669
|
+
if (e.is(clearFlashEffect)) {
|
|
670
|
+
return Decoration.none;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return decos;
|
|
674
|
+
},
|
|
675
|
+
provide: (f) => EditorView.decorations.from(f)
|
|
676
|
+
});
|
|
677
|
+
function scrollToTarget(view, target) {
|
|
678
|
+
if (!target) return;
|
|
679
|
+
const docText = view.state.doc.toString();
|
|
680
|
+
const normalized = target.replace(/"/g, "'");
|
|
681
|
+
let idx = docText.indexOf(normalized);
|
|
682
|
+
if (idx === -1) idx = docText.indexOf(target);
|
|
683
|
+
if (idx === -1) {
|
|
684
|
+
const tagMatch = target.match(/^<(\w+)/);
|
|
685
|
+
const classMatch = target.match(/class=["']([^"']*?)["']/);
|
|
686
|
+
if (tagMatch) {
|
|
687
|
+
const searchTag = tagMatch[0];
|
|
688
|
+
const searchClass = classMatch ? classMatch[1].split(" ")[0] : null;
|
|
689
|
+
for (let i = 1; i <= view.state.doc.lines; i++) {
|
|
690
|
+
const line = view.state.doc.line(i);
|
|
691
|
+
if (line.text.includes(searchTag) && (!searchClass || line.text.includes(searchClass))) {
|
|
692
|
+
idx = line.from;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (idx !== -1) {
|
|
699
|
+
const line = view.state.doc.lineAt(idx);
|
|
700
|
+
view.dispatch({
|
|
701
|
+
selection: { anchor: line.from },
|
|
702
|
+
effects: [
|
|
703
|
+
EditorView.scrollIntoView(line.from, { y: "center" }),
|
|
704
|
+
flashLineEffect.of({ from: line.from, to: line.to })
|
|
705
|
+
]
|
|
706
|
+
});
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
view.dispatch({ effects: clearFlashEffect.of(null) });
|
|
709
|
+
}, 2e3);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function CodeEditor({ code, label, scrollToText, onSave, onClose }) {
|
|
713
|
+
const containerRef = useRef4(null);
|
|
714
|
+
const viewRef = useRef4(null);
|
|
715
|
+
const [stats, setStats] = useState4({ lines: 0, kb: "0.0" });
|
|
716
|
+
const onSaveRef = useRef4(onSave);
|
|
717
|
+
const onCloseRef = useRef4(onClose);
|
|
718
|
+
onSaveRef.current = onSave;
|
|
719
|
+
onCloseRef.current = onClose;
|
|
720
|
+
const updateStats = useCallback2((doc) => {
|
|
721
|
+
setStats({ lines: doc.lines, kb: (doc.length / 1024).toFixed(1) });
|
|
722
|
+
}, []);
|
|
723
|
+
useEffect3(() => {
|
|
724
|
+
if (!containerRef.current) return;
|
|
725
|
+
const initialDoc = code.includes("\n") ? code : formatHtml(code);
|
|
726
|
+
const state = EditorState.create({
|
|
727
|
+
doc: initialDoc,
|
|
728
|
+
extensions: [
|
|
729
|
+
lineNumbers(),
|
|
730
|
+
highlightActiveLine(),
|
|
731
|
+
highlightActiveLineGutter(),
|
|
732
|
+
bracketMatching(),
|
|
733
|
+
closeBrackets(),
|
|
734
|
+
foldGutter(),
|
|
735
|
+
highlightSelectionMatches(),
|
|
736
|
+
html(),
|
|
737
|
+
oneDark,
|
|
738
|
+
history(),
|
|
739
|
+
EditorView.lineWrapping,
|
|
740
|
+
keymap.of([
|
|
741
|
+
{ key: "Mod-s", run: (v) => {
|
|
742
|
+
onSaveRef.current(v.state.doc.toString());
|
|
743
|
+
return true;
|
|
744
|
+
} },
|
|
745
|
+
{ key: "Escape", run: () => {
|
|
746
|
+
onCloseRef.current();
|
|
747
|
+
return true;
|
|
748
|
+
} },
|
|
749
|
+
indentWithTab,
|
|
750
|
+
...closeBracketsKeymap,
|
|
751
|
+
...searchKeymap,
|
|
752
|
+
...foldKeymap,
|
|
753
|
+
...historyKeymap,
|
|
754
|
+
...defaultKeymap
|
|
755
|
+
]),
|
|
756
|
+
EditorView.updateListener.of((update) => {
|
|
757
|
+
if (update.docChanged) {
|
|
758
|
+
updateStats(update.state.doc);
|
|
759
|
+
}
|
|
760
|
+
}),
|
|
761
|
+
flashLineField,
|
|
762
|
+
EditorView.theme({
|
|
763
|
+
"&": { height: "100%", fontSize: "13px" },
|
|
764
|
+
".cm-scroller": { overflow: "auto", fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace" },
|
|
765
|
+
".cm-content": { padding: "8px 0" },
|
|
766
|
+
".cm-gutters": { borderRight: "1px solid #21262d" },
|
|
767
|
+
".cm-flash-line": { backgroundColor: "rgba(250, 204, 21, 0.25)", transition: "background-color 2s ease-out" }
|
|
768
|
+
})
|
|
769
|
+
]
|
|
770
|
+
});
|
|
771
|
+
const view = new EditorView({ state, parent: containerRef.current });
|
|
772
|
+
viewRef.current = view;
|
|
773
|
+
updateStats(view.state.doc);
|
|
774
|
+
scrollToTarget(view, scrollToText);
|
|
775
|
+
view.focus();
|
|
776
|
+
return () => {
|
|
777
|
+
view.destroy();
|
|
778
|
+
};
|
|
779
|
+
}, []);
|
|
780
|
+
useEffect3(() => {
|
|
781
|
+
const view = viewRef.current;
|
|
782
|
+
if (!view || !scrollToText) return;
|
|
783
|
+
scrollToTarget(view, scrollToText);
|
|
784
|
+
}, [scrollToText]);
|
|
785
|
+
function handleFormat() {
|
|
786
|
+
const view = viewRef.current;
|
|
787
|
+
if (!view) return;
|
|
788
|
+
const formatted = formatHtml(view.state.doc.toString());
|
|
789
|
+
view.dispatch({
|
|
790
|
+
changes: { from: 0, to: view.state.doc.length, insert: formatted }
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function handleSave() {
|
|
794
|
+
const view = viewRef.current;
|
|
795
|
+
if (!view) return;
|
|
796
|
+
onSave(view.state.doc.toString());
|
|
797
|
+
}
|
|
798
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col h-full bg-[#0d1117]", children: [
|
|
799
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between px-4 py-3 border-b border-gray-800 shrink-0", children: [
|
|
800
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
|
|
801
|
+
/* @__PURE__ */ jsx4("span", { className: "px-2 py-0.5 rounded bg-orange-600/20 text-orange-400 text-[10px] font-mono font-bold uppercase tracking-wider", children: "HTML" }),
|
|
802
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sm font-bold text-gray-300", children: label })
|
|
803
|
+
] }),
|
|
804
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
805
|
+
/* @__PURE__ */ jsx4(
|
|
806
|
+
"button",
|
|
807
|
+
{
|
|
808
|
+
onClick: handleFormat,
|
|
809
|
+
className: "px-3 py-1.5 text-xs font-bold rounded-lg bg-gray-800 text-gray-400 hover:text-white hover:bg-gray-700 transition-colors",
|
|
810
|
+
children: "Formatear"
|
|
811
|
+
}
|
|
812
|
+
),
|
|
813
|
+
/* @__PURE__ */ jsx4(
|
|
814
|
+
"button",
|
|
815
|
+
{
|
|
816
|
+
onClick: handleSave,
|
|
817
|
+
className: "px-4 py-1.5 text-xs font-bold rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
|
|
818
|
+
children: "Guardar"
|
|
819
|
+
}
|
|
820
|
+
),
|
|
821
|
+
/* @__PURE__ */ jsx4(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
onClick: onClose,
|
|
825
|
+
className: "px-3 py-1.5 text-xs font-bold rounded-lg bg-gray-800 text-gray-400 hover:text-white hover:bg-gray-700 transition-colors",
|
|
826
|
+
children: "Cerrar"
|
|
827
|
+
}
|
|
828
|
+
)
|
|
829
|
+
] })
|
|
830
|
+
] }),
|
|
831
|
+
/* @__PURE__ */ jsx4("div", { ref: containerRef, className: "flex-1 overflow-hidden" }),
|
|
832
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between px-4 py-1.5 border-t border-gray-800 text-[10px] text-gray-500 font-mono shrink-0", children: [
|
|
833
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
834
|
+
stats.lines,
|
|
835
|
+
" lineas"
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ jsx4("span", { children: "Tab = indentar \xB7 Cmd+S = guardar \xB7 Esc = cerrar" }),
|
|
838
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
839
|
+
stats.kb,
|
|
840
|
+
" KB"
|
|
841
|
+
] })
|
|
842
|
+
] })
|
|
843
|
+
] });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export {
|
|
847
|
+
Canvas,
|
|
848
|
+
SectionList,
|
|
849
|
+
FloatingToolbar,
|
|
850
|
+
CodeEditor
|
|
851
|
+
};
|
|
852
|
+
//# sourceMappingURL=chunk-XM3D3TTJ.js.map
|