@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.
@@ -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
  };
@@ -208,15 +271,119 @@ var sanitizeBuilderCss = (value) => {
208
271
  }
209
272
  return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
210
273
  };
274
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
275
+ const trimmed = part.trim();
276
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
277
+ return trimmed;
278
+ }
279
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
280
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
281
+ }
282
+ return `${scope} ${trimmed}`;
283
+ }).filter(Boolean).join(", ");
284
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
285
+ const css = sanitizeBuilderCss(value);
286
+ if (!css) {
287
+ return "";
288
+ }
289
+ let output = "";
290
+ let cursor = 0;
291
+ const rulePattern = /([^{}]+)\{/g;
292
+ let match;
293
+ while ((match = rulePattern.exec(css)) !== null) {
294
+ const selectorStart = match.index;
295
+ const selector = match[1];
296
+ output += css.slice(cursor, selectorStart);
297
+ const trimmedSelector = selector.trim();
298
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
299
+ output += `${selector}{`;
300
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
301
+ output += `${selector}{`;
302
+ } else {
303
+ output += `${scopeSelector(selector, scope)} {`;
304
+ }
305
+ cursor = rulePattern.lastIndex;
306
+ }
307
+ output += css.slice(cursor);
308
+ return output;
309
+ };
310
+
311
+ // src/builder-v2/validation.ts
312
+ var hasMatch = (value, pattern) => pattern.test(value);
313
+ var validateBuilderV2Output = (input) => {
314
+ const html = typeof input.html === "string" ? input.html : "";
315
+ const css = typeof input.css === "string" ? input.css : "";
316
+ const issues = [];
317
+ if (!html.trim()) {
318
+ issues.push({
319
+ code: "empty-page",
320
+ message: "This page has no rendered content.",
321
+ path: "compiledHtml",
322
+ severity: "error"
323
+ });
324
+ }
325
+ if (!hasMatch(html, /<h1\b/i)) {
326
+ issues.push({
327
+ code: "missing-h1",
328
+ message: "Add one H1 so the published page has a clear primary heading.",
329
+ path: "compiledHtml",
330
+ severity: "warning"
331
+ });
332
+ }
333
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
334
+ issues.push({
335
+ code: "inline-event-handler",
336
+ message: "Inline event handlers are not allowed in published builder HTML.",
337
+ path: "compiledHtml",
338
+ severity: "error"
339
+ });
340
+ }
341
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
342
+ issues.push({
343
+ code: "unsafe-element",
344
+ message: "Script, object, and embed tags are not allowed in builder content.",
345
+ path: "compiledHtml",
346
+ severity: "error"
347
+ });
348
+ }
349
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
350
+ issues.push({
351
+ code: "unsafe-url",
352
+ message: "Links and media cannot use javascript URLs.",
353
+ path: "compiledHtml",
354
+ severity: "error"
355
+ });
356
+ }
357
+ if (hasMatch(css, /@import\s/i)) {
358
+ issues.push({
359
+ code: "css-import",
360
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
361
+ path: "compiledCss",
362
+ severity: "warning"
363
+ });
364
+ }
365
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
366
+ issues.push({
367
+ code: "fixed-position",
368
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
369
+ path: "compiledCss",
370
+ severity: "warning"
371
+ });
372
+ }
373
+ return issues;
374
+ };
375
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
211
376
 
212
377
  // src/builder-v2/payload/compile.ts
213
378
  var compileBuilderV2Output = (input) => {
214
379
  const html = sanitizeBuilderHtml(input.html);
215
- const css = sanitizeBuilderCss(input.css);
380
+ const css = scopeBuilderCss(input.css);
381
+ const validationIssues = validateBuilderV2Output({ css: input.css, html: input.html });
216
382
  return {
217
383
  css,
218
384
  dynamicComponents: parseBuilderV2DynamicComponents(html),
219
- html
385
+ html,
386
+ validationIssues
220
387
  };
221
388
  };
222
389
 
@@ -264,6 +431,25 @@ var normalizeBuilderV2ProjectData = (value, fallbackTitle = "Untitled Page") =>
264
431
 
265
432
  // src/builder-v2/payload/pageService.ts
266
433
  var pageTitle = (page) => typeof page.title === "string" && page.title.trim().length > 0 ? page.title : "Untitled Page";
