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