@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.
@@ -132,6 +132,7 @@ var allowedIframeHosts = [
132
132
  "calendar.google.com",
133
133
  "calendly.com",
134
134
  "player.vimeo.com",
135
+ "www.google.com",
135
136
  "www.youtube.com",
136
137
  "youtube.com"
137
138
  ];
@@ -157,10 +158,127 @@ var sanitizeBuilderCss = (value) => {
157
158
  }
158
159
  return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
159
160
  };
161
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
162
+ const trimmed = part.trim();
163
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
164
+ return trimmed;
165
+ }
166
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
167
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
168
+ }
169
+ return `${scope} ${trimmed}`;
170
+ }).filter(Boolean).join(", ");
171
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
172
+ const css = sanitizeBuilderCss(value);
173
+ if (!css) {
174
+ return "";
175
+ }
176
+ let output = "";
177
+ let cursor = 0;
178
+ const rulePattern = /([^{}]+)\{/g;
179
+ let match;
180
+ while ((match = rulePattern.exec(css)) !== null) {
181
+ const selectorStart = match.index;
182
+ const selector = match[1];
183
+ output += css.slice(cursor, selectorStart);
184
+ const trimmedSelector = selector.trim();
185
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
186
+ output += `${selector}{`;
187
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
188
+ output += `${selector}{`;
189
+ } else {
190
+ output += `${scopeSelector(selector, scope)} {`;
191
+ }
192
+ cursor = rulePattern.lastIndex;
193
+ }
194
+ output += css.slice(cursor);
195
+ return output;
196
+ };
197
+
198
+ // src/builder-v2/validation.ts
199
+ var hasMatch = (value, pattern) => pattern.test(value);
200
+ var validateBuilderV2Output = (input) => {
201
+ const html = typeof input.html === "string" ? input.html : "";
202
+ const css = typeof input.css === "string" ? input.css : "";
203
+ const issues = [];
204
+ if (!html.trim()) {
205
+ issues.push({
206
+ code: "empty-page",
207
+ message: "This page has no rendered content.",
208
+ path: "compiledHtml",
209
+ severity: "error"
210
+ });
211
+ }
212
+ if (!hasMatch(html, /<h1\b/i)) {
213
+ issues.push({
214
+ code: "missing-h1",
215
+ message: "Add one H1 so the published page has a clear primary heading.",
216
+ path: "compiledHtml",
217
+ severity: "warning"
218
+ });
219
+ }
220
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
221
+ issues.push({
222
+ code: "inline-event-handler",
223
+ message: "Inline event handlers are not allowed in published builder HTML.",
224
+ path: "compiledHtml",
225
+ severity: "error"
226
+ });
227
+ }
228
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
229
+ issues.push({
230
+ code: "unsafe-element",
231
+ message: "Script, object, and embed tags are not allowed in builder content.",
232
+ path: "compiledHtml",
233
+ severity: "error"
234
+ });
235
+ }
236
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
237
+ issues.push({
238
+ code: "unsafe-url",
239
+ message: "Links and media cannot use javascript URLs.",
240
+ path: "compiledHtml",
241
+ severity: "error"
242
+ });
243
+ }
244
+ if (hasMatch(css, /@import\s/i)) {
245
+ issues.push({
246
+ code: "css-import",
247
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
248
+ path: "compiledCss",
249
+ severity: "warning"
250
+ });
251
+ }
252
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
253
+ issues.push({
254
+ code: "fixed-position",
255
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
256
+ path: "compiledCss",
257
+ severity: "warning"
258
+ });
259
+ }
260
+ return issues;
261
+ };
262
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
160
263
 
161
264
  // src/builder-v2/editor/defaultBlocks.ts
