@orion-studios/payload-studio 0.6.0-beta.7 → 0.6.0-beta.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/client.js +2076 -618
- package/dist/admin/client.mjs +2037 -590
- package/dist/admin/index.d.mts +2 -2
- package/dist/admin/index.d.ts +2 -2
- package/dist/admin/index.js +140 -17
- package/dist/admin/index.mjs +2 -2
- package/dist/admin-app/client.js +11 -4
- package/dist/admin-app/client.mjs +1 -1
- package/dist/admin-app/index.d.mts +2 -2
- package/dist/admin-app/index.d.ts +2 -2
- package/dist/admin-app/styles.css +343 -41
- package/dist/admin.css +18 -2
- package/dist/builder-v2/client.d.mts +18 -0
- package/dist/builder-v2/client.d.ts +18 -0
- package/dist/builder-v2/client.js +1673 -0
- package/dist/builder-v2/client.mjs +1548 -0
- package/dist/builder-v2/index.d.mts +241 -0
- package/dist/builder-v2/index.d.ts +241 -0
- package/dist/builder-v2/index.js +760 -0
- package/dist/builder-v2/index.mjs +710 -0
- package/dist/builder-v2/styles.css +1524 -0
- package/dist/{chunk-XKUTZ7IU.mjs → chunk-276KAPGM.mjs} +56 -5
- package/dist/{chunk-KPIX7OSV.mjs → chunk-2XH7X34N.mjs} +11 -4
- package/dist/{chunk-PF3EBZXF.mjs → chunk-7ZMXZRBP.mjs} +39 -3
- package/dist/{chunk-5FNTVRCR.mjs → chunk-KHK6RTGC.mjs} +143 -20
- package/dist/{chunk-OTHERBGX.mjs → chunk-ZADL33R6.mjs} +1 -1
- package/dist/{index-QPDAedIX.d.ts → index-BV0vEGl6.d.ts} +4 -2
- package/dist/{index-Cv-6qnrw.d.mts → index-D5zrOdyv.d.mts} +3 -1
- package/dist/{index-52HdVLQq.d.ts → index-DAdN56fM.d.ts} +1 -1
- package/dist/{index-DyMmaRfI.d.mts → index-DLfPOqYA.d.mts} +4 -2
- package/dist/{index-Crx_MtPw.d.ts → index-Dv-Alx4h.d.ts} +3 -1
- package/dist/{index-DEQC3Dwj.d.mts → index-G_uTNffQ.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +231 -21
- package/dist/index.mjs +10 -10
- package/dist/nextjs/index.js +39 -3
- package/dist/nextjs/index.mjs +2 -2
- package/dist/{sitePreviewTypes-BkHCWxNW.d.mts → sitePreviewTypes-BrJwGzJj.d.mts} +1 -1
- package/dist/{sitePreviewTypes-BkHCWxNW.d.ts → sitePreviewTypes-BrJwGzJj.d.ts} +1 -1
- package/dist/studio-pages/builder.css +24 -5
- package/dist/studio-pages/client.js +574 -64
- package/dist/studio-pages/client.mjs +574 -64
- package/dist/studio-pages/index.d.mts +1 -1
- package/dist/studio-pages/index.d.ts +1 -1
- package/dist/studio-pages/index.js +91 -4
- package/dist/studio-pages/index.mjs +3 -3
- package/package.json +22 -3
|
@@ -0,0 +1,1673 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/builder-v2/client.ts
|
|
33
|
+
var client_exports = {};
|
|
34
|
+
__export(client_exports, {
|
|
35
|
+
GrapesPageEditor: () => GrapesPageEditor
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(client_exports);
|
|
38
|
+
|
|
39
|
+
// src/builder-v2/editor/GrapesPageEditor.tsx
|
|
40
|
+
var import_react = require("react");
|
|
41
|
+
|
|
42
|
+
// src/shared/clientImageUploadOptimization.ts
|
|
43
|
+
var MAX_DIRECT_UPLOAD_BYTES = 4e6;
|
|
44
|
+
var extensionForMimeType = (mimeType) => {
|
|
45
|
+
switch (mimeType) {
|
|
46
|
+
case "image/webp":
|
|
47
|
+
return ".webp";
|
|
48
|
+
case "image/png":
|
|
49
|
+
return ".png";
|
|
50
|
+
default:
|
|
51
|
+
return ".jpg";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var detectCanvasTransparency = (context, width, height) => {
|
|
55
|
+
try {
|
|
56
|
+
const { data } = context.getImageData(0, 0, width, height);
|
|
57
|
+
for (let index = 3; index < data.length; index += 4) {
|
|
58
|
+
if (data[index] < 255) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
var resolveOutputMimeTypes = (sourceMime, hasTransparency) => {
|
|
67
|
+
const candidates = [];
|
|
68
|
+
if (hasTransparency) {
|
|
69
|
+
candidates.push("image/webp", "image/png");
|
|
70
|
+
} else if (sourceMime === "image/webp") {
|
|
71
|
+
candidates.push("image/webp", "image/jpeg");
|
|
72
|
+
} else if (sourceMime === "image/png") {
|
|
73
|
+
candidates.push("image/webp", "image/jpeg", "image/png");
|
|
74
|
+
} else {
|
|
75
|
+
candidates.push("image/jpeg", "image/webp");
|
|
76
|
+
}
|
|
77
|
+
return [...new Set(candidates)];
|
|
78
|
+
};
|
|
79
|
+
async function optimizeImageForUpload(file) {
|
|
80
|
+
if (!file.type.startsWith("image/")) {
|
|
81
|
+
return file;
|
|
82
|
+
}
|
|
83
|
+
const objectURL = URL.createObjectURL(file);
|
|
84
|
+
try {
|
|
85
|
+
const image = await new Promise((resolve, reject) => {
|
|
86
|
+
const nextImage = new Image();
|
|
87
|
+
nextImage.onload = () => resolve(nextImage);
|
|
88
|
+
nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
|
|
89
|
+
nextImage.src = objectURL;
|
|
90
|
+
});
|
|
91
|
+
const canvas = document.createElement("canvas");
|
|
92
|
+
canvas.width = Math.max(1, image.width);
|
|
93
|
+
canvas.height = Math.max(1, image.height);
|
|
94
|
+
const context = canvas.getContext("2d");
|
|
95
|
+
if (!context) {
|
|
96
|
+
return file;
|
|
97
|
+
}
|
|
98
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
99
|
+
const sourceMime = file.type.toLowerCase();
|
|
100
|
+
const hasTransparency = detectCanvasTransparency(context, canvas.width, canvas.height);
|
|
101
|
+
const outputMimes = resolveOutputMimeTypes(sourceMime, hasTransparency);
|
|
102
|
+
const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36, 0.3, 0.26];
|
|
103
|
+
let bestFile = null;
|
|
104
|
+
for (const outputMime of outputMimes) {
|
|
105
|
+
const passes = outputMime === "image/png" ? [void 0] : qualityPasses;
|
|
106
|
+
for (const quality of passes) {
|
|
107
|
+
const blob = await new Promise((resolve) => {
|
|
108
|
+
canvas.toBlob((value) => resolve(value), outputMime, quality);
|
|
109
|
+
});
|
|
110
|
+
if (!blob) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const optimizedName = file.name.replace(/\.[^/.]+$/, extensionForMimeType(outputMime));
|
|
114
|
+
const optimized = new File([blob], optimizedName, {
|
|
115
|
+
lastModified: Date.now(),
|
|
116
|
+
type: outputMime
|
|
117
|
+
});
|
|
118
|
+
if (!bestFile || optimized.size < bestFile.size) {
|
|
119
|
+
bestFile = optimized;
|
|
120
|
+
}
|
|
121
|
+
if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!bestFile) {
|
|
127
|
+
return file;
|
|
128
|
+
}
|
|
129
|
+
return bestFile.size < file.size ? bestFile : file;
|
|
130
|
+
} finally {
|
|
131
|
+
URL.revokeObjectURL(objectURL);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/builder-v2/projectData.ts
|
|
136
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
137
|
+
var createEmptyBuilderV2ProjectData = (title = "Untitled Page") => ({
|
|
138
|
+
assets: [],
|
|
139
|
+
pages: [
|
|
140
|
+
{
|
|
141
|
+
component: {
|
|
142
|
+
components: [
|
|
143
|
+
{
|
|
144
|
+
attributes: {
|
|
145
|
+
class: "orion-builder-v2-section"
|
|
146
|
+
},
|
|
147
|
+
components: [
|
|
148
|
+
{
|
|
149
|
+
content: title,
|
|
150
|
+
tagName: "h1",
|
|
151
|
+
type: "text"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
content: "Start building this page by dragging blocks from the panel.",
|
|
155
|
+
tagName: "p",
|
|
156
|
+
type: "text"
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
tagName: "section"
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
type: "wrapper"
|
|
163
|
+
},
|
|
164
|
+
id: "page",
|
|
165
|
+
name: title
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
styles: []
|
|
169
|
+
});
|
|
170
|
+
var normalizeBuilderV2ProjectData = (value, fallbackTitle = "Untitled Page") => {
|
|
171
|
+
if (!isRecord(value)) {
|
|
172
|
+
return createEmptyBuilderV2ProjectData(fallbackTitle);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/builder-v2/runtime/placeholders.ts
|
|
178
|
+
var placeholderPattern = /<(?<tag>[a-zA-Z][a-zA-Z0-9-]*)\b(?<attrs>[^>]*)data-orion-component=["'](?<component>[^"']+)["'](?<rest>[^>]*)>(?<content>.*?)<\/\k<tag>>/gis;
|
|
179
|
+
var attrPattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)=(?:"([^"]*)"|'([^']*)')/g;
|
|
180
|
+
var decodeHtmlAttribute = (value) => value.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
181
|
+
var parseAttributes = (value) => {
|
|
182
|
+
const props = {};
|
|
183
|
+
let match;
|
|
184
|
+
while ((match = attrPattern.exec(value)) !== null) {
|
|
185
|
+
const name = match[1];
|
|
186
|
+
const rawValue = match[2] ?? match[3] ?? "";
|
|
187
|
+
if (!name.startsWith("data-")) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (name === "data-orion-component" || name === "data-orion-id") {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (name === "data-orion-props") {
|
|
194
|
+
const decoded = decodeHtmlAttribute(rawValue);
|
|
195
|
+
try {
|
|
196
|
+
const parsed = JSON.parse(decoded);
|
|
197
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
198
|
+
Object.assign(props, parsed);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
props.propsJson = decoded;
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const propName = name.replace(/^data-orion-/, "").replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
206
|
+
props[propName] = decodeHtmlAttribute(rawValue);
|
|
207
|
+
}
|
|
208
|
+
return props;
|
|
209
|
+
};
|
|
210
|
+
var extractAttribute = (attrs, name) => {
|
|
211
|
+
attrPattern.lastIndex = 0;
|
|
212
|
+
let match;
|
|
213
|
+
while ((match = attrPattern.exec(attrs)) !== null) {
|
|
214
|
+
if (match[1] === name) {
|
|
215
|
+
return decodeHtmlAttribute(match[2] ?? match[3] ?? "");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
};
|
|
220
|
+
var extractWrapperAttributes = (attrs) => {
|
|
221
|
+
const id = extractAttribute(attrs, "id") || void 0;
|
|
222
|
+
const className = extractAttribute(attrs, "class") || void 0;
|
|
223
|
+
return id || className ? { className, id } : void 0;
|
|
224
|
+
};
|
|
225
|
+
var parseBuilderV2DynamicComponents = (html) => {
|
|
226
|
+
const instances = [];
|
|
227
|
+
let match;
|
|
228
|
+
while ((match = placeholderPattern.exec(html)) !== null) {
|
|
229
|
+
const attrs = `${match.groups?.attrs ?? ""} ${match.groups?.rest ?? ""}`;
|
|
230
|
+
const component = match.groups?.component ?? "";
|
|
231
|
+
if (!component) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
instances.push({
|
|
235
|
+
component,
|
|
236
|
+
id: extractAttribute(attrs, "data-orion-id") || `component-${instances.length + 1}`,
|
|
237
|
+
props: parseAttributes(attrs),
|
|
238
|
+
wrapperAttributes: extractWrapperAttributes(attrs)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return instances;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/builder-v2/sanitize.ts
|
|
245
|
+
var import_sanitize_html = __toESM(require("sanitize-html"));
|
|
246
|
+
var allowedTags = import_sanitize_html.default.defaults.allowedTags.concat([
|
|
247
|
+
"article",
|
|
248
|
+
"aside",
|
|
249
|
+
"button",
|
|
250
|
+
"figure",
|
|
251
|
+
"figcaption",
|
|
252
|
+
"footer",
|
|
253
|
+
"header",
|
|
254
|
+
"main",
|
|
255
|
+
"nav",
|
|
256
|
+
"section",
|
|
257
|
+
"source",
|
|
258
|
+
"video"
|
|
259
|
+
]);
|
|
260
|
+
var allowedAttributes = {
|
|
261
|
+
...import_sanitize_html.default.defaults.allowedAttributes,
|
|
262
|
+
"*": [
|
|
263
|
+
"aria-*",
|
|
264
|
+
"class",
|
|
265
|
+
"data-*",
|
|
266
|
+
"id",
|
|
267
|
+
"role",
|
|
268
|
+
"style",
|
|
269
|
+
"title"
|
|
270
|
+
],
|
|
271
|
+
a: ["aria-*", "class", "data-*", "href", "id", "name", "rel", "style", "target", "title"],
|
|
272
|
+
button: ["aria-*", "class", "data-*", "disabled", "id", "style", "title", "type"],
|
|
273
|
+
iframe: ["allow", "allowfullscreen", "class", "data-*", "height", "loading", "src", "style", "title", "width"],
|
|
274
|
+
img: ["alt", "class", "data-*", "height", "id", "loading", "sizes", "src", "srcset", "style", "title", "width"],
|
|
275
|
+
source: ["media", "src", "srcset", "type"],
|
|
276
|
+
video: ["autoplay", "class", "controls", "height", "loop", "muted", "playsinline", "poster", "preload", "src", "style", "width"]
|
|
277
|
+
};
|
|
278
|
+
var allowedIframeHosts = [
|
|
279
|
+
"calendar.google.com",
|
|
280
|
+
"calendly.com",
|
|
281
|
+
"player.vimeo.com",
|
|
282
|
+
"www.google.com",
|
|
283
|
+
"www.youtube.com",
|
|
284
|
+
"youtube.com"
|
|
285
|
+
];
|
|
286
|
+
var sanitizeBuilderHtml = (value) => {
|
|
287
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
288
|
+
return "";
|
|
289
|
+
}
|
|
290
|
+
return (0, import_sanitize_html.default)(value, {
|
|
291
|
+
allowedAttributes,
|
|
292
|
+
allowedIframeHostnames: allowedIframeHosts,
|
|
293
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
294
|
+
allowedSchemesByTag: {
|
|
295
|
+
img: ["http", "https", "data"]
|
|
296
|
+
},
|
|
297
|
+
allowedTags,
|
|
298
|
+
allowProtocolRelative: false,
|
|
299
|
+
parseStyleAttributes: false
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
var sanitizeBuilderCss = (value) => {
|
|
303
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
304
|
+
return "";
|
|
305
|
+
}
|
|
306
|
+
return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
|
|
307
|
+
};
|
|
308
|
+
var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
|
|
309
|
+
const trimmed = part.trim();
|
|
310
|
+
if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
|
|
311
|
+
return trimmed;
|
|
312
|
+
}
|
|
313
|
+
if (/^(html|body|:root)\b/i.test(trimmed)) {
|
|
314
|
+
return trimmed.replace(/^(html|body|:root)\b/i, scope);
|
|
315
|
+
}
|
|
316
|
+
return `${scope} ${trimmed}`;
|
|
317
|
+
}).filter(Boolean).join(", ");
|
|
318
|
+
var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
|
|
319
|
+
const css = sanitizeBuilderCss(value);
|
|
320
|
+
if (!css) {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
let output = "";
|
|
324
|
+
let cursor = 0;
|
|
325
|
+
const rulePattern = /([^{}]+)\{/g;
|
|
326
|
+
let match;
|
|
327
|
+
while ((match = rulePattern.exec(css)) !== null) {
|
|
328
|
+
const selectorStart = match.index;
|
|
329
|
+
const selector = match[1];
|
|
330
|
+
output += css.slice(cursor, selectorStart);
|
|
331
|
+
const trimmedSelector = selector.trim();
|
|
332
|
+
if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
|
|
333
|
+
output += `${selector}{`;
|
|
334
|
+
} else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
|
|
335
|
+
output += `${selector}{`;
|
|
336
|
+
} else {
|
|
337
|
+
output += `${scopeSelector(selector, scope)} {`;
|
|
338
|
+
}
|
|
339
|
+
cursor = rulePattern.lastIndex;
|
|
340
|
+
}
|
|
341
|
+
output += css.slice(cursor);
|
|
342
|
+
return output;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/builder-v2/validation.ts
|
|
346
|
+
var hasMatch = (value, pattern) => pattern.test(value);
|
|
347
|
+
var validateBuilderV2Output = (input) => {
|
|
348
|
+
const html = typeof input.html === "string" ? input.html : "";
|
|
349
|
+
const css = typeof input.css === "string" ? input.css : "";
|
|
350
|
+
const issues = [];
|
|
351
|
+
if (!html.trim()) {
|
|
352
|
+
issues.push({
|
|
353
|
+
code: "empty-page",
|
|
354
|
+
message: "This page has no rendered content.",
|
|
355
|
+
path: "compiledHtml",
|
|
356
|
+
severity: "error"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (!hasMatch(html, /<h1\b/i)) {
|
|
360
|
+
issues.push({
|
|
361
|
+
code: "missing-h1",
|
|
362
|
+
message: "Add one H1 so the published page has a clear primary heading.",
|
|
363
|
+
path: "compiledHtml",
|
|
364
|
+
severity: "warning"
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (hasMatch(html, /\son[a-z]+\s*=/i)) {
|
|
368
|
+
issues.push({
|
|
369
|
+
code: "inline-event-handler",
|
|
370
|
+
message: "Inline event handlers are not allowed in published builder HTML.",
|
|
371
|
+
path: "compiledHtml",
|
|
372
|
+
severity: "error"
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (hasMatch(html, /<(script|object|embed)\b/i)) {
|
|
376
|
+
issues.push({
|
|
377
|
+
code: "unsafe-element",
|
|
378
|
+
message: "Script, object, and embed tags are not allowed in builder content.",
|
|
379
|
+
path: "compiledHtml",
|
|
380
|
+
severity: "error"
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
|
|
384
|
+
issues.push({
|
|
385
|
+
code: "unsafe-url",
|
|
386
|
+
message: "Links and media cannot use javascript URLs.",
|
|
387
|
+
path: "compiledHtml",
|
|
388
|
+
severity: "error"
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (hasMatch(css, /@import\s/i)) {
|
|
392
|
+
issues.push({
|
|
393
|
+
code: "css-import",
|
|
394
|
+
message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
|
|
395
|
+
path: "compiledCss",
|
|
396
|
+
severity: "warning"
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (hasMatch(css, /position\s*:\s*fixed/i)) {
|
|
400
|
+
issues.push({
|
|
401
|
+
code: "fixed-position",
|
|
402
|
+
message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
|
|
403
|
+
path: "compiledCss",
|
|
404
|
+
severity: "warning"
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return issues;
|
|
408
|
+
};
|
|
409
|
+
var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
|
|
410
|
+
|
|
411
|
+
// src/builder-v2/editor/defaultBlocks.ts
|
|
412
|
+
var registerOrionBuilderV2Blocks = (editor) => {
|
|
413
|
+
const blocks = editor.Blocks;
|
|
414
|
+
blocks.add("orion-nav", {
|
|
415
|
+
category: "Chrome",
|
|
416
|
+
content: `
|
|
417
|
+
<header class="orion-builder-v2-nav">
|
|
418
|
+
<a class="orion-builder-v2-logo" href="/">Brand</a>
|
|
419
|
+
<nav aria-label="Primary">
|
|
420
|
+
<a href="/">Home</a>
|
|
421
|
+
<a href="/about">About</a>
|
|
422
|
+
<a href="/contact">Contact</a>
|
|
423
|
+
</nav>
|
|
424
|
+
<a class="orion-builder-v2-button" href="/contact">Start</a>
|
|
425
|
+
</header>
|
|
426
|
+
`,
|
|
427
|
+
label: "Nav"
|
|
428
|
+
});
|
|
429
|
+
blocks.add("orion-section", {
|
|
430
|
+
category: "Layout",
|
|
431
|
+
content: `
|
|
432
|
+
<section class="orion-builder-v2-section">
|
|
433
|
+
<div class="orion-builder-v2-container">
|
|
434
|
+
<h2>Section Heading</h2>
|
|
435
|
+
<p>Add supporting content here.</p>
|
|
436
|
+
</div>
|
|
437
|
+
</section>
|
|
438
|
+
`,
|
|
439
|
+
label: "Section"
|
|
440
|
+
});
|
|
441
|
+
blocks.add("orion-hero", {
|
|
442
|
+
category: "Sections",
|
|
443
|
+
content: `
|
|
444
|
+
<section class="orion-builder-v2-hero">
|
|
445
|
+
<div class="orion-builder-v2-container">
|
|
446
|
+
<p class="orion-builder-v2-kicker">Optional kicker</p>
|
|
447
|
+
<h1>Build a stronger website</h1>
|
|
448
|
+
<p>Use this area for a clear value proposition and call to action.</p>
|
|
449
|
+
<a class="orion-builder-v2-button" href="/contact">Get started</a>
|
|
450
|
+
</div>
|
|
451
|
+
</section>
|
|
452
|
+
`,
|
|
453
|
+
label: "Hero"
|
|
454
|
+
});
|
|
455
|
+
blocks.add("orion-columns", {
|
|
456
|
+
category: "Layout",
|
|
457
|
+
content: `
|
|
458
|
+
<section class="orion-builder-v2-section">
|
|
459
|
+
<div class="orion-builder-v2-container orion-builder-v2-grid is-3">
|
|
460
|
+
<article class="orion-builder-v2-card"><h3>Column One</h3><p>Add content.</p></article>
|
|
461
|
+
<article class="orion-builder-v2-card"><h3>Column Two</h3><p>Add content.</p></article>
|
|
462
|
+
<article class="orion-builder-v2-card"><h3>Column Three</h3><p>Add content.</p></article>
|
|
463
|
+
</div>
|
|
464
|
+
</section>
|
|
465
|
+
`,
|
|
466
|
+
label: "Columns"
|
|
467
|
+
});
|
|
468
|
+
blocks.add("orion-card-grid", {
|
|
469
|
+
category: "Sections",
|
|
470
|
+
content: `
|
|
471
|
+
<section class="orion-builder-v2-section">
|
|
472
|
+
<div class="orion-builder-v2-container">
|
|
473
|
+
<p class="orion-builder-v2-kicker">Featured</p>
|
|
474
|
+
<h2>Cards that explain the offer</h2>
|
|
475
|
+
<div class="orion-builder-v2-grid is-3">
|
|
476
|
+
<article class="orion-builder-v2-card"><h3>Service One</h3><p>Describe the outcome clients care about.</p><a href="/contact">Learn more</a></article>
|
|
477
|
+
<article class="orion-builder-v2-card"><h3>Service Two</h3><p>Keep the card concise and scannable.</p><a href="/contact">Learn more</a></article>
|
|
478
|
+
<article class="orion-builder-v2-card"><h3>Service Three</h3><p>Use the same structure for visual rhythm.</p><a href="/contact">Learn more</a></article>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</section>
|
|
482
|
+
`,
|
|
483
|
+
label: "Card grid"
|
|
484
|
+
});
|
|
485
|
+
blocks.add("orion-gallery", {
|
|
486
|
+
category: "Media",
|
|
487
|
+
content: `
|
|
488
|
+
<section class="orion-builder-v2-section">
|
|
489
|
+
<div class="orion-builder-v2-container">
|
|
490
|
+
<h2>Gallery</h2>
|
|
491
|
+
<div class="orion-builder-v2-gallery">
|
|
492
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
493
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
494
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
495
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
</section>
|
|
499
|
+
`,
|
|
500
|
+
label: "Gallery"
|
|
501
|
+
});
|
|
502
|
+
blocks.add("orion-testimonials", {
|
|
503
|
+
category: "Sections",
|
|
504
|
+
content: `
|
|
505
|
+
<section class="orion-builder-v2-section is-muted">
|
|
506
|
+
<div class="orion-builder-v2-container">
|
|
507
|
+
<p class="orion-builder-v2-kicker">Testimonials</p>
|
|
508
|
+
<h2>What clients say</h2>
|
|
509
|
+
<div class="orion-builder-v2-grid is-3">
|
|
510
|
+
<blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
|
|
511
|
+
<blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
|
|
512
|
+
<blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</section>
|
|
516
|
+
`,
|
|
517
|
+
label: "Testimonials"
|
|
518
|
+
});
|
|
519
|
+
blocks.add("orion-faq", {
|
|
520
|
+
category: "Sections",
|
|
521
|
+
content: `
|
|
522
|
+
<section class="orion-builder-v2-section">
|
|
523
|
+
<div class="orion-builder-v2-container is-narrow">
|
|
524
|
+
<p class="orion-builder-v2-kicker">FAQ</p>
|
|
525
|
+
<h2>Questions answered</h2>
|
|
526
|
+
<details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
|
|
527
|
+
<details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
|
|
528
|
+
<details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
|
|
529
|
+
</div>
|
|
530
|
+
</section>
|
|
531
|
+
`,
|
|
532
|
+
label: "FAQ"
|
|
533
|
+
});
|
|
534
|
+
blocks.add("orion-pricing", {
|
|
535
|
+
category: "Commerce",
|
|
536
|
+
content: `
|
|
537
|
+
<section class="orion-builder-v2-section">
|
|
538
|
+
<div class="orion-builder-v2-container">
|
|
539
|
+
<p class="orion-builder-v2-kicker">Pricing</p>
|
|
540
|
+
<h2>Simple package options</h2>
|
|
541
|
+
<div class="orion-builder-v2-grid is-3">
|
|
542
|
+
<article class="orion-builder-v2-card"><h3>Starter</h3><p class="orion-builder-v2-price">$500</p><p>Best for focused launches.</p><a class="orion-builder-v2-button" href="/contact">Choose Starter</a></article>
|
|
543
|
+
<article class="orion-builder-v2-card is-featured"><h3>Growth</h3><p class="orion-builder-v2-price">$1,500</p><p>Best for expanding teams.</p><a class="orion-builder-v2-button" href="/contact">Choose Growth</a></article>
|
|
544
|
+
<article class="orion-builder-v2-card"><h3>Custom</h3><p class="orion-builder-v2-price">Quote</p><p>Best for complex builds.</p><a class="orion-builder-v2-button" href="/contact">Talk with us</a></article>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
</section>
|
|
548
|
+
`,
|
|
549
|
+
label: "Pricing"
|
|
550
|
+
});
|
|
551
|
+
blocks.add("orion-button", {
|
|
552
|
+
category: "Basic",
|
|
553
|
+
content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
|
|
554
|
+
label: "Button"
|
|
555
|
+
});
|
|
556
|
+
blocks.add("orion-image", {
|
|
557
|
+
category: "Basic",
|
|
558
|
+
content: '<img alt="Image" class="orion-builder-v2-image" src="https://placehold.co/1200x700" />',
|
|
559
|
+
label: "Image"
|
|
560
|
+
});
|
|
561
|
+
blocks.add("orion-form-embed", {
|
|
562
|
+
category: "Dynamic",
|
|
563
|
+
content: `
|
|
564
|
+
<div
|
|
565
|
+
class="orion-builder-v2-dynamic-placeholder"
|
|
566
|
+
data-orion-component="formEmbed"
|
|
567
|
+
data-orion-form-slug="contact"
|
|
568
|
+
>
|
|
569
|
+
<strong>Form Embed</strong>
|
|
570
|
+
<span>contact</span>
|
|
571
|
+
</div>
|
|
572
|
+
`,
|
|
573
|
+
label: "Form"
|
|
574
|
+
});
|
|
575
|
+
blocks.add("orion-footer", {
|
|
576
|
+
category: "Chrome",
|
|
577
|
+
content: `
|
|
578
|
+
<footer class="orion-builder-v2-footer">
|
|
579
|
+
<div>
|
|
580
|
+
<strong>Brand</strong>
|
|
581
|
+
<p>Short positioning line for the site footer.</p>
|
|
582
|
+
</div>
|
|
583
|
+
<nav aria-label="Footer">
|
|
584
|
+
<a href="/privacy">Privacy</a>
|
|
585
|
+
<a href="/contact">Contact</a>
|
|
586
|
+
</nav>
|
|
587
|
+
</footer>
|
|
588
|
+
`,
|
|
589
|
+
label: "Footer"
|
|
590
|
+
});
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/builder-v2/editor/projectComponents.ts
|
|
594
|
+
var normalizeDefinition = (type, value) => {
|
|
595
|
+
if (!value || typeof value === "function") {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
...value,
|
|
600
|
+
type: value.type || type
|
|
601
|
+
};
|
|
602
|
+
};
|
|
603
|
+
var decodeHtmlAttribute2 = (value) => {
|
|
604
|
+
if (typeof value !== "string") {
|
|
605
|
+
return "";
|
|
606
|
+
}
|
|
607
|
+
let decoded = value;
|
|
608
|
+
for (let index = 0; index < 3; index += 1) {
|
|
609
|
+
const next = decoded.replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
610
|
+
if (next === decoded) {
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
decoded = next;
|
|
614
|
+
}
|
|
615
|
+
return decoded;
|
|
616
|
+
};
|
|
617
|
+
var attrToPropName = (name) => name.replace(/^data-orion-/, "").replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
618
|
+
var propToAttrName = (name) => `data-orion-${name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`;
|
|
619
|
+
var propsFromAttributes = (attributes = {}) => {
|
|
620
|
+
const props = {};
|
|
621
|
+
Object.keys(attributes).forEach((name) => {
|
|
622
|
+
if (!name.startsWith("data-") || name === "data-orion-component" || name === "data-orion-id") {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const value = decodeHtmlAttribute2(attributes[name]);
|
|
626
|
+
if (name === "data-orion-props") {
|
|
627
|
+
try {
|
|
628
|
+
const parsed = JSON.parse(value);
|
|
629
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
630
|
+
Object.assign(props, parsed);
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
props.propsJson = value;
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
props[attrToPropName(name)] = value;
|
|
638
|
+
});
|
|
639
|
+
return props;
|
|
640
|
+
};
|
|
641
|
+
var previewForDefinition = (definition, props) => definition.editorPreview?.(props) || `<div class="orion-builder-v2-dynamic-placeholder"><strong>${definition.label}</strong></div>`;
|
|
642
|
+
var lockPreviewChildren = (component) => {
|
|
643
|
+
const children = typeof component.components === "function" ? component.components() : null;
|
|
644
|
+
const childList = children && typeof children === "object" && "forEach" in children ? children : null;
|
|
645
|
+
childList?.forEach((child) => {
|
|
646
|
+
if (typeof child.set === "function") {
|
|
647
|
+
child.set({
|
|
648
|
+
copyable: false,
|
|
649
|
+
draggable: false,
|
|
650
|
+
droppable: false,
|
|
651
|
+
editable: false,
|
|
652
|
+
highlightable: false,
|
|
653
|
+
hoverable: false,
|
|
654
|
+
removable: false,
|
|
655
|
+
selectable: false
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
lockPreviewChildren(child);
|
|
659
|
+
});
|
|
660
|
+
};
|
|
661
|
+
var parseJsonArray = (value) => {
|
|
662
|
+
const decoded = decodeHtmlAttribute2(value);
|
|
663
|
+
if (!decoded) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const parsed = JSON.parse(decoded);
|
|
668
|
+
return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
669
|
+
} catch {
|
|
670
|
+
return [];
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
var updateJsonListAttribute = ({
|
|
674
|
+
field,
|
|
675
|
+
index,
|
|
676
|
+
listAttr,
|
|
677
|
+
model,
|
|
678
|
+
value
|
|
679
|
+
}) => {
|
|
680
|
+
const attrs = model.getAttributes?.() || {};
|
|
681
|
+
const list = parseJsonArray(attrs[listAttr]);
|
|
682
|
+
if (!list[index]) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
list[index] = {
|
|
686
|
+
...list[index],
|
|
687
|
+
[field]: value
|
|
688
|
+
};
|
|
689
|
+
model.addAttributes?.({
|
|
690
|
+
[listAttr]: JSON.stringify(list)
|
|
691
|
+
});
|
|
692
|
+
};
|
|
693
|
+
var addJsonListItem = ({
|
|
694
|
+
listAttr,
|
|
695
|
+
model,
|
|
696
|
+
template
|
|
697
|
+
}) => {
|
|
698
|
+
const attrs = model.getAttributes?.() || {};
|
|
699
|
+
const list = parseJsonArray(attrs[listAttr]);
|
|
700
|
+
list.push(template);
|
|
701
|
+
model.addAttributes?.({
|
|
702
|
+
[listAttr]: JSON.stringify(list)
|
|
703
|
+
});
|
|
704
|
+
};
|
|
705
|
+
var duplicateJsonListItem = ({
|
|
706
|
+
index,
|
|
707
|
+
listAttr,
|
|
708
|
+
model
|
|
709
|
+
}) => {
|
|
710
|
+
const attrs = model.getAttributes?.() || {};
|
|
711
|
+
const list = parseJsonArray(attrs[listAttr]);
|
|
712
|
+
const item = list[index];
|
|
713
|
+
if (!item) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
list.splice(index + 1, 0, {
|
|
717
|
+
...item,
|
|
718
|
+
title: typeof item.title === "string" ? `${item.title} copy` : item.title
|
|
719
|
+
});
|
|
720
|
+
model.addAttributes?.({
|
|
721
|
+
[listAttr]: JSON.stringify(list)
|
|
722
|
+
});
|
|
723
|
+
};
|
|
724
|
+
var removeJsonListItem = ({
|
|
725
|
+
index,
|
|
726
|
+
listAttr,
|
|
727
|
+
model
|
|
728
|
+
}) => {
|
|
729
|
+
const attrs = model.getAttributes?.() || {};
|
|
730
|
+
const list = parseJsonArray(attrs[listAttr]);
|
|
731
|
+
if (!list[index]) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
list.splice(index, 1);
|
|
735
|
+
model.addAttributes?.({
|
|
736
|
+
[listAttr]: JSON.stringify(list)
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
var parseTemplateAttribute = (value) => {
|
|
740
|
+
const decoded = decodeHtmlAttribute2(value);
|
|
741
|
+
if (!decoded) {
|
|
742
|
+
return {
|
|
743
|
+
description: "Describe this item.",
|
|
744
|
+
imageURL: "",
|
|
745
|
+
title: "New item"
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
const parsed = JSON.parse(decoded);
|
|
750
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
|
|
751
|
+
description: "Describe this item.",
|
|
752
|
+
imageURL: "",
|
|
753
|
+
title: "New item"
|
|
754
|
+
};
|
|
755
|
+
} catch {
|
|
756
|
+
return {
|
|
757
|
+
description: "Describe this item.",
|
|
758
|
+
imageURL: "",
|
|
759
|
+
title: "New item"
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
var chooseAsset = (editor, currentSrc, callback) => {
|
|
764
|
+
const assetManager = editor?.AssetManager;
|
|
765
|
+
if (!assetManager?.open) {
|
|
766
|
+
const src = window.prompt("Image URL");
|
|
767
|
+
if (src) {
|
|
768
|
+
callback(src);
|
|
769
|
+
}
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (currentSrc) {
|
|
773
|
+
assetManager.add?.({
|
|
774
|
+
name: `Current image - ${currentSrc.split("/").pop() || currentSrc}`,
|
|
775
|
+
src: currentSrc,
|
|
776
|
+
type: "image"
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
assetManager.open({
|
|
780
|
+
select(asset) {
|
|
781
|
+
const src = typeof asset === "string" ? asset : asset && typeof asset === "object" && "get" in asset && typeof asset.get === "function" ? String(asset.get("src") || "") : asset && typeof asset === "object" && "src" in asset ? String(asset.src || "") : "";
|
|
782
|
+
if (src) {
|
|
783
|
+
callback(src);
|
|
784
|
+
}
|
|
785
|
+
assetManager.close?.();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
};
|
|
789
|
+
var bindEditablePreview = (view, editor) => {
|
|
790
|
+
const root = view.el;
|
|
791
|
+
if (!root) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
root.querySelectorAll("[data-orion-action]").forEach((element) => {
|
|
795
|
+
const action = element.dataset.orionAction || "";
|
|
796
|
+
const listName = element.dataset.orionEditList || "";
|
|
797
|
+
const listIndex = Number(element.dataset.orionEditIndex);
|
|
798
|
+
const listAttr = listName ? propToAttrName(listName) : "";
|
|
799
|
+
element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
|
|
800
|
+
const runAction = (event) => {
|
|
801
|
+
event.preventDefault();
|
|
802
|
+
event.stopPropagation();
|
|
803
|
+
if (typeof event.stopImmediatePropagation === "function") {
|
|
804
|
+
event.stopImmediatePropagation();
|
|
805
|
+
}
|
|
806
|
+
editor.select?.(view.model);
|
|
807
|
+
if (!listAttr) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (action === "add-list-item") {
|
|
811
|
+
addJsonListItem({
|
|
812
|
+
listAttr,
|
|
813
|
+
model: view.model,
|
|
814
|
+
template: parseTemplateAttribute(element.dataset.orionItemTemplate)
|
|
815
|
+
});
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (!Number.isInteger(listIndex)) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (action === "duplicate-list-item") {
|
|
822
|
+
duplicateJsonListItem({
|
|
823
|
+
index: listIndex,
|
|
824
|
+
listAttr,
|
|
825
|
+
model: view.model
|
|
826
|
+
});
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (action === "remove-list-item") {
|
|
830
|
+
removeJsonListItem({
|
|
831
|
+
index: listIndex,
|
|
832
|
+
listAttr,
|
|
833
|
+
model: view.model
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
element.addEventListener("pointerdown", runAction, true);
|
|
838
|
+
element.addEventListener("keydown", (event) => {
|
|
839
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
840
|
+
runAction(event);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
|
|
845
|
+
const field = element.dataset.orionEditField || "";
|
|
846
|
+
const listName = element.dataset.orionEditList || "";
|
|
847
|
+
const listIndex = Number(element.dataset.orionEditIndex);
|
|
848
|
+
const attrName = listName ? propToAttrName(listName) : propToAttrName(field);
|
|
849
|
+
const isImage = element.dataset.orionEditKind === "image" || element instanceof HTMLImageElement;
|
|
850
|
+
const placeholder = element.dataset.orionPlaceholder || "";
|
|
851
|
+
element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
|
|
852
|
+
element.style.cursor = "text";
|
|
853
|
+
element.addEventListener("pointerdown", (event) => {
|
|
854
|
+
event.stopPropagation();
|
|
855
|
+
if (typeof event.stopImmediatePropagation === "function") {
|
|
856
|
+
event.stopImmediatePropagation();
|
|
857
|
+
}
|
|
858
|
+
editor.select?.(view.model);
|
|
859
|
+
}, true);
|
|
860
|
+
element.addEventListener("click", (event) => {
|
|
861
|
+
event.stopPropagation();
|
|
862
|
+
editor.select?.(view.model);
|
|
863
|
+
if (!isImage) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
element.style.cursor = "pointer";
|
|
867
|
+
const currentSrc = element instanceof HTMLImageElement ? element.currentSrc || element.src || "" : element.style.backgroundImage.replace(/^url\(["']?/, "").replace(/["']?\)$/, "");
|
|
868
|
+
chooseAsset(editor, currentSrc, (src) => {
|
|
869
|
+
if (listName && Number.isInteger(listIndex)) {
|
|
870
|
+
updateJsonListAttribute({
|
|
871
|
+
field,
|
|
872
|
+
index: listIndex,
|
|
873
|
+
listAttr: attrName,
|
|
874
|
+
model: view.model,
|
|
875
|
+
value: src
|
|
876
|
+
});
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
view.model.addAttributes?.({
|
|
880
|
+
[attrName]: src
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
if (isImage) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
element.setAttribute("contenteditable", "true");
|
|
888
|
+
element.setAttribute("spellcheck", "true");
|
|
889
|
+
const commit = () => {
|
|
890
|
+
const typedValue = element.innerText.trim();
|
|
891
|
+
const value = placeholder && typedValue === placeholder ? "" : typedValue;
|
|
892
|
+
if (listName && Number.isInteger(listIndex)) {
|
|
893
|
+
updateJsonListAttribute({
|
|
894
|
+
field,
|
|
895
|
+
index: listIndex,
|
|
896
|
+
listAttr: attrName,
|
|
897
|
+
model: view.model,
|
|
898
|
+
value
|
|
899
|
+
});
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
view.model.addAttributes?.({
|
|
903
|
+
[attrName]: value
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
element.addEventListener("blur", commit);
|
|
907
|
+
element.addEventListener("keydown", (event) => {
|
|
908
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
909
|
+
event.preventDefault();
|
|
910
|
+
element.blur();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
};
|
|
915
|
+
var registerProjectDynamicComponents = (editor, adapter) => {
|
|
916
|
+
const components = adapter?.components || {};
|
|
917
|
+
Object.keys(components).forEach((type) => {
|
|
918
|
+
const definition = normalizeDefinition(type, components[type]);
|
|
919
|
+
if (!definition) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
editor.DomComponents.addType(`orion-${type}`, {
|
|
923
|
+
model: {
|
|
924
|
+
defaults: {
|
|
925
|
+
attributes: {
|
|
926
|
+
"data-orion-component": type,
|
|
927
|
+
"data-orion-id": `${type}-${Date.now()}`
|
|
928
|
+
},
|
|
929
|
+
components: "",
|
|
930
|
+
droppable: false,
|
|
931
|
+
tagName: "div",
|
|
932
|
+
traits: [
|
|
933
|
+
...(definition.traits || []).map((trait) => ({
|
|
934
|
+
label: trait.label,
|
|
935
|
+
name: `data-orion-${trait.name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`,
|
|
936
|
+
options: trait.options,
|
|
937
|
+
placeholder: trait.placeholder,
|
|
938
|
+
type: trait.type
|
|
939
|
+
}))
|
|
940
|
+
]
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
view: {
|
|
944
|
+
init() {
|
|
945
|
+
this.listenTo(this.model, "change:attributes", this.renderPreview);
|
|
946
|
+
this.renderPreview();
|
|
947
|
+
},
|
|
948
|
+
renderPreview() {
|
|
949
|
+
const attributes = this.model.getAttributes?.() || {};
|
|
950
|
+
const props = propsFromAttributes(attributes);
|
|
951
|
+
this.model.components(previewForDefinition(definition, props));
|
|
952
|
+
lockPreviewChildren(this.model);
|
|
953
|
+
queueMicrotask(() => bindEditablePreview(this, editor));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
editor.Blocks.add(`orion-dynamic-${type}`, {
|
|
958
|
+
category: "Project",
|
|
959
|
+
content: {
|
|
960
|
+
type: `orion-${type}`
|
|
961
|
+
},
|
|
962
|
+
label: definition.label
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
// src/builder-v2/editor/GrapesPageEditor.tsx
|
|
968
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
969
|
+
var decodeHtmlAttribute3 = (value) => {
|
|
970
|
+
if (typeof value !== "string") {
|
|
971
|
+
return "";
|
|
972
|
+
}
|
|
973
|
+
return value.replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
974
|
+
};
|
|
975
|
+
var parseItemsJson = (value) => {
|
|
976
|
+
const decoded = decodeHtmlAttribute3(value);
|
|
977
|
+
if (!decoded) {
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
const parsed = JSON.parse(decoded);
|
|
982
|
+
return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
|
|
983
|
+
} catch {
|
|
984
|
+
return [];
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
var postToParent = (payload) => {
|
|
988
|
+
window.parent?.postMessage(
|
|
989
|
+
{
|
|
990
|
+
source: "payload-visual-builder-child",
|
|
991
|
+
...payload
|
|
992
|
+
},
|
|
993
|
+
"*"
|
|
994
|
+
);
|
|
995
|
+
};
|
|
996
|
+
var buildSavePayload = (editor, status, projectData) => ({
|
|
997
|
+
builderMode: "grapes-v2",
|
|
998
|
+
compiledCss: scopeBuilderCss(editor.getCss()),
|
|
999
|
+
compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
|
|
1000
|
+
projectData,
|
|
1001
|
+
status
|
|
1002
|
+
});
|
|
1003
|
+
var parsePayloadErrorMessage = async (response, fallback) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const json = await response.json();
|
|
1006
|
+
return json.errors?.[0]?.message || json.message || fallback;
|
|
1007
|
+
} catch {
|
|
1008
|
+
const raw = await response.text();
|
|
1009
|
+
return raw.trim() || fallback;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var getRelationID = (value) => {
|
|
1013
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
1014
|
+
return value;
|
|
1015
|
+
}
|
|
1016
|
+
if (!value || typeof value !== "object") {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const id = value.id;
|
|
1020
|
+
return typeof id === "number" || typeof id === "string" ? id : null;
|
|
1021
|
+
};
|
|
1022
|
+
var normalizeAssetSrc = (value) => {
|
|
1023
|
+
const trimmed = value.trim();
|
|
1024
|
+
if (!trimmed) {
|
|
1025
|
+
return "";
|
|
1026
|
+
}
|
|
1027
|
+
if (/^(https?:)?\/\//i.test(trimmed) || trimmed.startsWith("/")) {
|
|
1028
|
+
return trimmed;
|
|
1029
|
+
}
|
|
1030
|
+
return `/${trimmed}`;
|
|
1031
|
+
};
|
|
1032
|
+
var mediaDocImageCandidates = (doc) => {
|
|
1033
|
+
const candidates = [];
|
|
1034
|
+
if (doc.sizes && typeof doc.sizes === "object") {
|
|
1035
|
+
Object.values(doc.sizes).forEach((size) => {
|
|
1036
|
+
if (size?.url) {
|
|
1037
|
+
candidates.push({ src: normalizeAssetSrc(size.url), width: size.width });
|
|
1038
|
+
} else if (size?.filename) {
|
|
1039
|
+
candidates.push({ src: `/api/media/file/${encodeURIComponent(size.filename)}`, width: size.width });
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof doc.thumbnailURL === "string" && doc.thumbnailURL.length > 0) {
|
|
1044
|
+
candidates.push({ src: normalizeAssetSrc(doc.thumbnailURL), width: 360 });
|
|
1045
|
+
}
|
|
1046
|
+
if (typeof doc.url === "string" && doc.url.length > 0) {
|
|
1047
|
+
candidates.push({ src: normalizeAssetSrc(doc.url), width: doc.width });
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof doc.filename === "string" && doc.filename.length > 0) {
|
|
1050
|
+
candidates.push({ src: `/api/media/file/${encodeURIComponent(doc.filename)}`, width: doc.width });
|
|
1051
|
+
}
|
|
1052
|
+
return candidates.filter((candidate, index, all) => candidate.src && all.findIndex((item) => item.src === candidate.src) === index);
|
|
1053
|
+
};
|
|
1054
|
+
var pickMediaAssetSrc = (doc) => {
|
|
1055
|
+
const candidates = mediaDocImageCandidates(doc);
|
|
1056
|
+
if (candidates.length === 0) {
|
|
1057
|
+
return "";
|
|
1058
|
+
}
|
|
1059
|
+
const absolute = candidates.find((candidate) => /^(https?:)?\/\//i.test(candidate.src));
|
|
1060
|
+
if (absolute) {
|
|
1061
|
+
return absolute.src;
|
|
1062
|
+
}
|
|
1063
|
+
const fullSize = candidates.find((candidate) => candidate.src === doc.url);
|
|
1064
|
+
if (fullSize) {
|
|
1065
|
+
return fullSize.src;
|
|
1066
|
+
}
|
|
1067
|
+
const widest = [...candidates].sort((left, right) => (right.width || 0) - (left.width || 0))[0];
|
|
1068
|
+
return widest?.src || candidates[0]?.src || "";
|
|
1069
|
+
};
|
|
1070
|
+
var mediaDocToAsset = (doc) => {
|
|
1071
|
+
const id = getRelationID(doc);
|
|
1072
|
+
const filename = typeof doc.filename === "string" ? doc.filename : "";
|
|
1073
|
+
const src = pickMediaAssetSrc(doc);
|
|
1074
|
+
if (id === null || !src) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
alt: typeof doc.alt === "string" ? doc.alt : "",
|
|
1079
|
+
id,
|
|
1080
|
+
name: filename || String(id),
|
|
1081
|
+
src,
|
|
1082
|
+
type: "image"
|
|
1083
|
+
};
|
|
1084
|
+
};
|
|
1085
|
+
var imageCanLoad = (src) => new Promise((resolve) => {
|
|
1086
|
+
if (!src) {
|
|
1087
|
+
resolve(false);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const image = new Image();
|
|
1091
|
+
image.onload = () => resolve(true);
|
|
1092
|
+
image.onerror = () => resolve(false);
|
|
1093
|
+
image.src = src;
|
|
1094
|
+
});
|
|
1095
|
+
var pruneBrokenImageAssets = async (editor) => {
|
|
1096
|
+
const assetManager = editor.AssetManager;
|
|
1097
|
+
const existingAssets = assetManager.getAll?.() || [];
|
|
1098
|
+
for (const asset of existingAssets) {
|
|
1099
|
+
const src = typeof asset.get === "function" ? String(asset.get("src") || "") : "";
|
|
1100
|
+
if (src && !await imageCanLoad(src)) {
|
|
1101
|
+
assetManager.remove?.(asset);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
var extractUploadedMedia = (value) => {
|
|
1106
|
+
const candidate = value && typeof value === "object" && "doc" in value ? value.doc : value;
|
|
1107
|
+
if (!candidate || typeof candidate !== "object") {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
const id = getRelationID(candidate);
|
|
1111
|
+
if (id === null) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
const typed = candidate;
|
|
1115
|
+
return {
|
|
1116
|
+
alt: typeof typed.alt === "string" ? typed.alt : "",
|
|
1117
|
+
filename: typeof typed.filename === "string" ? typed.filename : "",
|
|
1118
|
+
id,
|
|
1119
|
+
sizes: typed.sizes && typeof typed.sizes === "object" ? typed.sizes : void 0,
|
|
1120
|
+
thumbnailURL: typeof typed.thumbnailURL === "string" ? typed.thumbnailURL : "",
|
|
1121
|
+
url: typeof typed.url === "string" ? typed.url : "",
|
|
1122
|
+
width: typeof typed.width === "number" ? typed.width : void 0
|
|
1123
|
+
};
|
|
1124
|
+
};
|
|
1125
|
+
var loadPayloadMediaAssets = async (editor) => {
|
|
1126
|
+
const response = await fetch(`/api/media?depth=1&limit=100&sort=-updatedAt&_=${Date.now()}`, {
|
|
1127
|
+
cache: "no-store",
|
|
1128
|
+
credentials: "include"
|
|
1129
|
+
});
|
|
1130
|
+
if (!response.ok) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const json = await response.json();
|
|
1134
|
+
const candidateAssets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
|
|
1135
|
+
const assets = [];
|
|
1136
|
+
for (const asset of candidateAssets) {
|
|
1137
|
+
if (await imageCanLoad(asset.src)) {
|
|
1138
|
+
assets.push(asset);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (assets.length > 0) {
|
|
1142
|
+
editor.AssetManager.add(assets);
|
|
1143
|
+
}
|
|
1144
|
+
await pruneBrokenImageAssets(editor);
|
|
1145
|
+
};
|
|
1146
|
+
var uploadPayloadMediaAssets = async (editor, files) => {
|
|
1147
|
+
const fileArray = Array.from(files);
|
|
1148
|
+
const uploadedAssets = [];
|
|
1149
|
+
for (const file of fileArray) {
|
|
1150
|
+
const optimizedFile = await optimizeImageForUpload(file);
|
|
1151
|
+
if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
|
|
1152
|
+
throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
|
|
1153
|
+
}
|
|
1154
|
+
const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
|
|
1155
|
+
const formData = new FormData();
|
|
1156
|
+
formData.set("_payload", JSON.stringify({ alt: fallbackAlt || "Uploaded image" }));
|
|
1157
|
+
formData.set("alt", fallbackAlt || "Uploaded image");
|
|
1158
|
+
formData.set("file", optimizedFile);
|
|
1159
|
+
const response = await fetch("/api/media", {
|
|
1160
|
+
body: formData,
|
|
1161
|
+
credentials: "include",
|
|
1162
|
+
method: "POST"
|
|
1163
|
+
});
|
|
1164
|
+
if (!response.ok) {
|
|
1165
|
+
throw new Error(await parsePayloadErrorMessage(response, "Could not upload image."));
|
|
1166
|
+
}
|
|
1167
|
+
const uploaded = extractUploadedMedia(await response.json());
|
|
1168
|
+
const asset = uploaded ? mediaDocToAsset(uploaded) : null;
|
|
1169
|
+
if (asset) {
|
|
1170
|
+
uploadedAssets.push(asset);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (uploadedAssets.length > 0) {
|
|
1174
|
+
editor.AssetManager.add(uploadedAssets);
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
function GrapesPageEditor({
|
|
1178
|
+
adapter,
|
|
1179
|
+
autosaveIntervalMs = 3e4,
|
|
1180
|
+
initialData,
|
|
1181
|
+
pageID
|
|
1182
|
+
}) {
|
|
1183
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
1184
|
+
const editorRef = (0, import_react.useRef)(null);
|
|
1185
|
+
const autosaveTimerRef = (0, import_react.useRef)(null);
|
|
1186
|
+
const saveRef = (0, import_react.useRef)(async () => void 0);
|
|
1187
|
+
const selectedComponentRef = (0, import_react.useRef)(null);
|
|
1188
|
+
const [error, setError] = (0, import_react.useState)("");
|
|
1189
|
+
const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
|
|
1190
|
+
const [isDirty, setIsDirty] = (0, import_react.useState)(false);
|
|
1191
|
+
const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
|
|
1192
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
1193
|
+
const [publicationState, setPublicationState] = (0, import_react.useState)("live");
|
|
1194
|
+
const [selectedDevice, setSelectedDevice] = (0, import_react.useState)("desktop");
|
|
1195
|
+
const [saving, setSaving] = (0, import_react.useState)(null);
|
|
1196
|
+
const [saveMessage, setSaveMessage] = (0, import_react.useState)("");
|
|
1197
|
+
const [selectionSummary, setSelectionSummary] = (0, import_react.useState)(null);
|
|
1198
|
+
const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
|
|
1199
|
+
const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
|
|
1200
|
+
const pageTree = initialData?.meta?.pageTree || [];
|
|
1201
|
+
const summarizeSelectedComponent = (component) => {
|
|
1202
|
+
if (!component) {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
const attrs = component.getAttributes?.() || {};
|
|
1206
|
+
const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
|
|
1207
|
+
const rawName = String(component.get?.("name") || componentType || "Section");
|
|
1208
|
+
const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
|
|
1209
|
+
const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
|
|
1210
|
+
index,
|
|
1211
|
+
title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
|
|
1212
|
+
}));
|
|
1213
|
+
return {
|
|
1214
|
+
items: items.length > 0 ? items : void 0,
|
|
1215
|
+
label: label || "Section",
|
|
1216
|
+
type: componentType
|
|
1217
|
+
};
|
|
1218
|
+
};
|
|
1219
|
+
const updateSelectedItems = (updater) => {
|
|
1220
|
+
const component = selectedComponentRef.current;
|
|
1221
|
+
if (!component) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const attrs = component.getAttributes?.() || {};
|
|
1225
|
+
const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
|
|
1226
|
+
const nextItems = updater(currentItems);
|
|
1227
|
+
component.addAttributes?.({
|
|
1228
|
+
"data-orion-items-json": JSON.stringify(nextItems)
|
|
1229
|
+
});
|
|
1230
|
+
setSelectionSummary(summarizeSelectedComponent(component));
|
|
1231
|
+
};
|
|
1232
|
+
const updateHistoryState = (editor) => {
|
|
1233
|
+
const next = {
|
|
1234
|
+
canRedo: editor.UndoManager.hasRedo(),
|
|
1235
|
+
canUndo: editor.UndoManager.hasUndo()
|
|
1236
|
+
};
|
|
1237
|
+
setHistoryState(next);
|
|
1238
|
+
postToParent({
|
|
1239
|
+
...next,
|
|
1240
|
+
type: "history-state"
|
|
1241
|
+
});
|
|
1242
|
+
};
|
|
1243
|
+
const runValidation = (editor) => {
|
|
1244
|
+
const issues = validateBuilderV2Output({
|
|
1245
|
+
css: editor.getCss(),
|
|
1246
|
+
html: editor.getHtml()
|
|
1247
|
+
});
|
|
1248
|
+
setValidationIssues(issues);
|
|
1249
|
+
postToParent({ issues, type: "validation-state" });
|
|
1250
|
+
return issues;
|
|
1251
|
+
};
|
|
1252
|
+
const setDevice = (device) => {
|
|
1253
|
+
const editor = editorRef.current;
|
|
1254
|
+
if (!editor) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
editor.setDevice(device);
|
|
1258
|
+
setSelectedDevice(device);
|
|
1259
|
+
};
|
|
1260
|
+
(0, import_react.useEffect)(() => {
|
|
1261
|
+
let active = true;
|
|
1262
|
+
const init = async () => {
|
|
1263
|
+
if (!containerRef.current || editorRef.current) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
try {
|
|
1267
|
+
const grapesjs = (await import("grapesjs")).default;
|
|
1268
|
+
if (!active || !containerRef.current) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const projectData = normalizeBuilderV2ProjectData(initialData?.projectData, initialData?.title);
|
|
1272
|
+
const editor = grapesjs.init({
|
|
1273
|
+
assetManager: {
|
|
1274
|
+
uploadFile: async (event) => {
|
|
1275
|
+
const target = event.target;
|
|
1276
|
+
const dataTransfer = "dataTransfer" in event ? event.dataTransfer : null;
|
|
1277
|
+
const files = dataTransfer?.files || target?.files;
|
|
1278
|
+
if (!files || files.length === 0) {
|
|
1279
|
+
return void 0;
|
|
1280
|
+
}
|
|
1281
|
+
await uploadPayloadMediaAssets(editorRef.current || editor, files).catch((uploadError) => {
|
|
1282
|
+
setError(uploadError instanceof Error ? uploadError.message : "Could not upload image.");
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
blockManager: {
|
|
1287
|
+
appendTo: "#orion-builder-v2-blocks"
|
|
1288
|
+
},
|
|
1289
|
+
container: containerRef.current,
|
|
1290
|
+
deviceManager: {
|
|
1291
|
+
devices: [
|
|
1292
|
+
{ id: "desktop", name: "Desktop", width: "" },
|
|
1293
|
+
{ id: "tablet", name: "Tablet", width: "768px" },
|
|
1294
|
+
{ id: "mobile", name: "Mobile", width: "390px" }
|
|
1295
|
+
]
|
|
1296
|
+
},
|
|
1297
|
+
fromElement: false,
|
|
1298
|
+
height: "100%",
|
|
1299
|
+
panels: {
|
|
1300
|
+
defaults: []
|
|
1301
|
+
},
|
|
1302
|
+
selectorManager: {
|
|
1303
|
+
componentFirst: true
|
|
1304
|
+
},
|
|
1305
|
+
storageManager: false,
|
|
1306
|
+
styleManager: {
|
|
1307
|
+
appendTo: "#orion-builder-v2-styles",
|
|
1308
|
+
sectors: [
|
|
1309
|
+
{
|
|
1310
|
+
name: "Layout",
|
|
1311
|
+
open: true,
|
|
1312
|
+
properties: ["display", "position", "top", "right", "bottom", "left", "width", "height", "min-height", "margin", "padding"]
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: "Typography",
|
|
1316
|
+
open: true,
|
|
1317
|
+
properties: ["font-family", "font-size", "font-weight", "line-height", "letter-spacing", "color", "text-align"]
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
name: "Decoration",
|
|
1321
|
+
open: true,
|
|
1322
|
+
properties: ["background-color", "background", "border", "border-radius", "box-shadow", "opacity"]
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "Flex",
|
|
1326
|
+
open: false,
|
|
1327
|
+
properties: ["flex-direction", "justify-content", "align-items", "gap", "flex-wrap"]
|
|
1328
|
+
}
|
|
1329
|
+
]
|
|
1330
|
+
},
|
|
1331
|
+
traitManager: {
|
|
1332
|
+
appendTo: "#orion-builder-v2-traits"
|
|
1333
|
+
},
|
|
1334
|
+
width: "auto"
|
|
1335
|
+
});
|
|
1336
|
+
editorRef.current = editor;
|
|
1337
|
+
registerOrionBuilderV2Blocks(editor);
|
|
1338
|
+
registerProjectDynamicComponents(editor, adapter);
|
|
1339
|
+
editor.loadProjectData(projectData);
|
|
1340
|
+
void loadPayloadMediaAssets(editor);
|
|
1341
|
+
editor.on("update", () => {
|
|
1342
|
+
const hasDirtyChanges = editor.getDirtyCount() > 0;
|
|
1343
|
+
setIsDirty(hasDirtyChanges);
|
|
1344
|
+
postToParent({ dirty: hasDirtyChanges, type: "dirty-state" });
|
|
1345
|
+
updateHistoryState(editor);
|
|
1346
|
+
runValidation(editor);
|
|
1347
|
+
setSaveMessage("Unsaved changes");
|
|
1348
|
+
if (autosaveTimerRef.current) {
|
|
1349
|
+
window.clearTimeout(autosaveTimerRef.current);
|
|
1350
|
+
}
|
|
1351
|
+
autosaveTimerRef.current = window.setTimeout(() => {
|
|
1352
|
+
if (editor.getDirtyCount() > 0) {
|
|
1353
|
+
void saveRef.current("draft", { autosave: true });
|
|
1354
|
+
}
|
|
1355
|
+
}, autosaveIntervalMs);
|
|
1356
|
+
});
|
|
1357
|
+
editor.on("component:selected", (component) => {
|
|
1358
|
+
const typed = component;
|
|
1359
|
+
selectedComponentRef.current = typed;
|
|
1360
|
+
setSelectionSummary(summarizeSelectedComponent(typed));
|
|
1361
|
+
});
|
|
1362
|
+
setSelectedDevice(editor.getDevice() || "desktop");
|
|
1363
|
+
runValidation(editor);
|
|
1364
|
+
updateHistoryState(editor);
|
|
1365
|
+
setLoading(false);
|
|
1366
|
+
} catch (initError) {
|
|
1367
|
+
setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
|
|
1368
|
+
setLoading(false);
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
void init();
|
|
1372
|
+
return () => {
|
|
1373
|
+
active = false;
|
|
1374
|
+
if (autosaveTimerRef.current) {
|
|
1375
|
+
window.clearTimeout(autosaveTimerRef.current);
|
|
1376
|
+
}
|
|
1377
|
+
editorRef.current?.destroy();
|
|
1378
|
+
editorRef.current = null;
|
|
1379
|
+
};
|
|
1380
|
+
}, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
|
|
1381
|
+
const save = async (status, options = {}) => {
|
|
1382
|
+
const editor = editorRef.current;
|
|
1383
|
+
if (!editor || saving) {
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const issues = runValidation(editor);
|
|
1387
|
+
if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
|
|
1388
|
+
const message = "Resolve blocking validation errors before publishing.";
|
|
1389
|
+
setSaveMessage(message);
|
|
1390
|
+
postToParent({
|
|
1391
|
+
message,
|
|
1392
|
+
ok: false,
|
|
1393
|
+
status,
|
|
1394
|
+
type: "save-result"
|
|
1395
|
+
});
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
setSaving(status);
|
|
1399
|
+
try {
|
|
1400
|
+
const projectData = editor.getProjectData();
|
|
1401
|
+
const payload = buildSavePayload(editor, status, projectData);
|
|
1402
|
+
const dynamicComponents = parseBuilderV2DynamicComponents(String(payload.compiledHtml || ""));
|
|
1403
|
+
const endpoint = status === "draft" ? `/api/pages/${pageID}?draft=true` : `/api/pages/${pageID}`;
|
|
1404
|
+
const response = await fetch(endpoint, {
|
|
1405
|
+
body: JSON.stringify(
|
|
1406
|
+
status === "published" ? {
|
|
1407
|
+
_status: "published",
|
|
1408
|
+
builderDynamicComponents: dynamicComponents,
|
|
1409
|
+
builderMode: "grapes-v2",
|
|
1410
|
+
builderProjectData: projectData,
|
|
1411
|
+
builderPublishedProjectData: projectData,
|
|
1412
|
+
builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1413
|
+
builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1414
|
+
builderValidationIssues: issues,
|
|
1415
|
+
compiledCss: payload.compiledCss,
|
|
1416
|
+
compiledHtml: payload.compiledHtml
|
|
1417
|
+
} : {
|
|
1418
|
+
_status: "draft",
|
|
1419
|
+
builderAutosaveProjectData: options.autosave ? projectData : null,
|
|
1420
|
+
builderDynamicComponents: dynamicComponents,
|
|
1421
|
+
builderMode: "grapes-v2",
|
|
1422
|
+
builderProjectData: projectData,
|
|
1423
|
+
builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1424
|
+
builderValidationIssues: issues,
|
|
1425
|
+
compiledCss: payload.compiledCss,
|
|
1426
|
+
compiledHtml: payload.compiledHtml
|
|
1427
|
+
}
|
|
1428
|
+
),
|
|
1429
|
+
credentials: "include",
|
|
1430
|
+
headers: {
|
|
1431
|
+
"Content-Type": "application/json"
|
|
1432
|
+
},
|
|
1433
|
+
method: "PATCH"
|
|
1434
|
+
});
|
|
1435
|
+
if (!response.ok) {
|
|
1436
|
+
if (response.status === 401 || response.status === 403) {
|
|
1437
|
+
postToParent({ type: "session-expired" });
|
|
1438
|
+
}
|
|
1439
|
+
throw new Error(await parsePayloadErrorMessage(response, "Could not save this page."));
|
|
1440
|
+
}
|
|
1441
|
+
editor.clearDirtyCount();
|
|
1442
|
+
setIsDirty(false);
|
|
1443
|
+
setPublicationState(status === "published" ? "live" : "draft");
|
|
1444
|
+
postToParent({
|
|
1445
|
+
dirty: false,
|
|
1446
|
+
type: "dirty-state"
|
|
1447
|
+
});
|
|
1448
|
+
postToParent({
|
|
1449
|
+
message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
|
|
1450
|
+
ok: true,
|
|
1451
|
+
status,
|
|
1452
|
+
type: "save-result"
|
|
1453
|
+
});
|
|
1454
|
+
setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
|
|
1455
|
+
setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
|
|
1456
|
+
} catch (saveError) {
|
|
1457
|
+
const message = saveError instanceof Error ? saveError.message : "Could not save.";
|
|
1458
|
+
setSaveMessage(message);
|
|
1459
|
+
postToParent({
|
|
1460
|
+
message,
|
|
1461
|
+
ok: false,
|
|
1462
|
+
status,
|
|
1463
|
+
type: "save-result"
|
|
1464
|
+
});
|
|
1465
|
+
} finally {
|
|
1466
|
+
setSaving(null);
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
(0, import_react.useEffect)(() => {
|
|
1470
|
+
saveRef.current = save;
|
|
1471
|
+
}, [saving]);
|
|
1472
|
+
(0, import_react.useEffect)(() => {
|
|
1473
|
+
const onMessage = (event) => {
|
|
1474
|
+
const data = event.data;
|
|
1475
|
+
const editor = editorRef.current;
|
|
1476
|
+
if (!data || data.source !== "payload-visual-builder-parent" || !editor) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (data.type === "dirty-check-request") {
|
|
1480
|
+
postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
|
|
1481
|
+
updateHistoryState(editor);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (data.type === "history-check-request") {
|
|
1485
|
+
updateHistoryState(editor);
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
if (data.type === "undo") {
|
|
1489
|
+
editor.UndoManager.undo();
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (data.type === "redo") {
|
|
1493
|
+
editor.UndoManager.redo();
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (data.type === "save" && (data.status === "draft" || data.status === "published")) {
|
|
1497
|
+
void save(data.status);
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
window.addEventListener("message", onMessage);
|
|
1501
|
+
return () => window.removeEventListener("message", onMessage);
|
|
1502
|
+
}, [saving]);
|
|
1503
|
+
const statusLabel = saving ? saving === "published" ? "Publishing..." : "Saving draft..." : isDirty ? "Unsaved changes" : saveMessage && saveMessage !== "Unsaved changes" ? saveMessage : "All changes saved";
|
|
1504
|
+
const statusMeta = lastSavedAt ? `Last saved ${lastSavedAt}` : "Ready";
|
|
1505
|
+
const liveStatusLabel = isDirty ? "Save draft to update" : publicationState === "draft" ? "Unpublished draft changes" : "Live is up to date";
|
|
1506
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-editor", children: [
|
|
1507
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "orion-builder-v2-topbar", children: [
|
|
1508
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1509
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
|
|
1510
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: initialData?.title || "Untitled page" })
|
|
1511
|
+
] }),
|
|
1512
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
|
|
1513
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `orion-builder-v2-sync is-${isDirty ? "dirty" : publicationState}`, children: [
|
|
1514
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: statusLabel }),
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: liveStatusLabel }),
|
|
1516
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: statusMeta })
|
|
1517
|
+
] }),
|
|
1518
|
+
["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1519
|
+
"button",
|
|
1520
|
+
{
|
|
1521
|
+
"aria-pressed": selectedDevice === device,
|
|
1522
|
+
className: "orion-builder-v2-tool",
|
|
1523
|
+
onClick: () => setDevice(device),
|
|
1524
|
+
type: "button",
|
|
1525
|
+
children: device
|
|
1526
|
+
},
|
|
1527
|
+
device
|
|
1528
|
+
)),
|
|
1529
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1530
|
+
"button",
|
|
1531
|
+
{
|
|
1532
|
+
className: "orion-builder-v2-tool",
|
|
1533
|
+
disabled: !historyState.canUndo,
|
|
1534
|
+
onClick: () => {
|
|
1535
|
+
editorRef.current?.UndoManager.undo();
|
|
1536
|
+
editorRef.current && updateHistoryState(editorRef.current);
|
|
1537
|
+
},
|
|
1538
|
+
type: "button",
|
|
1539
|
+
children: "Undo"
|
|
1540
|
+
}
|
|
1541
|
+
),
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1543
|
+
"button",
|
|
1544
|
+
{
|
|
1545
|
+
className: "orion-builder-v2-tool",
|
|
1546
|
+
disabled: !historyState.canRedo,
|
|
1547
|
+
onClick: () => {
|
|
1548
|
+
editorRef.current?.UndoManager.redo();
|
|
1549
|
+
editorRef.current && updateHistoryState(editorRef.current);
|
|
1550
|
+
},
|
|
1551
|
+
type: "button",
|
|
1552
|
+
children: "Redo"
|
|
1553
|
+
}
|
|
1554
|
+
),
|
|
1555
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "orion-builder-v2-tool is-primary", disabled: Boolean(saving), onClick: () => void save("draft"), type: "button", children: saving === "draft" ? "Saving..." : "Save draft" }),
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "orion-builder-v2-tool is-publish", disabled: Boolean(saving), onClick: () => void save("published"), type: "button", children: saving === "published" ? "Publishing..." : "Publish" })
|
|
1557
|
+
] })
|
|
1558
|
+
] }),
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "orion-builder-v2-sidebar", children: [
|
|
1560
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1561
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Pages" }),
|
|
1562
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-page-tree", children: pageTree.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No pages loaded." }) : pageTree.map((page) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1563
|
+
"a",
|
|
1564
|
+
{
|
|
1565
|
+
className: "orion-builder-v2-page-link",
|
|
1566
|
+
href: `${editorPageBasePath}/${page.id}`,
|
|
1567
|
+
onClick: (event) => {
|
|
1568
|
+
const target = `${editorPageBasePath}/${page.id}`;
|
|
1569
|
+
if (window.parent && window.parent !== window) {
|
|
1570
|
+
event.preventDefault();
|
|
1571
|
+
window.parent.location.href = target;
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1574
|
+
children: [
|
|
1575
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: page.title }),
|
|
1576
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: page.path })
|
|
1577
|
+
]
|
|
1578
|
+
},
|
|
1579
|
+
page.id
|
|
1580
|
+
)) })
|
|
1581
|
+
] }),
|
|
1582
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1583
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Blocks" }),
|
|
1584
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
|
|
1585
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-blocks" })
|
|
1586
|
+
] }),
|
|
1587
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Validation" }),
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-validation-list", children: validationIssues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No issues found." }) : validationIssues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `orion-builder-v2-validation is-${issue.severity}`, children: [
|
|
1590
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: issue.message }),
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: issue.severity })
|
|
1592
|
+
] }, `${issue.code}-${issue.path}`)) })
|
|
1593
|
+
] })
|
|
1594
|
+
] }),
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "orion-builder-v2-main", children: [
|
|
1596
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
|
|
1597
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-error", children: error }) : null,
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-canvas", ref: containerRef })
|
|
1599
|
+
] }),
|
|
1600
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "orion-builder-v2-inspector", "aria-label": "Selected section settings", children: [
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1602
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: selectionSummary ? selectionSummary.label : "No section selected" }),
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
|
|
1604
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
|
|
1605
|
+
] }),
|
|
1606
|
+
selectionSummary?.items ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Items" }),
|
|
1608
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-item-row", children: [
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.title }),
|
|
1611
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1612
|
+
"button",
|
|
1613
|
+
{
|
|
1614
|
+
onClick: () => {
|
|
1615
|
+
updateSelectedItems((items) => {
|
|
1616
|
+
const source = items[item.index];
|
|
1617
|
+
if (!source) {
|
|
1618
|
+
return items;
|
|
1619
|
+
}
|
|
1620
|
+
const copy = [...items];
|
|
1621
|
+
copy.splice(item.index + 1, 0, {
|
|
1622
|
+
...source,
|
|
1623
|
+
title: typeof source.title === "string" ? `${source.title} copy` : source.title
|
|
1624
|
+
});
|
|
1625
|
+
return copy;
|
|
1626
|
+
});
|
|
1627
|
+
},
|
|
1628
|
+
type: "button",
|
|
1629
|
+
children: "Duplicate"
|
|
1630
|
+
}
|
|
1631
|
+
),
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1633
|
+
"button",
|
|
1634
|
+
{
|
|
1635
|
+
onClick: () => {
|
|
1636
|
+
updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
|
|
1637
|
+
},
|
|
1638
|
+
type: "button",
|
|
1639
|
+
children: "Remove"
|
|
1640
|
+
}
|
|
1641
|
+
)
|
|
1642
|
+
] }, `${item.index}-${item.title}`)) }),
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1644
|
+
"button",
|
|
1645
|
+
{
|
|
1646
|
+
className: "orion-builder-v2-wide-action",
|
|
1647
|
+
onClick: () => {
|
|
1648
|
+
updateSelectedItems((items) => [
|
|
1649
|
+
...items,
|
|
1650
|
+
{
|
|
1651
|
+
description: "Describe this feature.",
|
|
1652
|
+
imageURL: "",
|
|
1653
|
+
title: "New feature"
|
|
1654
|
+
}
|
|
1655
|
+
]);
|
|
1656
|
+
},
|
|
1657
|
+
type: "button",
|
|
1658
|
+
children: "Add item"
|
|
1659
|
+
}
|
|
1660
|
+
)
|
|
1661
|
+
] }) : null,
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1663
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Design" }),
|
|
1664
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-styles" })
|
|
1666
|
+
] })
|
|
1667
|
+
] })
|
|
1668
|
+
] });
|
|
1669
|
+
}
|
|
1670
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1671
|
+
0 && (module.exports = {
|
|
1672
|
+
GrapesPageEditor
|
|
1673
|
+
});
|