@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.
@@ -30,6 +30,13 @@ var createBuilderV2PageFields = (options = {}) => {
30
30
  },
31
31
  hidden
32
32
  ),
33
+ withHiddenAdmin(
34
+ {
35
+ name: "builderAutosaveProjectData",
36
+ type: "json"
37
+ },
38
+ hidden
39
+ ),
33
40
  withHiddenAdmin(
34
41
  {
35
42
  name: "builderPublishedProjectData",
@@ -64,6 +71,62 @@ var createBuilderV2PageFields = (options = {}) => {
64
71
  type: "json"
65
72
  },
66
73
  hidden
74
+ ),
75
+ withHiddenAdmin(
76
+ {
77
+ name: "builderVersions",
78
+ type: "json"
79
+ },
80
+ hidden
81
+ ),
82
+ withHiddenAdmin(
83
+ {
84
+ name: "builderPublishedSnapshot",
85
+ type: "json"
86
+ },
87
+ hidden
88
+ ),
89
+ withHiddenAdmin(
90
+ {
91
+ name: "builderReusableSections",
92
+ type: "json"
93
+ },
94
+ hidden
95
+ ),
96
+ withHiddenAdmin(
97
+ {
98
+ name: "builderThemeTokens",
99
+ type: "json"
100
+ },
101
+ hidden
102
+ ),
103
+ withHiddenAdmin(
104
+ {
105
+ name: "builderLockedAreas",
106
+ type: "json"
107
+ },
108
+ hidden
109
+ ),
110
+ withHiddenAdmin(
111
+ {
112
+ name: "builderSeo",
113
+ type: "json"
114
+ },
115
+ hidden
116
+ ),
117
+ withHiddenAdmin(
118
+ {
119
+ name: "builderLastSavedAt",
120
+ type: "date"
121
+ },
122
+ hidden
123
+ ),
124
+ withHiddenAdmin(
125
+ {
126
+ name: "builderLastPublishedAt",
127
+ type: "date"
128
+ },
129
+ hidden
67
130
  )
68
131
  ];
69
132
  };
@@ -183,6 +246,7 @@ var allowedIframeHosts = [
183
246
  "calendar.google.com",
184
247
  "calendly.com",
185
248
  "player.vimeo.com",
249
+ "www.google.com",
186
250
  "www.youtube.com",
187
251
  "youtube.com"
188
252
  ];
@@ -208,15 +272,119 @@ var sanitizeBuilderCss = (value) => {
208
272
  }
209
273
  return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
210
274
  };
275
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
276
+ const trimmed = part.trim();
277
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
278
+ return trimmed;
279
+ }
280
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
281
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
282
+ }
283
+ return `${scope} ${trimmed}`;
284
+ }).filter(Boolean).join(", ");
285
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
286
+ const css = sanitizeBuilderCss(value);
287
+ if (!css) {
288
+ return "";
289
+ }
290
+ let output = "";
291
+ let cursor = 0;
292
+ const rulePattern = /([^{}]+)\{/g;
293
+ let match;
294
+ while ((match = rulePattern.exec(css)) !== null) {
295
+ const selectorStart = match.index;
296
+ const selector = match[1];
297
+ output += css.slice(cursor, selectorStart);
298
+ const trimmedSelector = selector.trim();
299
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
300
+ output += `${selector}{`;
301
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
302
+ output += `${selector}{`;
303
+ } else {
304
+ output += `${scopeSelector(selector, scope)} {`;
305
+ }
306
+ cursor = rulePattern.lastIndex;
307
+ }
308
+ output += css.slice(cursor);
309
+ return output;
310
+ };
311
+
312
+ // src/builder-v2/validation.ts
313
+ var hasMatch = (value, pattern) => pattern.test(value);
314
+ var validateBuilderV2Output = (input) => {
315
+ const html = typeof input.html === "string" ? input.html : "";
316
+ const css = typeof input.css === "string" ? input.css : "";
317
+ const issues = [];
318
+ if (!html.trim()) {
319
+ issues.push({
320
+ code: "empty-page",
321
+ message: "This page has no rendered content.",
322
+ path: "compiledHtml",
323
+ severity: "error"
324
+ });
325
+ }
326
+ if (!hasMatch(html, /<h1\b/i)) {
327
+ issues.push({
328
+ code: "missing-h1",
329
+ message: "Add one H1 so the published page has a clear primary heading.",
330
+ path: "compiledHtml",
331
+ severity: "warning"
332
+ });
333
+ }
334
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
335
+ issues.push({
336
+ code: "inline-event-handler",
337
+ message: "Inline event handlers are not allowed in published builder HTML.",
338
+ path: "compiledHtml",
339
+ severity: "error"
340
+ });
341
+ }
342
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
343
+ issues.push({
344
+ code: "unsafe-element",
345
+ message: "Script, object, and embed tags are not allowed in builder content.",
346
+ path: "compiledHtml",
347
+ severity: "error"
348
+ });
349
+ }
350
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
351
+ issues.push({
352
+ code: "unsafe-url",
353
+ message: "Links and media cannot use javascript URLs.",
354
+ path: "compiledHtml",
355
+ severity: "error"
356
+ });
357
+ }
358
+ if (hasMatch(css, /@import\s/i)) {
359
+ issues.push({
360
+ code: "css-import",
361
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
362
+ path: "compiledCss",
363
+ severity: "warning"
364
+ });
365
+ }
366
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
367
+ issues.push({
368
+ code: "fixed-position",
369
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
370
+ path: "compiledCss",
371
+ severity: "warning"
372
+ });
373
+ }
374
+ return issues;
375
+ };
376
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
211
377
 