162
265
  var registerOrionBuilderV2Blocks = (editor) => {
163
266
  const blocks = editor.Blocks;
267
+ blocks.add("orion-nav", {
268
+ category: "Chrome",
269
+ content: `
270
+ <header class="orion-builder-v2-nav">
271
+ <a class="orion-builder-v2-logo" href="/">Brand</a>
272
+ <nav aria-label="Primary">
273
+ <a href="/">Home</a>
274
+ <a href="/about">About</a>
275
+ <a href="/contact">Contact</a>
276
+ </nav>
277
+ <a class="orion-builder-v2-button" href="/contact">Start</a>
278
+ </header>
279
+ `,
280
+ label: "Nav"
281
+ });
164
282
  blocks.add("orion-section", {
165
283
  category: "Layout",
166
284
  content: `
@@ -200,6 +318,89 @@ var registerOrionBuilderV2Blocks = (editor) => {
200
318
  `,
201
319
  label: "Columns"
202
320
  });
321
+ blocks.add("orion-card-grid", {
322
+ category: "Sections",
323
+ content: `
324
+ <section class="orion-builder-v2-section">
325
+ <div class="orion-builder-v2-container">
326
+ <p class="orion-builder-v2-kicker">Featured</p>
327
+ <h2>Cards that explain the offer</h2>
328
+ <div class="orion-builder-v2-grid is-3">
329
+ <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>
330
+ <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>
331
+ <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>
332
+ </div>
333
+ </div>
334
+ </section>
335
+ `,
336
+ label: "Card grid"
337
+ });
338
+ blocks.add("orion-gallery", {
339
+ category: "Media",
340
+ content: `
341
+ <section class="orion-builder-v2-section">
342
+ <div class="orion-builder-v2-container">
343
+ <h2>Gallery</h2>
344
+ <div class="orion-builder-v2-gallery">
345
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
346
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
347
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
348
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
349
+ </div>
350
+ </div>
351
+ </section>
352
+ `,
353
+ label: "Gallery"
354
+ });
355
+ blocks.add("orion-testimonials", {
356
+ category: "Sections",
357
+ content: `
358
+ <section class="orion-builder-v2-section is-muted">
359
+ <div class="orion-builder-v2-container">
360
+ <p class="orion-builder-v2-kicker">Testimonials</p>
361
+ <h2>What clients say</h2>
362
+ <div class="orion-builder-v2-grid is-3">
363
+ <blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
364
+ <blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
365
+ <blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
366
+ </div>
367
+ </div>
368
+ </section>
369
+ `,
370
+ label: "Testimonials"
371
+ });
372
+ blocks.add("orion-faq", {
373
+ category: "Sections",
374
+ content: `
375
+ <section class="orion-builder-v2-section">
376
+ <div class="orion-builder-v2-container is-narrow">
377
+ <p class="orion-builder-v2-kicker">FAQ</p>
378
+ <h2>Questions answered</h2>
379
+ <details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
380
+ <details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
381
+ <details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
382
+ </div>
383
+ </section>
384
+ `,
385
+ label: "FAQ"
386
+ });
387
+ blocks.add("orion-pricing", {
388
+ category: "Commerce",
389
+ content: `
390
+ <section class="orion-builder-v2-section">
391
+ <div class="orion-builder-v2-container">
392
+ <p class="orion-builder-v2-kicker">Pricing</p>
393
+ <h2>Simple package options</h2>
394
+ <div class="orion-builder-v2-grid is-3">
395
+ <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>
396
+ <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>
397
+ <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>
398
+ </div>
399
+ </div>
400
+ </section>
401
+ `,
402
+ label: "Pricing"
403
+ });
203
404
  blocks.add("orion-button", {
204
405
  category: "Basic",
205
406
  content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
@@ -224,6 +425,22 @@ var registerOrionBuilderV2Blocks = (editor) => {
224
425
  `,
225
426
  label: "Form"
226
427
  });
428
+ blocks.add("orion-footer", {
429
+ category: "Chrome",
430
+ content: `
431
+ <footer class="orion-builder-v2-footer">
432
+ <div>
433
+ <strong>Brand</strong>
434
+ <p>Short positioning line for the site footer.</p>
435
+ </div>
436
+ <nav aria-label="Footer">
437
+ <a href="/privacy">Privacy</a>
438
+ <a href="/contact">Contact</a>
439
+ </nav>
440
+ </footer>
441
+ `,
442
+ label: "Footer"
443
+ });
227
444
  };
228
445
 
229
446
  // src/builder-v2/editor/projectComponents.ts
@@ -293,7 +510,7 @@ var postToParent = (payload) => {
293
510
  };
294
511
  var buildSavePayload = (editor, status, projectData) => ({
295
512
  builderMode: "grapes-v2",
296
- compiledCss: sanitizeBuilderCss(editor.getCss()),
513
+ compiledCss: scopeBuilderCss(editor.getCss()),
297
514
  compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
298
515
  projectData,
299
516
  status
@@ -392,12 +609,53 @@ var uploadPayloadMediaAssets = async (editor, files) => {
392
609
  editor.AssetManager.add(uploadedAssets);
393
610
  }
394
611
  };
395
- function GrapesPageEditor({ adapter, initialData, pageID }) {
612
+ function GrapesPageEditor({
613
+ adapter,
614
+ autosaveIntervalMs = 3e4,
615
+ initialData,
616
+ pageID
617
+ }) {
396
618
  const containerRef = useRef(null);
397
619
  const editorRef = useRef(null);
620
+ const autosaveTimerRef = useRef(null);
621
+ const saveRef = useRef(async () => void 0);
398
622
  const [error, setError] = useState("");
623
+ const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
624
+ const [lastSavedAt, setLastSavedAt] = useState("");
399
625
  const [loading, setLoading] = useState(true);
626
+ const [selectedDevice, setSelectedDevice] = useState("desktop");
400
627
  const [saving, setSaving] = useState(null);
628
+ const [saveMessage, setSaveMessage] = useState("");
629
+ const [validationIssues, setValidationIssues] = useState([]);
630
+ const pageTree = initialData?.meta?.pageTree || [];
631
+ const updateHistoryState = (editor) => {
632
+ const next = {
633
+ canRedo: editor.UndoManager.hasRedo(),
634
+ canUndo: editor.UndoManager.hasUndo()
635
+ };
636
+ setHistoryState(next);
637
+ postToParent({
638
+ ...next,
639
+ type: "history-state"
640
+ });
641
+ };
642
+ const runValidation = (editor) => {
643
+ const issues = validateBuilderV2Output({
644
+ css: editor.getCss(),
645
+ html: editor.getHtml()
646
+ });
647
+ setValidationIssues(issues);
648
+ postToParent({ issues, type: "validation-state" });
649
+ return issues;
650
+ };
651
+ const setDevice = (device) => {
652
+ const editor = editorRef.current;
653
+ if (!editor) {
654
+ return;
655
+ }
656
+ editor.setDevice(device);
657
+ setSelectedDevice(device);
658
+ };
401
659
  useEffect(() => {
402
660
  let active = true;
403
661
  const init = async () => {
@@ -481,12 +739,24 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
481
739
  void loadPayloadMediaAssets(editor);
482
740
  editor.on("update", () => {
483
741
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
484
- postToParent({
485
- canRedo: editor.UndoManager.hasRedo(),
486
- canUndo: editor.UndoManager.hasUndo(),
487
- type: "history-state"
488
- });
742
+ updateHistoryState(editor);
743
+ runValidation(editor);
744
+ setSaveMessage("Unsaved changes");
745
+ if (autosaveTimerRef.current) {
746
+ window.clearTimeout(autosaveTimerRef.current);
747
+ }
748
+ autosaveTimerRef.current = window.setTimeout(() => {
749
+ if (editor.getDirtyCount() > 0) {
750
+ void saveRef.current("draft", { autosave: true });
751
+ }
752
+ }, autosaveIntervalMs);
489
753
  });
754
+ editor.on("component:selected", () => {
755
+ setSaveMessage("Selection ready");
756
+ });
757
+ setSelectedDevice(editor.getDevice() || "desktop");
758
+ runValidation(editor);
759
+ updateHistoryState(editor);
490
760
  setLoading(false);
491
761
  } catch (initError) {
492
762
  setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
@@ -496,15 +766,30 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
496
766
  void init();
497
767
  return () => {
498
768
  active = false;
769
+ if (autosaveTimerRef.current) {
770
+ window.clearTimeout(autosaveTimerRef.current);
771
+ }
499
772
  editorRef.current?.destroy();
500
773
  editorRef.current = null;
501
774
  };
502
- }, [adapter, initialData?.projectData, initialData?.title]);
503
- const save = async (status) => {
775
+ }, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
776
+ const save = async (status, options = {}) => {
504
777
  const editor = editorRef.current;
505
778
  if (!editor || saving) {
506
779
  return;
507
780
  }
781
+ const issues = runValidation(editor);
782
+ if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
783
+ const message = "Resolve blocking validation errors before publishing.";
784
+ setSaveMessage(message);
785
+ postToParent({
786
+ message,
787
+ ok: false,
788
+ status,
789
+ type: "save-result"
790
+ });
791
+ return;
792
+ }
508
793
  setSaving(status);
509
794
  try {
510
795
  const projectData = editor.getProjectData();
@@ -519,15 +804,19 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
519
804
  builderMode: "grapes-v2",
520
805
  builderProjectData: projectData,
521
806
  builderPublishedProjectData: projectData,
522
- builderValidationIssues: [],
807
+ builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
808
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
809
+ builderValidationIssues: issues,
523
810
  compiledCss: payload.compiledCss,
524
811
  compiledHtml: payload.compiledHtml
525
812
  } : {
526
813
  _status: "draft",
814
+ builderAutosaveProjectData: options.autosave ? projectData : null,
527
815
  builderDynamicComponents: dynamicComponents,
528
816
  builderMode: "grapes-v2",
529
817
  builderProjectData: projectData,
530
- builderValidationIssues: [],
818
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
819
+ builderValidationIssues: issues,
531
820
  compiledCss: payload.compiledCss,
532
821
  compiledHtml: payload.compiledHtml
533
822
  }
@@ -550,14 +839,18 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
550
839
  type: "dirty-state"
551
840
  });
552
841
  postToParent({
553
- message: status === "published" ? "Published." : "Draft saved.",
842
+ message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
554
843
  ok: true,
555
844
  status,
556
845
  type: "save-result"
557
846
  });
847
+ setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
848
+ setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
558
849
  } catch (saveError) {
850
+ const message = saveError instanceof Error ? saveError.message : "Could not save.";
851
+ setSaveMessage(message);
559
852
  postToParent({
560
- message: saveError instanceof Error ? saveError.message : "Could not save.",
853
+ message,
561
854
  ok: false,
562
855
  status,
563
856
  type: "save-result"
@@ -566,6 +859,9 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
566
859
  setSaving(null);
567
860
  }
568
861
  };
862
+ useEffect(() => {
863
+ saveRef.current = save;
864
+ }, [saving]);
569
865
  useEffect(() => {
570
866
  const onMessage = (event) => {
571
867
  const data = event.data;
@@ -575,19 +871,11 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
575
871
  }
576
872
  if (data.type === "dirty-check-request") {
577
873
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
578
- postToParent({
579
- canRedo: editor.UndoManager.hasRedo(),
580
- canUndo: editor.UndoManager.hasUndo(),
581
- type: "history-state"
582
- });
874
+ updateHistoryState(editor);
583
875
  return;
584
876
  }
585
877
  if (data.type === "history-check-request") {
586
- postToParent({
587
- canRedo: editor.UndoManager.hasRedo(),
588
- canUndo: editor.UndoManager.hasUndo(),
589
- type: "history-state"
590
- });
878
+ updateHistoryState(editor);
591
879
  return;
592
880
  }
593
881
  if (data.type === "undo") {
@@ -606,19 +894,89 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
606
894
  return () => window.removeEventListener("message", onMessage);
607
895
  }, [saving]);
608
896
  return /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-editor", children: [
897
+ /* @__PURE__ */ jsxs("header", { className: "orion-builder-v2-topbar", children: [
898
+ /* @__PURE__ */ jsxs("div", { children: [
899
+ /* @__PURE__ */ jsx("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
900
+ /* @__PURE__ */ jsx("h1", { children: initialData?.title || "Untitled page" })
901
+ ] }),
902
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
903
+ ["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ jsx(
904
+ "button",
905
+ {
906
+ "aria-pressed": selectedDevice === device,
907
+ className: "orion-builder-v2-tool",
908
+ onClick: () => setDevice(device),
909
+ type: "button",
910
+ children: device
911
+ },
912
+ device
913
+ )),
914
+ /* @__PURE__ */ jsx(
915
+ "button",
916
+ {
917
+ className: "orion-builder-v2-tool",
918
+ disabled: !historyState.canUndo,
919
+ onClick: () => {
920
+ editorRef.current?.UndoManager.undo();
921
+ editorRef.current && updateHistoryState(editorRef.current);
922
+ },
923
+ type: "button",
924
+ children: "Undo"
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsx(
928
+ "button",
929
+ {
930
+ className: "orion-builder-v2-tool",
931
+ disabled: !historyState.canRedo,
932
+ onClick: () => {
933
+ editorRef.current?.UndoManager.redo();
934
+ editorRef.current && updateHistoryState(editorRef.current);
935
+ },
936
+ type: "button",
937
+ children: "Redo"
938
+ }
939
+ ),
940
+ /* @__PURE__ */ jsx("button", { className: "orion-builder-v2-tool is-primary", disabled: Boolean(saving), onClick: () => void save("draft"), type: "button", children: saving === "draft" ? "Saving..." : "Save draft" }),
941
+ /* @__PURE__ */ jsx("button", { className: "orion-builder-v2-tool is-publish", disabled: Boolean(saving), onClick: () => void save("published"), type: "button", children: saving === "published" ? "Publishing..." : "Publish" })
942
+ ] })
943
+ ] }),
609
944
  /* @__PURE__ */ jsxs("aside", { className: "orion-builder-v2-sidebar", children: [
945
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
946
+ /* @__PURE__ */ jsx("h2", { children: "Pages" }),
947
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-page-tree", children: pageTree.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No pages loaded." }) : pageTree.map((page) => /* @__PURE__ */ jsxs("a", { className: "orion-builder-v2-page-link", href: `#page-${page.id}`, children: [
948
+ /* @__PURE__ */ jsx("span", { children: page.title }),
949
+ /* @__PURE__ */ jsx("small", { children: page.path })
950
+ ] }, page.id)) })
951
+ ] }),
610
952
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
611
953
  /* @__PURE__ */ jsx("h2", { children: "Blocks" }),