434
+ var normalizeVersions = (value) => Array.isArray(value) ? value.filter((item) => Boolean(item && typeof item === "object")) : [];
435
+ var createVersionSnapshot = ({
436
+ compiled,
437
+ label,
438
+ projectData,
439
+ published = false
440
+ }) => {
441
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
442
+ return {
443
+ compiledCss: compiled.css,
444
+ compiledHtml: compiled.html,
445
+ createdAt,
446
+ id: `builder-v2-${createdAt.replace(/[^0-9]/g, "")}`,
447
+ label,
448
+ projectData,
449
+ published
450
+ };
451
+ };
452
+ var appendVersionSnapshot = (current, snapshot) => [snapshot, ...normalizeVersions(current)].slice(0, 50);
267
453
  var toBuilderV2EditorInitialData = (page) => ({
268
454
  projectData: normalizeBuilderV2ProjectData(
269
455
  page.builderProjectData || page.builderPublishedProjectData,
@@ -293,14 +479,33 @@ var createBuilderV2PageService = ({
293
479
  },
294
480
  saveDraft: async (pageID, input) => {
295
481
  const compiled = compileBuilderV2Output(input.compiled);
482
+ const existing = await payload.findByID({
483
+ collection: collectionSlug,
484
+ depth: 0,
485
+ draft: true,
486
+ id: pageID,
487
+ overrideAccess: false,
488
+ user
489
+ });
490
+ const validationIssues = input.validationIssues || compiled.validationIssues || [];
296
491
  await payload.update({
297
492
  collection: collectionSlug,
298
493
  data: {
299
494
  _status: "draft",
495
+ builderAutosaveProjectData: null,
300
496
  builderDynamicComponents: compiled.dynamicComponents,
497
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
301
498
  builderMode: "grapes-v2",
302
499
  builderProjectData: input.projectData,
303
- builderValidationIssues: input.validationIssues || [],
500
+ builderValidationIssues: validationIssues,
501
+ builderVersions: appendVersionSnapshot(
502
+ existing.builderVersions,
503
+ createVersionSnapshot({
504
+ compiled,
505
+ label: "Draft saved",
506
+ projectData: input.projectData
507
+ })
508
+ ),
304
509
  compiledCss: compiled.css,
305
510
  compiledHtml: compiled.html,
306
511
  ...input.title ? { title: input.title } : {}
@@ -317,15 +522,35 @@ var createBuilderV2PageService = ({
317
522
  },
318
523
  publish: async (pageID, input) => {
319
524
  const compiled = compileBuilderV2Output(input.compiled);
525
+ const existing = await payload.findByID({
526
+ collection: collectionSlug,
527
+ depth: 0,
528
+ draft: true,
529
+ id: pageID,
530
+ overrideAccess: false,
531
+ user
532
+ });
533
+ const validationIssues = input.validationIssues || compiled.validationIssues || [];
534
+ const snapshot = createVersionSnapshot({
535
+ compiled,
536
+ label: "Published",
537
+ projectData: input.projectData,
538
+ published: true
539
+ });
320
540
  await payload.update({
321
541
  collection: collectionSlug,
322
542
  data: {
323
543
  _status: "published",
544
+ builderAutosaveProjectData: null,
324
545
  builderDynamicComponents: compiled.dynamicComponents,
546
+ builderLastPublishedAt: snapshot.createdAt,
547
+ builderLastSavedAt: snapshot.createdAt,
325
548
  builderMode: "grapes-v2",
326
549
  builderProjectData: input.projectData,
327
550
  builderPublishedProjectData: input.projectData,
328
- builderValidationIssues: input.validationIssues || [],
551
+ builderPublishedSnapshot: snapshot,
552
+ builderValidationIssues: validationIssues,
553
+ builderVersions: appendVersionSnapshot(existing.builderVersions, snapshot),
329
554
  compiledCss: compiled.css,
330
555
  compiledHtml: compiled.html,
331
556
  ...input.title ? { title: input.title } : {}
@@ -342,6 +567,46 @@ var createBuilderV2PageService = ({
342
567
  }
343
568
  });
344
569
 
570
+ // src/builder-v2/pageTree.ts
571
+ var relationId = (value) => {
572
+ if (typeof value === "number" || typeof value === "string") {
573
+ return value;
574
+ }
575
+ if (value && typeof value === "object") {
576
+ const id = value.id;
577
+ return typeof id === "number" || typeof id === "string" ? id : null;
578
+ }
579
+ return null;
580
+ };
581
+ var buildBuilderV2PageTree = (pages) => {
582
+ const nodes = /* @__PURE__ */ new Map();
583
+ const roots = [];
584
+ pages.forEach((page) => {
585
+ nodes.set(page.id, {
586
+ children: [],
587
+ id: page.id,
588
+ parentId: page.parentId ?? relationId(page.parent),
589
+ path: page.path || (page.slug ? `/${page.slug}` : "/"),
590
+ slug: page.slug || "",
591
+ status: page._status,
592
+ title: page.title || "Untitled page"
593
+ });
594
+ });
595
+ nodes.forEach((node) => {
596
+ if (node.parentId && nodes.has(node.parentId)) {
597
+ nodes.get(node.parentId)?.children?.push(node);
598
+ } else {
599
+ roots.push(node);
600
+ }
601
+ });
602
+ const sortNodes = (items) => {
603
+ items.sort((a, b) => a.path.localeCompare(b.path));
604
+ items.forEach((item) => sortNodes(item.children || []));
605
+ };
606
+ sortNodes(roots);
607
+ return roots;
608
+ };
609
+
345
610
  // src/builder-v2/runtime/BuilderPageRuntime.tsx
346
611
  import { createElement, Fragment } from "react";
347
612
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -357,7 +622,7 @@ var resolveRuntimeComponent = (adapter, type) => {
357
622
  };
358
623
  function BuilderPageRuntime({ adapter, className, page }) {
359
624
  const html = sanitizeBuilderHtml(page.compiledHtml);
360
- const css = sanitizeBuilderCss(page.compiledCss);
625
+ const css = scopeBuilderCss(page.compiledCss);
361
626
  const chunks = splitBuilderV2HtmlIntoChunks(html);
362
627
  return /* @__PURE__ */ jsxs("div", { className: ["orion-builder-v2-runtime", className].filter(Boolean).join(" "), children: [
363
628
  css ? /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } }) : null,
@@ -390,14 +655,18 @@ function BuilderPageRuntime({ adapter, className, page }) {
390
655
  export {
391
656
  BuilderPageRuntime,
392
657
  appendBuilderV2PageFields,
658
+ buildBuilderV2PageTree,
393
659
  compileBuilderV2Output,
394
660
  createBuilderV2PageFields,
395
661
  createBuilderV2PageService,
396
662
  createEmptyBuilderV2ProjectData,
663
+ hasBlockingBuilderV2Issues,
397
664
  normalizeBuilderV2ProjectData,
398
665
  parseBuilderV2DynamicComponents,
399
666
  sanitizeBuilderCss,
400
667
  sanitizeBuilderHtml,
668
+ scopeBuilderCss,
401
669
  splitBuilderV2HtmlIntoChunks,
402
- toBuilderV2EditorInitialData
670
+ toBuilderV2EditorInitialData,
671
+ validateBuilderV2Output
403
672
  };
@@ -1,14 +1,86 @@
1
1
  @import 'grapesjs/dist/css/grapes.min.css';
2
2
 
3
3
  .orion-builder-v2-editor {
4
- background: #f4f6f8;
4
+ background: #eef1f4;
5
5
  color: #172033;
6
6
  display: grid;
7
7
  grid-template-columns: minmax(260px, 310px) 1fr;
8
+ grid-template-rows: auto minmax(0, 1fr);
8
9
  height: 100dvh;
9
10
  min-height: 640px;
10
11
  }
11
12
 
13
+ .orion-builder-v2-topbar {
14
+ align-items: center;
15
+ background: #101827;
16
+ border-bottom: 1px solid rgba(255, 255, 255, 0.12);
17
+ color: #ffffff;
18
+ display: flex;
19
+ gap: 20px;
20
+ grid-column: 1 / -1;
21
+ justify-content: space-between;
22
+ min-width: 0;
23
+ padding: 12px 16px;
24
+ }
25
+
26
+ .orion-builder-v2-topbar h1 {
27
+ font-size: 1.05rem;
28
+ line-height: 1.2;
29
+ margin: 2px 0 0;
30
+ }
31
+
32
+ .orion-builder-v2-eyebrow {
33
+ color: #9fb1c8;
34
+ font-size: 0.72rem;
35
+ font-weight: 900;
36
+ letter-spacing: 0.08em;
37
+ margin: 0;
38
+ text-transform: uppercase;
39
+ }
40
+
41
+ .orion-builder-v2-toolbar {
42
+ align-items: center;
43
+ display: flex;
44
+ flex-wrap: wrap;
45
+ gap: 8px;
46
+ justify-content: flex-end;
47
+ }
48
+
49
+ .orion-builder-v2-tool {
50
+ background: rgba(255, 255, 255, 0.08);
51
+ border: 1px solid rgba(255, 255, 255, 0.16);
52
+ border-radius: 7px;
53
+ color: #ffffff;
54
+ cursor: pointer;
55
+ font: inherit;
56
+ font-size: 0.82rem;
57
+ font-weight: 800;
58
+ padding: 8px 10px;
59
+ text-transform: capitalize;
60
+ }
61
+
62
+ .orion-builder-v2-tool[aria-pressed='true'],
63
+ .orion-builder-v2-tool:hover {
64
+ background: #ffffff;
65
+ color: #101827;
66
+ }
67
+
68
+ .orion-builder-v2-tool:disabled {
69
+ cursor: not-allowed;
70
+ opacity: 0.45;
71
+ }
72
+
73
+ .orion-builder-v2-tool.is-primary {
74
+ background: #e8eef7;
75
+ color: #101827;
76
+ }
77
+
78
+ .orion-builder-v2-tool.is-publish {
79
+ background: #c35f36;
80
+ border-color: #c35f36;
81
+ color: #ffffff;
82
+ }
83
+
12
84
  .orion-builder-v2-sidebar {
13
85
  background: #ffffff;
14
86
  border-right: 1px solid rgba(23, 32, 51, 0.12);
@@ -23,6 +95,13 @@
23
95
  gap: 10px;
24
96
  }
25
97
 
98
+ .orion-builder-v2-panel p {
99
+ color: #526074;
100
+ font-size: 0.82rem;
101
+ line-height: 1.4;
102
+ margin: 0;
103
+ }
104
+
26
105
  .orion-builder-v2-panel h2 {
27
106
  font-size: 0.8rem;
28
107
  letter-spacing: 0;
@@ -40,7 +119,8 @@
40
119
  }
41
120
 
42
121
  .orion-builder-v2-status,
43
- .orion-builder-v2-error {
122
+ .orion-builder-v2-error,
123
+ .orion-builder-v2-save-status {
44
124
  border-radius: 8px;
45
125
  font-weight: 800;
46
126
  left: 16px;
@@ -61,6 +141,55 @@
61
141
  color: #9f1239;
62
142
  }
63
143
 
144
+ .orion-builder-v2-save-status {
145
+ background: #ffffff;
146
+ border: 1px solid rgba(23, 32, 51, 0.12);
147
+ box-shadow: 0 14px 40px rgba(23, 32, 51, 0.12);
148
+ display: grid;
149
+ gap: 2px;
150
+ left: auto;
151
+ right: 16px;
152
+ }
153
+
154
+ .orion-builder-v2-save-status span {
155
+ color: #526074;
156
+ font-size: 0.72rem;
157
+ font-weight: 700;
158
+ }
159
+
160
+ .orion-builder-v2-validation-list {
161
+ display: grid;
162
+ gap: 8px;
163
+ }
164
+
165
+ .orion-builder-v2-validation {
166
+ border-radius: 8px;
167
+ display: grid;
168
+ gap: 4px;
169
+ padding: 9px;
170
+ }
171
+
172
+ .orion-builder-v2-validation strong {
173
+ font-size: 0.78rem;
174
+ line-height: 1.3;
175
+ }
176
+
177
+ .orion-builder-v2-validation span {
178
+ font-size: 0.68rem;
179
+ font-weight: 900;
180
+ text-transform: uppercase;
181
+ }
182
+
183
+ .orion-builder-v2-validation.is-warning {
184
+ background: #fff7ed;
185
+ color: #9a3412;
186
+ }
187
+
188
+ .orion-builder-v2-validation.is-error {
189
+ background: #fff1f2;
190
+ color: #9f1239;
191
+ }
192
+
64
193
  .orion-builder-v2-runtime {
65
194
  width: 100%;
66
195
  }
@@ -78,6 +207,10 @@
78
207
  padding: 64px 24px;
79
208
  }
80
209
 
210
+ .orion-builder-v2-section.is-muted {
211
+ background: #f4f6f8;
212
+ }
213
+
81
214
  .orion-builder-v2-hero {
82
215
  background: #172033;
83
216
  color: #ffffff;
@@ -88,6 +221,10 @@
88
221
  max-width: 1120px;
89
222
  }
90
223
 
224
+ .orion-builder-v2-container.is-narrow {
225
+ max-width: 760px;
226
+ }
227
+
91
228
  .orion-builder-v2-grid {
92
229
  display: grid;
93
230
  gap: 20px;
@@ -105,6 +242,11 @@
105
242
  padding: 20px;
106
243
  }
107
244
 
245
+ .orion-builder-v2-card.is-featured {
246
+ border-color: #c35f36;
247
+ box-shadow: 0 20px 50px rgba(195, 95, 54, 0.16);
248
+ }
249
+
108
250
  .orion-builder-v2-button {
109
251
  background: #172033;
110
252
  border-radius: 8px;
@@ -132,6 +274,52 @@
132
274
  max-width: 100%;
133
275
  }
134
276
 
277
+ .orion-builder-v2-nav,
278
+ .orion-builder-v2-footer {
279
+ align-items: center;
280
+ display: flex;
281
+ gap: 20px;
282
+ justify-content: space-between;
283
+ padding: 20px 24px;
284
+ }
285
+
286
+ .orion-builder-v2-nav nav,
287
+ .orion-builder-v2-footer nav {
288
+ display: flex;
289
+ flex-wrap: wrap;
290
+ gap: 14px;
291
+ }
292
+
293
+ .orion-builder-v2-logo {
294
+ color: inherit;
295
+ font-weight: 900;
296
+ text-decoration: none;
297
+ }
298
+
299
+ .orion-builder-v2-footer {
300
+ background: #101827;
301
+ color: #ffffff;
302
+ }
303
+
304
+ .orion-builder-v2-gallery {
305
+ display: grid;
306
+ gap: 14px;
307
+ grid-template-columns: repeat(4, minmax(0, 1fr));
308
+ }
309
+
310
+ .orion-builder-v2-gallery img {
311
+ aspect-ratio: 1;
312
+ border-radius: 8px;
313
+ height: 100%;
314
+ object-fit: cover;
315
+ width: 100%;
316
+ }
317
+
318
+ .orion-builder-v2-price {
319
+ font-size: 2rem;
320
+ font-weight: 900;
321
+ }
322
+
135
323
  .orion-builder-v2-dynamic-placeholder {
136
324
  align-items: center;
137
325
  background: repeating-linear-gradient(
@@ -152,7 +340,12 @@
152
340
  @media (max-width: 800px) {
153
341
  .orion-builder-v2-editor {
154
342
  grid-template-columns: 1fr;
155
- grid-template-rows: auto 1fr;
343
+ grid-template-rows: auto auto 1fr;
344
+ }
345
+
346
+ .orion-builder-v2-topbar {
347
+ align-items: flex-start;
348
+ flex-direction: column;
156
349
  }
157
350
 
158
351
  .orion-builder-v2-sidebar {
@@ -162,4 +355,14 @@
162
355
  .orion-builder-v2-grid.is-3 {
163
356
  grid-template-columns: 1fr;
164
357
  }
358
+
359
+ .orion-builder-v2-gallery {
360
+ grid-template-columns: repeat(2, minmax(0, 1fr));
361
+ }
362
+
363
+ .orion-builder-v2-nav,
364
+ .orion-builder-v2-footer {
365
+ align-items: flex-start;
366
+ flex-direction: column;
367
+ }
165
368
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.6.0-beta.45",
3
+ "version": "0.6.0-beta.46",
4
4
  "description": "Base CMS, builder, and custom admin toolkit for Orion Studios websites",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",