@orion-studios/payload-studio 0.6.0-beta.45 → 0.6.0-beta.47
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/builder-v2/client.d.mts +5 -0
- package/dist/builder-v2/client.d.ts +5 -0
- package/dist/builder-v2/client.js +382 -24
- package/dist/builder-v2/client.mjs +382 -24
- package/dist/builder-v2/index.d.mts +81 -1
- package/dist/builder-v2/index.d.ts +81 -1
- package/dist/builder-v2/index.js +281 -7
- package/dist/builder-v2/index.mjs +276 -6
- package/dist/builder-v2/styles.css +625 -10
- package/package.json +1 -1
|
@@ -3,11 +3,16 @@ import type { FC } from 'react'
|
|
|
3
3
|
export type {
|
|
4
4
|
BuilderV2EditorInitialData,
|
|
5
5
|
BuilderV2EditorProps,
|
|
6
|
+
BuilderV2PageTreeNode,
|
|
7
|
+
BuilderV2PermissionSet,
|
|
6
8
|
BuilderV2ProjectAdapter,
|
|
7
9
|
BuilderV2ProjectComponentDefinition,
|
|
8
10
|
BuilderV2RuntimeComponent,
|
|
9
11
|
BuilderV2RuntimeComponentProps,
|
|
10
12
|
BuilderV2TraitDefinition,
|
|
13
|
+
BuilderV2ThemeTokens,
|
|
14
|
+
BuilderV2ValidationIssue,
|
|
15
|
+
BuilderV2VersionSnapshot,
|
|
11
16
|
} from './types'
|
|
12
17
|
|
|
13
18
|
export declare const GrapesPageEditor: FC<import('./types').BuilderV2EditorProps>
|
|
@@ -3,11 +3,16 @@ import type { FC } from 'react'
|
|
|
3
3
|
export type {
|
|
4
4
|
BuilderV2EditorInitialData,
|
|
5
5
|
BuilderV2EditorProps,
|
|
6
|
+
BuilderV2PageTreeNode,
|
|
7
|
+
BuilderV2PermissionSet,
|
|
6
8
|
BuilderV2ProjectAdapter,
|
|
7
9
|
BuilderV2ProjectComponentDefinition,
|
|
8
10
|
BuilderV2RuntimeComponent,
|
|
9
11
|
BuilderV2RuntimeComponentProps,
|
|
10
12
|
BuilderV2TraitDefinition,
|
|
13
|
+
BuilderV2ThemeTokens,
|
|
14
|
+
BuilderV2ValidationIssue,
|
|
15
|
+
BuilderV2VersionSnapshot,
|
|
11
16
|
} from './types'
|
|
12
17
|
|
|
13
18
|
export declare const GrapesPageEditor: FC<import('./types').BuilderV2EditorProps>
|
|
@@ -256,6 +256,7 @@ var allowedIframeHosts = [
|
|
|
256
256
|
"calendar.google.com",
|
|
257
257
|
"calendly.com",
|
|
258
258
|
"player.vimeo.com",
|
|
259
|
+
"www.google.com",
|
|
259
260
|
"www.youtube.com",
|
|
260
261
|
"youtube.com"
|
|
261
262
|
];
|
|
@@ -281,10 +282,127 @@ var sanitizeBuilderCss = (value) => {
|
|
|
281
282
|
}
|
|
282
283
|
return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
|
|
283
284
|
};
|
|
285
|
+
var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
|
|
286
|
+
const trimmed = part.trim();
|
|
287
|
+
if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
|
|
288
|
+
return trimmed;
|
|
289
|
+
}
|
|
290
|
+
if (/^(html|body|:root)\b/i.test(trimmed)) {
|
|
291
|
+
return trimmed.replace(/^(html|body|:root)\b/i, scope);
|
|
292
|
+
}
|
|
293
|
+
return `${scope} ${trimmed}`;
|
|
294
|
+
}).filter(Boolean).join(", ");
|
|
295
|
+
var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
|
|
296
|
+
const css = sanitizeBuilderCss(value);
|
|
297
|
+
if (!css) {
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
let output = "";
|
|
301
|
+
let cursor = 0;
|
|
302
|
+
const rulePattern = /([^{}]+)\{/g;
|
|
303
|
+
let match;
|
|
304
|
+
while ((match = rulePattern.exec(css)) !== null) {
|
|
305
|
+
const selectorStart = match.index;
|
|
306
|
+
const selector = match[1];
|
|
307
|
+
output += css.slice(cursor, selectorStart);
|
|
308
|
+
const trimmedSelector = selector.trim();
|
|
309
|
+
if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
|
|
310
|
+
output += `${selector}{`;
|
|
311
|
+
} else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
|
|
312
|
+
output += `${selector}{`;
|
|
313
|
+
} else {
|
|
314
|
+
output += `${scopeSelector(selector, scope)} {`;
|
|
315
|
+
}
|
|
316
|
+
cursor = rulePattern.lastIndex;
|
|
317
|
+
}
|
|
318
|
+
output += css.slice(cursor);
|
|
319
|
+
return output;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/builder-v2/validation.ts
|
|
323
|
+
var hasMatch = (value, pattern) => pattern.test(value);
|
|
324
|
+
var validateBuilderV2Output = (input) => {
|
|
325
|
+
const html = typeof input.html === "string" ? input.html : "";
|
|
326
|
+
const css = typeof input.css === "string" ? input.css : "";
|
|
327
|
+
const issues = [];
|
|
328
|
+
if (!html.trim()) {
|
|
329
|
+
issues.push({
|
|
330
|
+
code: "empty-page",
|
|
331
|
+
message: "This page has no rendered content.",
|
|
332
|
+
path: "compiledHtml",
|
|
333
|
+
severity: "error"
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (!hasMatch(html, /<h1\b/i)) {
|
|
337
|
+
issues.push({
|
|
338
|
+
code: "missing-h1",
|
|
339
|
+
message: "Add one H1 so the published page has a clear primary heading.",
|
|
340
|
+
path: "compiledHtml",
|
|
341
|
+
severity: "warning"
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (hasMatch(html, /\son[a-z]+\s*=/i)) {
|
|
345
|
+
issues.push({
|
|
346
|
+
code: "inline-event-handler",
|
|
347
|
+
message: "Inline event handlers are not allowed in published builder HTML.",
|
|
348
|
+
path: "compiledHtml",
|
|
349
|
+
severity: "error"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (hasMatch(html, /<(script|object|embed)\b/i)) {
|
|
353
|
+
issues.push({
|
|
354
|
+
code: "unsafe-element",
|
|
355
|
+
message: "Script, object, and embed tags are not allowed in builder content.",
|
|
356
|
+
path: "compiledHtml",
|
|
357
|
+
severity: "error"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
|
|
361
|
+
issues.push({
|
|
362
|
+
code: "unsafe-url",
|
|
363
|
+
message: "Links and media cannot use javascript URLs.",
|
|
364
|
+
path: "compiledHtml",
|
|
365
|
+
severity: "error"
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (hasMatch(css, /@import\s/i)) {
|
|
369
|
+
issues.push({
|
|
370
|
+
code: "css-import",
|
|
371
|
+
message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
|
|
372
|
+
path: "compiledCss",
|
|
373
|
+
severity: "warning"
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
if (hasMatch(css, /position\s*:\s*fixed/i)) {
|
|
377
|
+
issues.push({
|
|
378
|
+
code: "fixed-position",
|
|
379
|
+
message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
|
|
380
|
+
path: "compiledCss",
|
|
381
|
+
severity: "warning"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return issues;
|
|
385
|
+
};
|
|
386
|
+
var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
|
|
284
387
|
|
|
285
388
|
// src/builder-v2/editor/defaultBlocks.ts
|
|
286
389
|
var registerOrionBuilderV2Blocks = (editor) => {
|
|
287
390
|
const blocks = editor.Blocks;
|
|
391
|
+
blocks.add("orion-nav", {
|
|
392
|
+
category: "Chrome",
|
|
393
|
+
content: `
|
|
394
|
+
<header class="orion-builder-v2-nav">
|
|
395
|
+
<a class="orion-builder-v2-logo" href="/">Brand</a>
|
|
396
|
+
<nav aria-label="Primary">
|
|
397
|
+
<a href="/">Home</a>
|
|
398
|
+
<a href="/about">About</a>
|
|
399
|
+
<a href="/contact">Contact</a>
|
|
400
|
+
</nav>
|
|
401
|
+
<a class="orion-builder-v2-button" href="/contact">Start</a>
|
|
402
|
+
</header>
|
|
403
|
+
`,
|
|
404
|
+
label: "Nav"
|
|
405
|
+
});
|
|
288
406
|
blocks.add("orion-section", {
|
|
289
407
|
category: "Layout",
|
|
290
408
|
content: `
|
|
@@ -324,6 +442,89 @@ var registerOrionBuilderV2Blocks = (editor) => {
|
|
|
324
442
|
`,
|
|
325
443
|
label: "Columns"
|
|
326
444
|
});
|
|
445
|
+
blocks.add("orion-card-grid", {
|
|
446
|
+
category: "Sections",
|
|
447
|
+
content: `
|
|
448
|
+
<section class="orion-builder-v2-section">
|
|
449
|
+
<div class="orion-builder-v2-container">
|
|
450
|
+
<p class="orion-builder-v2-kicker">Featured</p>
|
|
451
|
+
<h2>Cards that explain the offer</h2>
|
|
452
|
+
<div class="orion-builder-v2-grid is-3">
|
|
453
|
+
<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>
|
|
454
|
+
<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>
|
|
455
|
+
<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>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</section>
|
|
459
|
+
`,
|
|
460
|
+
label: "Card grid"
|
|
461
|
+
});
|
|
462
|
+
blocks.add("orion-gallery", {
|
|
463
|
+
category: "Media",
|
|
464
|
+
content: `
|
|
465
|
+
<section class="orion-builder-v2-section">
|
|
466
|
+
<div class="orion-builder-v2-container">
|
|
467
|
+
<h2>Gallery</h2>
|
|
468
|
+
<div class="orion-builder-v2-gallery">
|
|
469
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
470
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
471
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
472
|
+
<img alt="Gallery item" src="https://placehold.co/900x700" />
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
</section>
|
|
476
|
+
`,
|
|
477
|
+
label: "Gallery"
|
|
478
|
+
});
|
|
479
|
+
blocks.add("orion-testimonials", {
|
|
480
|
+
category: "Sections",
|
|
481
|
+
content: `
|
|
482
|
+
<section class="orion-builder-v2-section is-muted">
|
|
483
|
+
<div class="orion-builder-v2-container">
|
|
484
|
+
<p class="orion-builder-v2-kicker">Testimonials</p>
|
|
485
|
+
<h2>What clients say</h2>
|
|
486
|
+
<div class="orion-builder-v2-grid is-3">
|
|
487
|
+
<blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
|
|
488
|
+
<blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
|
|
489
|
+
<blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</section>
|
|
493
|
+
`,
|
|
494
|
+
label: "Testimonials"
|
|
495
|
+
});
|
|
496
|
+
blocks.add("orion-faq", {
|
|
497
|
+
category: "Sections",
|
|
498
|
+
content: `
|
|
499
|
+
<section class="orion-builder-v2-section">
|
|
500
|
+
<div class="orion-builder-v2-container is-narrow">
|
|
501
|
+
<p class="orion-builder-v2-kicker">FAQ</p>
|
|
502
|
+
<h2>Questions answered</h2>
|
|
503
|
+
<details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
|
|
504
|
+
<details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
|
|
505
|
+
<details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
|
|
506
|
+
</div>
|
|
507
|
+
</section>
|
|
508
|
+
`,
|
|
509
|
+
label: "FAQ"
|
|
510
|
+
});
|
|
511
|
+
blocks.add("orion-pricing", {
|
|
512
|
+
category: "Commerce",
|
|
513
|
+
content: `
|
|
514
|
+
<section class="orion-builder-v2-section">
|
|
515
|
+
<div class="orion-builder-v2-container">
|
|
516
|
+
<p class="orion-builder-v2-kicker">Pricing</p>
|
|
517
|
+
<h2>Simple package options</h2>
|
|
518
|
+
<div class="orion-builder-v2-grid is-3">
|
|
519
|
+
<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>
|
|
520
|
+
<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>
|
|
521
|
+
<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>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
</section>
|
|
525
|
+
`,
|
|
526
|
+
label: "Pricing"
|
|
527
|
+
});
|
|
327
528
|
blocks.add("orion-button", {
|
|
328
529
|
category: "Basic",
|
|
329
530
|
content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
|
|
@@ -348,6 +549,22 @@ var registerOrionBuilderV2Blocks = (editor) => {
|
|
|
348
549
|
`,
|
|
349
550
|
label: "Form"
|
|
350
551
|
});
|
|
552
|
+
blocks.add("orion-footer", {
|
|
553
|
+
category: "Chrome",
|
|
554
|
+
content: `
|
|
555
|
+
<footer class="orion-builder-v2-footer">
|
|
556
|
+
<div>
|
|
557
|
+
<strong>Brand</strong>
|
|
558
|
+
<p>Short positioning line for the site footer.</p>
|
|
559
|
+
</div>
|
|
560
|
+
<nav aria-label="Footer">
|
|
561
|
+
<a href="/privacy">Privacy</a>
|
|
562
|
+
<a href="/contact">Contact</a>
|
|
563
|
+
</nav>
|
|
564
|
+
</footer>
|
|
565
|
+
`,
|
|
566
|
+
label: "Footer"
|
|
567
|
+
});
|
|
351
568
|
};
|
|
352
569
|
|
|
353
570
|
// src/builder-v2/editor/projectComponents.ts
|
|
@@ -417,7 +634,7 @@ var postToParent = (payload) => {
|
|
|
417
634
|
};
|
|
418
635
|
var buildSavePayload = (editor, status, projectData) => ({
|
|
419
636
|
builderMode: "grapes-v2",
|
|
420
|
-
compiledCss:
|
|
637
|
+
compiledCss: scopeBuilderCss(editor.getCss()),
|
|
421
638
|
compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
|
|
422
639
|
projectData,
|
|
423
640
|
status
|
|
@@ -516,12 +733,53 @@ var uploadPayloadMediaAssets = async (editor, files) => {
|
|
|
516
733
|
editor.AssetManager.add(uploadedAssets);
|
|
517
734
|
}
|
|
518
735
|
};
|
|
519
|
-
function GrapesPageEditor({
|
|
736
|
+
function GrapesPageEditor({
|
|
737
|
+
adapter,
|
|
738
|
+
autosaveIntervalMs = 3e4,
|
|
739
|
+
initialData,
|
|
740
|
+
pageID
|
|
741
|
+
}) {
|
|
520
742
|
const containerRef = (0, import_react.useRef)(null);
|
|
521
743
|
const editorRef = (0, import_react.useRef)(null);
|
|
744
|
+
const autosaveTimerRef = (0, import_react.useRef)(null);
|
|
745
|
+
const saveRef = (0, import_react.useRef)(async () => void 0);
|
|
522
746
|
const [error, setError] = (0, import_react.useState)("");
|
|
747
|
+
const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
|
|
748
|
+
const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
|
|
523
749
|
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
750
|
+
const [selectedDevice, setSelectedDevice] = (0, import_react.useState)("desktop");
|
|
524
751
|
const [saving, setSaving] = (0, import_react.useState)(null);
|
|
752
|
+
const [saveMessage, setSaveMessage] = (0, import_react.useState)("");
|
|
753
|
+
const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
|
|
754
|
+
const pageTree = initialData?.meta?.pageTree || [];
|
|
755
|
+
const updateHistoryState = (editor) => {
|
|
756
|
+
const next = {
|
|
757
|
+
canRedo: editor.UndoManager.hasRedo(),
|
|
758
|
+
canUndo: editor.UndoManager.hasUndo()
|
|
759
|
+
};
|
|
760
|
+
setHistoryState(next);
|
|
761
|
+
postToParent({
|
|
762
|
+
...next,
|
|
763
|
+
type: "history-state"
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
const runValidation = (editor) => {
|
|
767
|
+
const issues = validateBuilderV2Output({
|
|
768
|
+
css: editor.getCss(),
|
|
769
|
+
html: editor.getHtml()
|
|
770
|
+
});
|
|
771
|
+
setValidationIssues(issues);
|
|
772
|
+
postToParent({ issues, type: "validation-state" });
|
|
773
|
+
return issues;
|
|
774
|
+
};
|
|
775
|
+
const setDevice = (device) => {
|
|
776
|
+
const editor = editorRef.current;
|
|
777
|
+
if (!editor) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
editor.setDevice(device);
|
|
781
|
+
setSelectedDevice(device);
|
|
782
|
+
};
|
|
525
783
|
(0, import_react.useEffect)(() => {
|
|
526
784
|
let active = true;
|
|
527
785
|
const init = async () => {
|
|
@@ -605,12 +863,24 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
605
863
|
void loadPayloadMediaAssets(editor);
|
|
606
864
|
editor.on("update", () => {
|
|
607
865
|
postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
866
|
+
updateHistoryState(editor);
|
|
867
|
+
runValidation(editor);
|
|
868
|
+
setSaveMessage("Unsaved changes");
|
|
869
|
+
if (autosaveTimerRef.current) {
|
|
870
|
+
window.clearTimeout(autosaveTimerRef.current);
|
|
871
|
+
}
|
|
872
|
+
autosaveTimerRef.current = window.setTimeout(() => {
|
|
873
|
+
if (editor.getDirtyCount() > 0) {
|
|
874
|
+
void saveRef.current("draft", { autosave: true });
|
|
875
|
+
}
|
|
876
|
+
}, autosaveIntervalMs);
|
|
613
877
|
});
|
|
878
|
+
editor.on("component:selected", () => {
|
|
879
|
+
setSaveMessage("Selection ready");
|
|
880
|
+
});
|
|
881
|
+
setSelectedDevice(editor.getDevice() || "desktop");
|
|
882
|
+
runValidation(editor);
|
|
883
|
+
updateHistoryState(editor);
|
|
614
884
|
setLoading(false);
|
|
615
885
|
} catch (initError) {
|
|
616
886
|
setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
|
|
@@ -620,15 +890,30 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
620
890
|
void init();
|
|
621
891
|
return () => {
|
|
622
892
|
active = false;
|
|
893
|
+
if (autosaveTimerRef.current) {
|
|
894
|
+
window.clearTimeout(autosaveTimerRef.current);
|
|
895
|
+
}
|
|
623
896
|
editorRef.current?.destroy();
|
|
624
897
|
editorRef.current = null;
|
|
625
898
|
};
|
|
626
|
-
}, [adapter, initialData?.projectData, initialData?.title]);
|
|
627
|
-
const save = async (status) => {
|
|
899
|
+
}, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
|
|
900
|
+
const save = async (status, options = {}) => {
|
|
628
901
|
const editor = editorRef.current;
|
|
629
902
|
if (!editor || saving) {
|
|
630
903
|
return;
|
|
631
904
|
}
|
|
905
|
+
const issues = runValidation(editor);
|
|
906
|
+
if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
|
|
907
|
+
const message = "Resolve blocking validation errors before publishing.";
|
|
908
|
+
setSaveMessage(message);
|
|
909
|
+
postToParent({
|
|
910
|
+
message,
|
|
911
|
+
ok: false,
|
|
912
|
+
status,
|
|
913
|
+
type: "save-result"
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
632
917
|
setSaving(status);
|
|
633
918
|
try {
|
|
634
919
|
const projectData = editor.getProjectData();
|
|
@@ -643,15 +928,19 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
643
928
|
builderMode: "grapes-v2",
|
|
644
929
|
builderProjectData: projectData,
|
|
645
930
|
builderPublishedProjectData: projectData,
|
|
646
|
-
|
|
931
|
+
builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
932
|
+
builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
933
|
+
builderValidationIssues: issues,
|
|
647
934
|
compiledCss: payload.compiledCss,
|
|
648
935
|
compiledHtml: payload.compiledHtml
|
|
649
936
|
} : {
|
|
650
937
|
_status: "draft",
|
|
938
|
+
builderAutosaveProjectData: options.autosave ? projectData : null,
|
|
651
939
|
builderDynamicComponents: dynamicComponents,
|
|
652
940
|
builderMode: "grapes-v2",
|
|
653
941
|
builderProjectData: projectData,
|
|
654
|
-
|
|
942
|
+
builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
943
|
+
builderValidationIssues: issues,
|
|
655
944
|
compiledCss: payload.compiledCss,
|
|
656
945
|
compiledHtml: payload.compiledHtml
|
|
657
946
|
}
|
|
@@ -674,14 +963,18 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
674
963
|
type: "dirty-state"
|
|
675
964
|
});
|
|
676
965
|
postToParent({
|
|
677
|
-
message: status === "published" ? "Published." : "Draft saved.",
|
|
966
|
+
message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
|
|
678
967
|
ok: true,
|
|
679
968
|
status,
|
|
680
969
|
type: "save-result"
|
|
681
970
|
});
|
|
971
|
+
setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
|
|
972
|
+
setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
|
|
682
973
|
} catch (saveError) {
|
|
974
|
+
const message = saveError instanceof Error ? saveError.message : "Could not save.";
|
|
975
|
+
setSaveMessage(message);
|
|
683
976
|
postToParent({
|
|
684
|
-
message
|
|
977
|
+
message,
|
|
685
978
|
ok: false,
|
|
686
979
|
status,
|
|
687
980
|
type: "save-result"
|
|
@@ -690,6 +983,9 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
690
983
|
setSaving(null);
|
|
691
984
|
}
|
|
692
985
|
};
|
|
986
|
+
(0, import_react.useEffect)(() => {
|
|
987
|
+
saveRef.current = save;
|
|
988
|
+
}, [saving]);
|
|
693
989
|
(0, import_react.useEffect)(() => {
|
|
694
990
|
const onMessage = (event) => {
|
|
695
991
|
const data = event.data;
|
|
@@ -699,19 +995,11 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
699
995
|
}
|
|
700
996
|
if (data.type === "dirty-check-request") {
|
|
701
997
|
postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
|
|
702
|
-
|
|
703
|
-
canRedo: editor.UndoManager.hasRedo(),
|
|
704
|
-
canUndo: editor.UndoManager.hasUndo(),
|
|
705
|
-
type: "history-state"
|
|
706
|
-
});
|
|
998
|
+
updateHistoryState(editor);
|
|
707
999
|
return;
|
|
708
1000
|
}
|
|
709
1001
|
if (data.type === "history-check-request") {
|
|
710
|
-
|
|
711
|
-
canRedo: editor.UndoManager.hasRedo(),
|
|
712
|
-
canUndo: editor.UndoManager.hasUndo(),
|
|
713
|
-
type: "history-state"
|
|
714
|
-
});
|
|
1002
|
+
updateHistoryState(editor);
|
|
715
1003
|
return;
|
|
716
1004
|
}
|
|
717
1005
|
if (data.type === "undo") {
|
|
@@ -730,19 +1018,89 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
|
|
|
730
1018
|
return () => window.removeEventListener("message", onMessage);
|
|
731
1019
|
}, [saving]);
|
|
732
1020
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-editor", children: [
|
|
1021
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "orion-builder-v2-topbar", children: [
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
|
|
1024
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: initialData?.title || "Untitled page" })
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
|
|
1027
|
+
["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1028
|
+
"button",
|
|
1029
|
+
{
|
|
1030
|
+
"aria-pressed": selectedDevice === device,
|
|
1031
|
+
className: "orion-builder-v2-tool",
|
|
1032
|
+
onClick: () => setDevice(device),
|
|
1033
|
+
type: "button",
|
|
1034
|
+
children: device
|
|
1035
|
+
},
|
|
1036
|
+
device
|
|
1037
|
+
)),
|
|
1038
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1039
|
+
"button",
|
|
1040
|
+
{
|
|
1041
|
+
className: "orion-builder-v2-tool",
|
|
1042
|
+
disabled: !historyState.canUndo,
|
|
1043
|
+
onClick: () => {
|
|
1044
|
+
editorRef.current?.UndoManager.undo();
|
|
1045
|
+
editorRef.current && updateHistoryState(editorRef.current);
|
|
1046
|
+
},
|
|
1047
|
+
type: "button",
|
|
1048
|
+
children: "Undo"
|
|
1049
|
+
}
|
|
1050
|
+
),
|
|
1051
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1052
|
+
"button",
|
|
1053
|
+
{
|
|
1054
|
+
className: "orion-builder-v2-tool",
|
|
1055
|
+
disabled: !historyState.canRedo,
|
|
1056
|
+
onClick: () => {
|
|
1057
|
+
editorRef.current?.UndoManager.redo();
|
|
1058
|
+
editorRef.current && updateHistoryState(editorRef.current);
|
|
1059
|
+
},
|
|
1060
|
+
type: "button",
|
|
1061
|
+
children: "Redo"
|
|
1062
|
+
}
|
|
1063
|
+
),
|
|
1064
|
+
/* @__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" }),
|
|
1065
|
+
/* @__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" })
|
|
1066
|
+
] })
|
|
1067
|
+
] }),
|
|
733
1068
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "orion-builder-v2-sidebar", children: [
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Pages" }),
|
|
1071
|
+
/* @__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)("a", { className: "orion-builder-v2-page-link", href: `#page-${page.id}`, children: [
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: page.title }),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: page.path })
|
|
1074
|
+
] }, page.id)) })
|
|
1075
|
+
] }),
|
|
734
1076
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
735
1077
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Blocks" }),
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
|
|
736
1079
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-blocks" })
|
|
737
1080
|
] }),
|
|
738
1081
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
739
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Inspector" }),
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
|
|
740
1084
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Validation" }),
|
|
1088
|
+
/* @__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: [
|
|
1089
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: issue.message }),
|
|
1090
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: issue.severity })
|
|
1091
|
+
] }, `${issue.code}-${issue.path}`)) })
|
|
741
1092
|
] })
|
|
742
1093
|
] }),
|
|
743
1094
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "orion-builder-v2-main", children: [
|
|
744
1095
|
loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
|
|
745
1096
|
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-error", children: error }) : null,
|
|
1097
|
+
saveMessage || lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-save-status", children: [
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: saveMessage || "Ready" }),
|
|
1099
|
+
lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1100
|
+
"Last saved ",
|
|
1101
|
+
lastSavedAt
|
|
1102
|
+
] }) : null
|
|
1103
|
+
] }) : null,
|
|
746
1104
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-canvas", ref: containerRef })
|
|
747
1105
|
] })
|
|
748
1106
|
] });
|