954
+ /* @__PURE__ */ jsx("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
612
955
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-blocks" })
613
956
  ] }),
614
957
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
615
- /* @__PURE__ */ jsx("h2", { children: "Settings" }),
958
+ /* @__PURE__ */ jsx("h2", { children: "Inspector" }),
959
+ /* @__PURE__ */ jsx("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
616
960
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
961
+ ] }),
962
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
963
+ /* @__PURE__ */ jsx("h2", { children: "Validation" }),
964
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-validation-list", children: validationIssues.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No issues found." }) : validationIssues.map((issue) => /* @__PURE__ */ jsxs("div", { className: `orion-builder-v2-validation is-${issue.severity}`, children: [
965
+ /* @__PURE__ */ jsx("strong", { children: issue.message }),
966
+ /* @__PURE__ */ jsx("span", { children: issue.severity })
967
+ ] }, `${issue.code}-${issue.path}`)) })
617
968
  ] })
618
969
  ] }),
619
970
  /* @__PURE__ */ jsxs("main", { className: "orion-builder-v2-main", children: [
620
971
  loading ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
621
972
  error ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-error", children: error }) : null,
973
+ saveMessage || lastSavedAt ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-save-status", children: [
974
+ /* @__PURE__ */ jsx("strong", { children: saveMessage || "Ready" }),
975
+ lastSavedAt ? /* @__PURE__ */ jsxs("span", { children: [
976
+ "Last saved ",
977
+ lastSavedAt
978
+ ] }) : null
979
+ ] }) : null,
622
980
  /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-canvas", ref: containerRef })
