@orion-studios/payload-studio 0.6.0-beta.57 → 0.6.0-beta.58

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.
@@ -953,6 +953,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
953
953
 
954
954
  // src/builder-v2/editor/GrapesPageEditor.tsx
955
955
  var import_jsx_runtime = require("react/jsx-runtime");
956
+ var decodeHtmlAttribute3 = (value) => {
957
+ if (typeof value !== "string") {
958
+ return "";
959
+ }
960
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
961
+ };
962
+ var parseItemsJson = (value) => {
963
+ const decoded = decodeHtmlAttribute3(value);
964
+ if (!decoded) {
965
+ return [];
966
+ }
967
+ try {
968
+ const parsed = JSON.parse(decoded);
969
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
970
+ } catch {
971
+ return [];
972
+ }
973
+ };
956
974
  var postToParent = (payload) => {
957
975
  window.parent?.postMessage(
958
976
  {
@@ -1153,6 +1171,7 @@ function GrapesPageEditor({
1153
1171
  const editorRef = (0, import_react.useRef)(null);
1154
1172
  const autosaveTimerRef = (0, import_react.useRef)(null);
1155
1173
  const saveRef = (0, import_react.useRef)(async () => void 0);
1174
+ const selectedComponentRef = (0, import_react.useRef)(null);
1156
1175
  const [error, setError] = (0, import_react.useState)("");
1157
1176
  const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
1158
1177
  const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
@@ -1164,6 +1183,37 @@ function GrapesPageEditor({
1164
1183
  const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
1165
1184
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1166
1185
  const pageTree = initialData?.meta?.pageTree || [];
1186
+ const summarizeSelectedComponent = (component) => {
1187
+ if (!component) {
1188
+ return null;
1189
+ }
1190
+ const attrs = component.getAttributes?.() || {};
1191
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1192
+ const rawName = String(component.get?.("name") || componentType || "Section");
1193
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1194
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1195
+ index,
1196
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1197
+ }));
1198
+ return {
1199
+ items: items.length > 0 ? items : void 0,
1200
+ label: label || "Section",
1201
+ type: componentType
1202
+ };
1203
+ };
1204
+ const updateSelectedItems = (updater) => {
1205
+ const component = selectedComponentRef.current;
1206
+ if (!component) {
1207
+ return;
1208
+ }
1209
+ const attrs = component.getAttributes?.() || {};
1210
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1211
+ const nextItems = updater(currentItems);
1212
+ component.addAttributes?.({
1213
+ "data-orion-items-json": JSON.stringify(nextItems)
1214
+ });
1215
+ setSelectionSummary(summarizeSelectedComponent(component));
1216
+ };
1167
1217
  const updateHistoryState = (editor) => {
1168
1218
  const next = {
1169
1219
  canRedo: editor.UndoManager.hasRedo(),
@@ -1289,14 +1339,8 @@ function GrapesPageEditor({
1289
1339
  });
1290
1340
  editor.on("component:selected", (component) => {
1291
1341
  const typed = component;
1292
- const attrs = typed?.getAttributes?.() || {};
1293
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1294
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1295
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1296
- setSelectionSummary({
1297
- label: label || "Section",
1298
- type: componentType
1299
- });
1342
+ selectedComponentRef.current = typed;
1343
+ setSelectionSummary(summarizeSelectedComponent(typed));
1300
1344
  setSaveMessage("Selection ready");
1301
1345
  });
1302
1346
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1540,6 +1584,62 @@ function GrapesPageEditor({
1540
1584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
1541
1585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1542
1586
  ] }),
1587
+ selectionSummary?.items ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Items" }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-item-row", children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.title }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1593
+ "button",
1594
+ {
1595
+ onClick: () => {
1596
+ updateSelectedItems((items) => {
1597
+ const source = items[item.index];
1598
+ if (!source) {
1599
+ return items;
1600
+ }
1601
+ const copy = [...items];
1602
+ copy.splice(item.index + 1, 0, {
1603
+ ...source,
1604
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1605
+ });
1606
+ return copy;
1607
+ });
1608
+ },
1609
+ type: "button",
1610
+ children: "Duplicate"
1611
+ }
1612
+ ),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1614
+ "button",
1615
+ {
1616
+ onClick: () => {
1617
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1618
+ },
1619
+ type: "button",
1620
+ children: "Remove"
1621
+ }
1622
+ )
1623
+ ] }, `${item.index}-${item.title}`)) }),
1624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1625
+ "button",
1626
+ {
1627
+ className: "orion-builder-v2-wide-action",
1628
+ onClick: () => {
1629
+ updateSelectedItems((items) => [
1630
+ ...items,
1631
+ {
1632
+ description: "Describe this feature.",
1633
+ imageURL: "",
1634
+ title: "New feature"
1635
+ }
1636
+ ]);
1637
+ },
1638
+ type: "button",
1639
+ children: "Add item"
1640
+ }
1641
+ )
1642
+ ] }) : null,
1543
1643
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1544
1644
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Design" }),
1545
1645
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
@@ -829,6 +829,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
829
829
 