212
378
  // src/builder-v2/payload/compile.ts
213
379
  var compileBuilderV2Output = (input) => {
214
380
  const html = sanitizeBuilderHtml(input.html);
215
- const css = sanitizeBuilderCss(input.css);
381
+ const css = scopeBuilderCss(input.css);
382
+ const validationIssues = validateBuilderV2Output({ css: input.css, html: input.html });
216
383
  return {
217
384
  css,
218
385
  dynamicComponents: parseBuilderV2DynamicComponents(html),
219
- html
386
+ html,
387
+ validationIssues
220
388
  };
221
389
  };
222
390
 
@@ -264,6 +432,25 @@ var normalizeBuilderV2ProjectData = (value, fallbackTitle = "Untitled Page") =>
264
432
 
265
433
  // src/builder-v2/payload/pageService.ts
266
434
  var pageTitle = (page) => typeof page.title === "string" && page.title.trim().length > 0 ? page.title : "Untitled Page";
435
+ var normalizeVersions = (value) => Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object")) : [];
436
+ var createVersionSnapshot = ({
437
+ compiled,
438
+ label,
439
+ projectData,
440
+ published = false
441
+ }) => {
442
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
443
+ return {
444
+ compiledCss: compiled.css,
445
+ compiledHtml: compiled.html,
446
+ createdAt,
447
+ id: `builder-v2-${createdAt.replace(/[^0-9]/g, "")}`,
448
+ label,
449
+ projectData,
450
+ published
451
+ };
452
+ };
453
+ var appendVersionSnapshot = (current, snapshot) => [snapshot, ...normalizeVersions(current)].slice(0, 50);
267
454
  var toBuilderV2EditorInitialData = (page) => ({
268
455
  projectData: normalizeBuilderV2ProjectData(
269
456
  page.builderProjectData || page.builderPublishedProjectData,
@@ -293,14 +480,33 @@ var createBuilderV2PageService = ({
293
480
  },
294
481
  saveDraft: async (pageID, input) => {
295
482
  const compiled = compileBuilderV2Output(input.compiled);
483
+ const existing = await payload.findByID({
484
+ collection: collectionSlug,
485
+ depth: 0,
486
+ draft: true,
487
+ id: pageID,
488
+ overrideAccess: false,
489
+ user
490
+ });
491
+ const validationIssues = input.validationIssues || compiled.validationIssues || [];
296
492
  await payload.update({
297
493
  collection: collectionSlug,
298
494
  data: {
299
495
  _status: "draft",
496
+ builderAutosaveProjectData: null,
300
497
  builderDynamicComponents: compiled.dynamicComponents,
498
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
301
499
  builderMode: "grapes-v2",
302
500
  builderProjectData: input.projectData,
303
- builderValidationIssues: input.validationIssues || [],
501
+ builderValidationIssues: validationIssues,
502
+ builderVersions: appendVersionSnapshot(
503
+ existing.builderVersions,
504
+ createVersionSnapshot({
505
+ compiled,
506
+ label: "Draft saved",
507
+ projectData: input.projectData
508
+ })
509
+ ),
304
510
  compiledCss: compiled.css,
305
511
  compiledHtml: compiled.html,
306
512
  ...input.title ? { title: input.title } : {}
@@ -317,15 +523,35 @@ var createBuilderV2PageService = ({
317
523
  },
318
524
  publish: async (pageID, input) => {
319
525
  const compiled = compileBuilderV2Output(input.compiled);
526
+ const existing = await payload.findByID({
527
+ collection: collectionSlug,
528
+ depth: 0,
529
+ draft: true,
530
+ id: pageID,
531
+ overrideAccess: false,
532
+ user
533
+ });
534
+ const validationIssues = input.validationIssues || compiled.validationIssues || [];
535
+ const snapshot = createVersionSnapshot({
536
+ compiled,
537
+ label: "Published",
538
+ projectData: input.projectData,
539
+ published: true
540
+ });
320
541
  await payload.update({
321
542
  collection: collectionSlug,
322
543
  data: {
323
544
  _status: "published",
545
+ builderAutosaveProjectData: null,
324
546
  builderDynamicComponents: compiled.dynamicComponents,
547
+ builderLastPublishedAt: snapshot.createdAt,
548
+ builderLastSavedAt: snapshot.createdAt,
325
549
  builderMode: "grapes-v2",
326
550
  builderProjectData: input.projectData,
327
551
  builderPublishedProjectData: input.projectData,
328
- builderValidationIssues: input.validationIssues || [],
552
+ builderPublishedSnapshot: snapshot,
553
+ builderValidationIssues: validationIssues,
554
+ builderVersions: appendVersionSnapshot(existing.builderVersions, snapshot),
329
555
  compiledCss: compiled.css,
330
556
  compiledHtml: compiled.html,
331
557
  ...input.title ? { title: input.title } : {}
@@ -342,6 +568,46 @@ var createBuilderV2PageService = ({
342
568
  }
343
569
  });
344
570
 
571
+ // src/builder-v2/pageTree.ts
572
+ var relationId = (value) => {
573
+ if (typeof value === "number" || typeof value === "string") {
574
+ return value;
575
+ }
576
+ if (value && typeof value === "object") {
577
+ const id = value.id;
578
+ return typeof id === "number" || typeof id === "string" ? id : null;
579
+ }
580
+ return null;
581
+ };
582
+ var buildBuilderV2PageTree = (pages) => {
583
+ const nodes = /* @__PURE__ */ new Map();
584
+ const roots = [];
585
+ pages.forEach((page) => {
586
+ nodes.set(page.id, {
587
+ children: [],
588
+ id: page.id,
589
+ parentId: page.parentId ?? relationId(page.parent),
590
+ path: page.path || (page.slug ? `/${page.slug}` : "/"),
591
+ slug: page.slug || "",
592
+ status: page._status,
593
+ title: page.title || "Untitled page"
594
+ });
595
+ });
596
+ nodes.forEach((node) => {
597
+ if (node.parentId && nodes.has(node.parentId)) {
598
+ nodes.get(node.parentId)?.children?.push(node);
599
+ } else {
600
+ roots.push(node);
601
+ }
602
+ });
603
+ const sortNodes = (items) => {
604
+ items.sort((a, b) => a.path.localeCompare(b.path));
605
+ items.forEach((item) => sortNodes(item.children || []));
606
+ };
607
+ sortNodes(roots);
608
+ return roots;
609
+ };
610
+
345
611
  // src/builder-v2/runtime/BuilderPageRuntime.tsx
346
612
  import { createElement, Fragment } from "react";
347
613
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -357,7 +623,7 @@ var resolveRuntimeComponent = (adapter, type) => {
357
623
  };
358
624
  function BuilderPageRuntime({ adapter, className, page }) {
359
625
  const html = sanitizeBuilderHtml(page.compiledHtml);
360
- const css = sanitizeBuilderCss(page.compiledCss);
626
+ const css = scopeBuilderCss(page.compiledCss);
361
627
  const chunks = splitBuilderV2HtmlIntoChunks(html);
362
628
  return /* @__PURE__ */ jsxs("div", { className: ["orion-builder-v2-runtime", className].filter(Boolean).join(" "), children: [
363
629
  css ? /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } }) : null,
@@ -390,14 +656,18 @@ function BuilderPageRuntime({ adapter, className, page }) {
390
656
  export {
391
657
  BuilderPageRuntime,
392
658
  appendBuilderV2PageFields,
659
+ buildBuilderV2PageTree,
393
660
  compileBuilderV2Output,
394
661
  createBuilderV2PageFields,
395
662
  createBuilderV2PageService,
396
663
  createEmptyBuilderV2ProjectData,
664
+ hasBlockingBuilderV2Issues,
397
665
  normalizeBuilderV2ProjectData,
398
666
  parseBuilderV2DynamicComponents,
399
667
  sanitizeBuilderCss,
400
668
  sanitizeBuilderHtml,
669
+ scopeBuilderCss,
401
670
  splitBuilderV2HtmlIntoChunks,
402
- toBuilderV2EditorInitialData
671
+ toBuilderV2EditorInitialData,
672
+ validateBuilderV2Output
403
673
  };