623
981
  ] })
624
982
  ] });
@@ -15,18 +15,76 @@ type BuilderV2ValidationIssue = {
15
15
  path: string;
16
16
  severity: 'error' | 'warning';
17
17
  };
18
+ type BuilderV2Role = 'admin' | 'client' | 'developer' | 'editor';
19
+ type BuilderV2PermissionSet = {
20
+ canEditCustomCode?: boolean;
21
+ canManageGlobalStyles?: boolean;
22
+ canManageReusableSections?: boolean;
23
+ canPublish?: boolean;
24
+ canUseEmbeds?: boolean;
25
+ role?: BuilderV2Role;
26
+ };
18
27
  type BuilderV2CompiledOutput = {
19
28
  css: string;
20
29
  dynamicComponents: BuilderV2DynamicComponentInstance[];
21
30
  html: string;
31
+ validationIssues?: BuilderV2ValidationIssue[];
22
32
  };
23
33
  type BuilderV2ProjectData = Record<string, unknown>;
34
+ type BuilderV2ThemeTokens = {
35
+ buttons?: Record<string, unknown>;
36
+ colors?: Record<string, string>;
37
+ fonts?: Record<string, string>;
38
+ radii?: Record<string, string>;
39
+ shadows?: Record<string, string>;
40
+ spacing?: Record<string, string>;
41
+ typography?: Record<string, unknown>;
42
+ };
43
+ type BuilderV2PageTreeNode = {
44
+ children?: BuilderV2PageTreeNode[];
45
+ id: number | string;
46
+ parentId?: number | string | null;
47
+ path: string;
48
+ slug: string;
49
+ status?: 'draft' | 'published';
50
+ title: string;
51
+ };
52
+ type BuilderV2VersionSnapshot = {
53
+ compiledCss?: string;
54
+ compiledHtml?: string;
55
+ createdAt: string;
56
+ id: string;
57
+ label: string;
58
+ projectData?: BuilderV2ProjectData | null;
59
+ published?: boolean;
60
+ };
61
+ type BuilderV2ReusableSection = {
62
+ category?: string;
63
+ id: string;
64
+ name: string;
65
+ projectData: BuilderV2ProjectData;
66
+ thumbnail?: string;
67
+ };
68
+ type BuilderV2EditorMeta = {
69
+ pageTree?: BuilderV2PageTreeNode[];
70
+ permissions?: BuilderV2PermissionSet;
71
+ themeTokens?: BuilderV2ThemeTokens;
72
+ };
24
73
  type BuilderV2PageData = {
74
+ builderAutosaveProjectData?: BuilderV2ProjectData | null;
25
75
  builderDynamicComponents?: BuilderV2DynamicComponentInstance[];
76
+ builderLastPublishedAt?: string;
77
+ builderLastSavedAt?: string;
78
+ builderLockedAreas?: string[];
26
79
  builderMode?: string;
27
80
  builderProjectData?: BuilderV2ProjectData | null;
28
81
  builderPublishedProjectData?: BuilderV2ProjectData | null;
82
+ builderPublishedSnapshot?: BuilderV2VersionSnapshot | null;
83
+ builderReusableSections?: BuilderV2ReusableSection[];
84
+ builderSeo?: Record<string, unknown> | null;
85
+ builderThemeTokens?: BuilderV2ThemeTokens | null;
29
86
  builderValidationIssues?: BuilderV2ValidationIssue[];
87
+ builderVersions?: BuilderV2VersionSnapshot[];
30
88
  compiledCss?: string;
31
89
  compiledHtml?: string;
32
90
  id?: number | string;
@@ -70,11 +128,13 @@ type BuilderV2TraitDefinition = {
70
128
  type BuilderV2ProjectAdapter = {
71
129
  components?: Record<string, BuilderV2ProjectComponentDefinition | BuilderV2RuntimeComponent>;
72
130
  dataProviders?: Record<string, unknown>;
131
+ defaultThemeTokens?: BuilderV2ThemeTokens;
73
132
  id: string;
74
133
  label?: string;
75
134
  themeTokens?: Record<string, unknown>;
76
135
  };
77
136
  type BuilderV2EditorInitialData = {
137
+ meta?: BuilderV2EditorMeta;
78
138
  projectData?: BuilderV2ProjectData | null;
79
139
  title?: string;
80
140
  };
@@ -145,6 +205,19 @@ declare const createBuilderV2PageService: ({ collectionSlug, payload, user, }: C
145
205
  declare const createEmptyBuilderV2ProjectData: (title?: string) => BuilderV2ProjectData;
146
206
  declare const normalizeBuilderV2ProjectData: (value: unknown, fallbackTitle?: string) => BuilderV2ProjectData;
147
207
 
208
+ type PageLike = {
209
+ id: number | string;
210
+ parent?: number | string | {
211
+ id?: number | string;
212
+ } | null;
213
+ parentId?: number | string | null;
214
+ path?: string | null;
215
+ slug?: string | null;
216
+ title?: string | null;
217
+ _status?: 'draft' | 'published';
218
+ };
219
+ declare const buildBuilderV2PageTree: (pages: PageLike[]) => BuilderV2PageTreeNode[];
220
+
148
221
  declare function BuilderPageRuntime({ adapter, className, page }: BuilderV2RuntimeProps): react_jsx_runtime.JSX.Element;
149
222
 
150
223
  declare const parseBuilderV2DynamicComponents: (html: string) => BuilderV2DynamicComponentInstance[];
@@ -152,5 +225,12 @@ declare const splitBuilderV2HtmlIntoChunks: (html: string) => BuilderV2Renderabl
152
225
 
153
226
  declare const sanitizeBuilderHtml: (value: unknown) => string;
154
227
  declare const sanitizeBuilderCss: (value: unknown) => string;
228
+ declare const scopeBuilderCss: (value: unknown, scope?: string) => string;
229
+
230
+ declare const validateBuilderV2Output: (input: {
231
+ css?: unknown;
232
+ html?: unknown;
233
+ }) => BuilderV2ValidationIssue[];
234
+ declare const hasBlockingBuilderV2Issues: (issues: BuilderV2ValidationIssue[]) => boolean;
155
235
 
156
- export { BuilderPageRuntime, type BuilderV2Asset, type BuilderV2CompiledOutput, type BuilderV2DynamicComponentInstance, type BuilderV2EditorInitialData, type BuilderV2FieldOptions, type BuilderV2Mode, type BuilderV2PageData, type BuilderV2PagePayloadDoc, type BuilderV2ProjectAdapter, type BuilderV2ProjectComponentDefinition, type BuilderV2ProjectData, type BuilderV2RenderResult, type BuilderV2RenderableChunk, type BuilderV2RuntimeComponent, type BuilderV2RuntimeComponentProps, type BuilderV2RuntimeProps, type BuilderV2TraitDefinition, type BuilderV2ValidationIssue, type CreateBuilderV2PageServiceArgs, type SaveBuilderV2PageInput, appendBuilderV2PageFields, compileBuilderV2Output, createBuilderV2PageFields, createBuilderV2PageService, createEmptyBuilderV2ProjectData, normalizeBuilderV2ProjectData, parseBuilderV2DynamicComponents, sanitizeBuilderCss, sanitizeBuilderHtml, splitBuilderV2HtmlIntoChunks, toBuilderV2EditorInitialData };
236
+ export { BuilderPageRuntime, type BuilderV2Asset, type BuilderV2CompiledOutput, type BuilderV2DynamicComponentInstance, type BuilderV2EditorInitialData, type BuilderV2FieldOptions, type BuilderV2Mode, type BuilderV2PageData, type BuilderV2PagePayloadDoc, type BuilderV2PageTreeNode, type BuilderV2PermissionSet, type BuilderV2ProjectAdapter, type BuilderV2ProjectComponentDefinition, type BuilderV2ProjectData, type BuilderV2RenderResult, type BuilderV2RenderableChunk, type BuilderV2RuntimeComponent, type BuilderV2RuntimeComponentProps, type BuilderV2RuntimeProps, type BuilderV2ThemeTokens, type BuilderV2TraitDefinition, type BuilderV2ValidationIssue, type BuilderV2VersionSnapshot, type CreateBuilderV2PageServiceArgs, type SaveBuilderV2PageInput, appendBuilderV2PageFields, buildBuilderV2PageTree, compileBuilderV2Output, createBuilderV2PageFields, createBuilderV2PageService, createEmptyBuilderV2ProjectData, hasBlockingBuilderV2Issues, normalizeBuilderV2ProjectData, parseBuilderV2DynamicComponents, sanitizeBuilderCss, sanitizeBuilderHtml, scopeBuilderCss, splitBuilderV2HtmlIntoChunks, toBuilderV2EditorInitialData, validateBuilderV2Output };