830
830
  // src/builder-v2/editor/GrapesPageEditor.tsx
831
831
  import { jsx, jsxs } from "react/jsx-runtime";
832
+ var decodeHtmlAttribute3 = (value) => {
833
+ if (typeof value !== "string") {
834
+ return "";
835
+ }
836
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
837
+ };
838
+ var parseItemsJson = (value) => {
839
+ const decoded = decodeHtmlAttribute3(value);
840
+ if (!decoded) {
841
+ return [];
842
+ }
843
+ try {
844
+ const parsed = JSON.parse(decoded);
845
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
846
+ } catch {
847
+ return [];
848
+ }
849
+ };
832
850
  var postToParent = (payload) => {
833
851
  window.parent?.postMessage(
834
852
  {
@@ -1029,6 +1047,7 @@ function GrapesPageEditor({
1029
1047
  const editorRef = useRef(null);
1030
1048
  const autosaveTimerRef = useRef(null);
1031
1049
  const saveRef = useRef(async () => void 0);
1050
+ const selectedComponentRef = useRef(null);
1032
1051
  const [error, setError] = useState("");
1033
1052
  const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
1034
1053
  const [lastSavedAt, setLastSavedAt] = useState("");
@@ -1040,6 +1059,37 @@ function GrapesPageEditor({
1040
1059
  const [validationIssues, setValidationIssues] = useState([]);
1041
1060
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1042
1061
  const pageTree = initialData?.meta?.pageTree || [];
1062
+ const summarizeSelectedComponent = (component) => {
1063
+ if (!component) {
1064
+ return null;
1065
+ }
1066
+ const attrs = component.getAttributes?.() || {};
1067
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1068
+ const rawName = String(component.get?.("name") || componentType || "Section");
1069
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1070
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1071
+ index,
1072
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1073
+ }));
1074
+ return {
1075
+ items: items.length > 0 ? items : void 0,
1076
+ label: label || "Section",
1077
+ type: componentType
1078
+ };
1079
+ };
1080
+ const updateSelectedItems = (updater) => {
1081
+ const component = selectedComponentRef.current;
1082
+ if (!component) {
1083
+ return;
1084
+ }
1085
+ const attrs = component.getAttributes?.() || {};
1086
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1087
+ const nextItems = updater(currentItems);
1088
+ component.addAttributes?.({
1089
+ "data-orion-items-json": JSON.stringify(nextItems)
1090
+ });
1091
+ setSelectionSummary(summarizeSelectedComponent(component));
1092
+ };
1043
1093
  const updateHistoryState = (editor) => {
1044
1094
  const next = {
1045
1095
  canRedo: editor.UndoManager.hasRedo(),
@@ -1165,14 +1215,8 @@ function GrapesPageEditor({
1165
1215
  });
1166
1216
  editor.on("component:selected", (component) => {
1167
1217
  const typed = component;
1168
- const attrs = typed?.getAttributes?.() || {};
1169
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1170
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1171
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1172
- setSelectionSummary({
1173
- label: label || "Section",
1174
- type: componentType
1175
- });
1218
+ selectedComponentRef.current = typed;
1219
+ setSelectionSummary(summarizeSelectedComponent(typed));
1176
1220
  setSaveMessage("Selection ready");
1177
1221
  });
1178
1222
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1416,6 +1460,62 @@ function GrapesPageEditor({
1416
1460
  /* @__PURE__ */ jsx("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
1417
1461
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
1418
1462
  ] }),
1463
+ selectionSummary?.items ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1464
+ /* @__PURE__ */ jsx("h2", { children: "Items" }),
1465
+ /* @__PURE__ */ jsx("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1466
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-item-row", children: [
1467
+ /* @__PURE__ */ jsx("span", { children: item.title }),
1468
+ /* @__PURE__ */ jsx(
1469
+ "button",
1470
+ {
1471
+ onClick: () => {
1472
+ updateSelectedItems((items) => {
1473
+ const source = items[item.index];
1474
+ if (!source) {
1475
+ return items;
1476
+ }
1477
+ const copy = [...items];
1478
+ copy.splice(item.index + 1, 0, {
1479
+ ...source,
1480
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1481
+ });
1482
+ return copy;
1483
+ });
1484
+ },
1485
+ type: "button",
1486
+ children: "Duplicate"
1487
+ }
1488
+ ),
1489
+ /* @__PURE__ */ jsx(
1490
+ "button",
1491
+ {
1492
+ onClick: () => {
1493
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1494
+ },
1495
+ type: "button",
1496
+ children: "Remove"
1497
+ }
1498
+ )
1499
+ ] }, `${item.index}-${item.title}`)) }),
1500
+ /* @__PURE__ */ jsx(
1501
+ "button",
1502
+ {
1503
+ className: "orion-builder-v2-wide-action",
1504
+ onClick: () => {
1505
+ updateSelectedItems((items) => [
1506
+ ...items,
1507
+ {
1508
+ description: "Describe this feature.",
1509
+ imageURL: "",
1510
+ title: "New feature"
1511
+ }
1512
+ ]);
1513
+ },
1514
+ type: "button",
1515
+ children: "Add item"
1516
+ }
1517
+ )
1518
+ ] }) : null,
1419
1519
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1420
1520
  /* @__PURE__ */ jsx("h2", { children: "Design" }),
1421
1521
  /* @__PURE__ */ jsx("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
@@ -252,6 +252,52 @@
252
252
  color: #9f1239;
253
253
  }
254
254
 
255
+ .orion-builder-v2-items {
256
+ display: grid;
257
+ gap: 8px;
258
+ }
259
+
260
+ .orion-builder-v2-item-row {
261
+ align-items: center;
262
+ background: #fff8ef;
263
+ border: 1px solid rgba(231, 203, 185, 0.9);
264
+ border-radius: 10px;
265
+ display: grid;
266
+ gap: 6px;
267
+ grid-template-columns: minmax(0, 1fr) auto auto;
268
+ padding: 8px;
269
+ }
270
+
271
+ .orion-builder-v2-item-row span {
272
+ color: var(--builder-v2-ink);
273
+ font-size: 0.8rem;
274
+ font-weight: 900;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ }
279
+
280
+ .orion-builder-v2-item-row button,
281
+ .orion-builder-v2-wide-action {
282
+ background: #ffffff;
283
+ border: 1px solid #e7cbb9;
284
+ border-radius: 999px;
285
+ color: var(--builder-v2-ink);
286
+ cursor: pointer;
287
+ font: inherit;
288
+ font-size: 0.72rem;
289
+ font-weight: 900;
290
+ min-height: 30px;
291
+ padding: 6px 9px;
292
+ }
293
+
294
+ .orion-builder-v2-wide-action {
295
+ background: var(--builder-v2-ink);
296
+ color: #ffffff;
297
+ margin-top: 2px;
298
+ width: 100%;
299
+ }
300
+
255
301
  .orion-builder-v2-runtime {
256
302
  width: 100%;
257
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.6.0-beta.57",
3
+ "version": "0.6.0-beta.58",
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",