@orion-studios/payload-studio 0.6.0-beta.54 → 0.6.0-beta.56

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.
@@ -7,14 +7,14 @@ import {
7
7
  socialMediaConnectionsField,
8
8
  themePreferenceField,
9
9
  withTooltips
10
- } from "../chunk-KHK6RTGC.mjs";
10
+ } from "../chunk-JC3UV74N.mjs";
11
+ import "../chunk-W2UOCJDX.mjs";
11
12
  import {
12
13
  SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
13
14
  SOCIAL_MEDIA_ICON_OPTIONS,
14
15
  SOCIAL_MEDIA_PLATFORMS,
15
16
  SOCIAL_MEDIA_PLATFORM_LABELS
16
17
  } from "../chunk-ZTXJG4K5.mjs";
17
- import "../chunk-W2UOCJDX.mjs";
18
18
  import "../chunk-6BWS3CLP.mjs";
19
19
  export {
20
20
  SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
@@ -679,7 +679,77 @@ var updateJsonListAttribute = ({
679
679
  [listAttr]: JSON.stringify(list)
680
680
  });
681
681
  };
682
- var chooseAsset = (editor, callback) => {
682
+ var addJsonListItem = ({
683
+ listAttr,
684
+ model,
685
+ template
686
+ }) => {
687
+ const attrs = model.getAttributes?.() || {};
688
+ const list = parseJsonArray(attrs[listAttr]);
689
+ list.push(template);
690
+ model.addAttributes?.({
691
+ [listAttr]: JSON.stringify(list)
692
+ });
693
+ };
694
+ var duplicateJsonListItem = ({
695
+ index,
696
+ listAttr,
697
+ model
698
+ }) => {
699
+ const attrs = model.getAttributes?.() || {};
700
+ const list = parseJsonArray(attrs[listAttr]);
701
+ const item = list[index];
702
+ if (!item) {
703
+ return;
704
+ }
705
+ list.splice(index + 1, 0, {
706
+ ...item,
707
+ title: typeof item.title === "string" ? `${item.title} copy` : item.title
708
+ });
709
+ model.addAttributes?.({
710
+ [listAttr]: JSON.stringify(list)
711
+ });
712
+ };
713
+ var removeJsonListItem = ({
714
+ index,
715
+ listAttr,
716
+ model
717
+ }) => {
718
+ const attrs = model.getAttributes?.() || {};
719
+ const list = parseJsonArray(attrs[listAttr]);
720
+ if (!list[index]) {
721
+ return;
722
+ }
723
+ list.splice(index, 1);
724
+ model.addAttributes?.({
725
+ [listAttr]: JSON.stringify(list)
726
+ });
727
+ };
728
+ var parseTemplateAttribute = (value) => {
729
+ const decoded = decodeHtmlAttribute2(value);
730
+ if (!decoded) {
731
+ return {
732
+ description: "Describe this item.",
733
+ imageURL: "",
734
+ title: "New item"
735
+ };
736
+ }
737
+ try {
738
+ const parsed = JSON.parse(decoded);
739
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
740
+ description: "Describe this item.",
741
+ imageURL: "",
742
+ title: "New item"
743
+ };
744
+ } catch {
745
+ return {
746
+ description: "Describe this item.",
747
+ imageURL: "",
748
+ title: "New item"
749
+ };
750
+ }
751
+ };
752
+ var chooseAsset = (editor, currentSrc, callback) => {
683
753
  const assetManager = editor?.AssetManager;
684
754
  if (!assetManager?.open) {
685
755
  const src = window.prompt("Image URL");
@@ -688,6 +758,13 @@ var chooseAsset = (editor, callback) => {
688
758
  }
689
759
  return;
690
760
  }
761
+ if (currentSrc) {
762
+ assetManager.add?.({
763
+ name: `Current image - ${currentSrc.split("/").pop() || currentSrc}`,
764
+ src: currentSrc,
765
+ type: "image"
766
+ });
767
+ }
691
768
  assetManager.open({
692
769
  select(asset) {
693
770
  const src = typeof asset === "string" ? asset : asset && typeof asset === "object" && "get" in asset && typeof asset.get === "function" ? String(asset.get("src") || "") : asset && typeof asset === "object" && "src" in asset ? String(asset.src || "") : "";
@@ -703,12 +780,54 @@ var bindEditablePreview = (view, editor) => {
703
780
  if (!root) {
704
781
  return;
705
782
  }
783
+ root.querySelectorAll("[data-orion-action]").forEach((element) => {
784
+ const action = element.dataset.orionAction || "";
785
+ const listName = element.dataset.orionEditList || "";
786
+ const listIndex = Number(element.dataset.orionEditIndex);
787
+ const listAttr = listName ? propToAttrName(listName) : "";
788
+ element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
789
+ element.addEventListener("click", (event) => {
790
+ event.preventDefault();
791
+ event.stopPropagation();
792
+ editor.select?.(view.model);
793
+ if (!listAttr) {
794
+ return;
795
+ }
796
+ if (action === "add-list-item") {
797
+ addJsonListItem({
798
+ listAttr,
799
+ model: view.model,
800
+ template: parseTemplateAttribute(element.dataset.orionItemTemplate)
801
+ });
802
+ return;
803
+ }
804
+ if (!Number.isInteger(listIndex)) {
805
+ return;
806
+ }
807
+ if (action === "duplicate-list-item") {
808
+ duplicateJsonListItem({
809
+ index: listIndex,
810
+ listAttr,
811
+ model: view.model
812
+ });
813
+ return;
814
+ }
815
+ if (action === "remove-list-item") {
816
+ removeJsonListItem({
817
+ index: listIndex,
818
+ listAttr,
819
+ model: view.model
820
+ });
821
+ }
822
+ });
823
+ });
706
824
  root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
707
825
  const field = element.dataset.orionEditField || "";
708
826
  const listName = element.dataset.orionEditList || "";
709
827
  const listIndex = Number(element.dataset.orionEditIndex);
710
828
  const attrName = listName ? propToAttrName(listName) : propToAttrName(field);
711
829
  const isImage = element.dataset.orionEditKind === "image" || element instanceof HTMLImageElement;
830
+ const placeholder = element.dataset.orionPlaceholder || "";
712
831
  element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
713
832
  element.style.cursor = "text";
714
833
  element.addEventListener("click", (event) => {
@@ -718,7 +837,8 @@ var bindEditablePreview = (view, editor) => {
718
837
  return;
719
838
  }
720
839
  element.style.cursor = "pointer";
721
- chooseAsset(editor, (src) => {
840
+ const currentSrc = element instanceof HTMLImageElement ? element.currentSrc || element.src || "" : element.style.backgroundImage.replace(/^url\(["']?/, "").replace(/["']?\)$/, "");
841
+ chooseAsset(editor, currentSrc, (src) => {
722
842
  if (listName && Number.isInteger(listIndex)) {
723
843
  updateJsonListAttribute({
724
844
  field,
@@ -740,7 +860,8 @@ var bindEditablePreview = (view, editor) => {
740
860
  element.setAttribute("contenteditable", "true");
741
861
  element.setAttribute("spellcheck", "true");
742
862
  const commit = () => {
743
- const value = element.innerText.trim();
863
+ const typedValue = element.innerText.trim();
864
+ const value = placeholder && typedValue === placeholder ? "" : typedValue;
744
865
  if (listName && Number.isInteger(listIndex)) {
745
866
  updateJsonListAttribute({
746
867
  field,
@@ -858,10 +979,58 @@ var getRelationID = (value) => {
858
979
  const id = value.id;
859
980
  return typeof id === "number" || typeof id === "string" ? id : null;
860
981
  };
982
+ var normalizeAssetSrc = (value) => {
983
+ const trimmed = value.trim();
984
+ if (!trimmed) {
985
+ return "";
986
+ }
987
+ if (/^(https?:)?\/\//i.test(trimmed) || trimmed.startsWith("/")) {
988
+ return trimmed;
989
+ }
990
+ return `/${trimmed}`;
991
+ };
992
+ var mediaDocImageCandidates = (doc) => {
993
+ const candidates = [];
994
+ if (doc.sizes && typeof doc.sizes === "object") {
995
+ Object.values(doc.sizes).forEach((size) => {
996
+ if (size?.url) {
997
+ candidates.push({ src: normalizeAssetSrc(size.url), width: size.width });
998
+ } else if (size?.filename) {
999
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(size.filename)}`, width: size.width });
1000
+ }
1001
+ });
1002
+ }
1003
+ if (typeof doc.thumbnailURL === "string" && doc.thumbnailURL.length > 0) {
1004
+ candidates.push({ src: normalizeAssetSrc(doc.thumbnailURL), width: 360 });
1005
+ }
1006
+ if (typeof doc.url === "string" && doc.url.length > 0) {
1007
+ candidates.push({ src: normalizeAssetSrc(doc.url), width: doc.width });
1008
+ }
1009
+ if (typeof doc.filename === "string" && doc.filename.length > 0) {
1010
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(doc.filename)}`, width: doc.width });
1011
+ }
1012
+ return candidates.filter((candidate, index, all) => candidate.src && all.findIndex((item) => item.src === candidate.src) === index);
1013
+ };
1014
+ var pickMediaAssetSrc = (doc) => {
1015
+ const candidates = mediaDocImageCandidates(doc);
1016
+ if (candidates.length === 0) {
1017
+ return "";
1018
+ }
1019
+ const absolute = candidates.find((candidate) => /^(https?:)?\/\//i.test(candidate.src));
1020
+ if (absolute) {
1021
+ return absolute.src;
1022
+ }
1023
+ const fullSize = candidates.find((candidate) => candidate.src === doc.url);
1024
+ if (fullSize) {
1025
+ return fullSize.src;
1026
+ }
1027
+ const widest = [...candidates].sort((left, right) => (right.width || 0) - (left.width || 0))[0];
1028
+ return widest?.src || candidates[0]?.src || "";
1029
+ };
861
1030
  var mediaDocToAsset = (doc) => {
862
1031
  const id = getRelationID(doc);
863
1032
  const filename = typeof doc.filename === "string" ? doc.filename : "";
864
- const src = typeof doc.url === "string" && doc.url.length > 0 ? doc.url : filename ? `/api/media/file/${encodeURIComponent(filename)}` : "";
1033
+ const src = pickMediaAssetSrc(doc);
865
1034
  if (id === null || !src) {
866
1035
  return null;
867
1036
  }
@@ -873,6 +1042,26 @@ var mediaDocToAsset = (doc) => {
873
1042
  type: "image"
874
1043
  };
875
1044
  };
1045
+ var imageCanLoad = (src) => new Promise((resolve) => {
1046
+ if (!src) {
1047
+ resolve(false);
1048
+ return;
1049
+ }
1050
+ const image = new Image();
1051
+ image.onload = () => resolve(true);
1052
+ image.onerror = () => resolve(false);
1053
+ image.src = src;
1054
+ });
1055
+ var pruneBrokenImageAssets = async (editor) => {
1056
+ const assetManager = editor.AssetManager;
1057
+ const existingAssets = assetManager.getAll?.() || [];
1058
+ for (const asset of existingAssets) {
1059
+ const src = typeof asset.get === "function" ? String(asset.get("src") || "") : "";
1060
+ if (src && !await imageCanLoad(src)) {
1061
+ assetManager.remove?.(asset);
1062
+ }
1063
+ }
1064
+ };
876
1065
  var extractUploadedMedia = (value) => {
877
1066
  const candidate = value && typeof value === "object" && "doc" in value ? value.doc : value;
878
1067
  if (!candidate || typeof candidate !== "object") {
@@ -887,7 +1076,10 @@ var extractUploadedMedia = (value) => {
887
1076
  alt: typeof typed.alt === "string" ? typed.alt : "",
888
1077
  filename: typeof typed.filename === "string" ? typed.filename : "",
889
1078
  id,
890
- url: typeof typed.url === "string" ? typed.url : ""
1079
+ sizes: typed.sizes && typeof typed.sizes === "object" ? typed.sizes : void 0,
1080
+ thumbnailURL: typeof typed.thumbnailURL === "string" ? typed.thumbnailURL : "",
1081
+ url: typeof typed.url === "string" ? typed.url : "",
1082
+ width: typeof typed.width === "number" ? typed.width : void 0
891
1083
  };
892
1084
  };
893
1085
  var loadPayloadMediaAssets = async (editor) => {
@@ -899,8 +1091,17 @@ var loadPayloadMediaAssets = async (editor) => {
899
1091
  return;
900
1092
  }
901
1093
  const json = await response.json();
902
- const assets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
903
- editor.AssetManager.add(assets);
1094
+ const candidateAssets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
1095
+ const assets = [];
1096
+ for (const asset of candidateAssets) {
1097
+ if (await imageCanLoad(asset.src)) {
1098
+ assets.push(asset);
1099
+ }
1100
+ }
1101
+ if (assets.length > 0) {
1102
+ editor.AssetManager.add(assets);
1103
+ }
1104
+ await pruneBrokenImageAssets(editor);
904
1105
  };
905
1106
  var uploadPayloadMediaAssets = async (editor, files) => {
906
1107
  const fileArray = Array.from(files);
@@ -950,6 +1151,7 @@ function GrapesPageEditor({
950
1151
  const [selectedDevice, setSelectedDevice] = (0, import_react.useState)("desktop");
951
1152
  const [saving, setSaving] = (0, import_react.useState)(null);
952
1153
  const [saveMessage, setSaveMessage] = (0, import_react.useState)("");
1154
+ const [selectionSummary, setSelectionSummary] = (0, import_react.useState)(null);
953
1155
  const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
954
1156
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
955
1157
  const pageTree = initialData?.meta?.pageTree || [];
@@ -1028,6 +1230,7 @@ function GrapesPageEditor({
1028
1230
  },
1029
1231
  storageManager: false,
1030
1232
  styleManager: {
1233
+ appendTo: "#orion-builder-v2-styles",
1031
1234
  sectors: [
1032
1235
  {
1033
1236
  name: "Layout",
@@ -1075,7 +1278,16 @@ function GrapesPageEditor({
1075
1278
  }
1076
1279
  }, autosaveIntervalMs);
1077
1280
  });
1078
- editor.on("component:selected", () => {
1281
+ editor.on("component:selected", (component) => {
1282
+ const typed = component;
1283
+ const attrs = typed?.getAttributes?.() || {};
1284
+ const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1285
+ const rawName = String(typed?.get?.("name") || componentType || "Section");
1286
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1287
+ setSelectionSummary({
1288
+ label: label || "Section",
1289
+ type: componentType
1290
+ });
1079
1291
  setSaveMessage("Selection ready");
1080
1292
  });
1081
1293
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1293,11 +1505,6 @@ function GrapesPageEditor({
1293
1505
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
1294
1506
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-blocks" })
1295
1507
  ] }),
1296
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1297
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Inspector" }),
1298
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
1299
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1300
- ] }),
1301
1508
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1302
1509
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Validation" }),
1303
1510
  /* @__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: [
@@ -1309,6 +1516,9 @@ function GrapesPageEditor({
1309
1516
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "orion-builder-v2-main", children: [
1310
1517
  loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
1311
1518
  error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-error", children: error }) : null,
1519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-canvas", ref: containerRef })
1520
+ ] }),
1521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "orion-builder-v2-inspector", "aria-label": "Selected section settings", children: [
1312
1522
  saveMessage || lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-save-status", children: [
1313
1523
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: saveMessage || "Ready" }),
1314
1524
  lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
@@ -1316,7 +1526,16 @@ function GrapesPageEditor({
1316
1526
  lastSavedAt
1317
1527
  ] }) : null
1318
1528
  ] }) : null,
1319
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-canvas", ref: containerRef })
1529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: selectionSummary ? selectionSummary.label : "No section selected" }),
1531
+ /* @__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." }),
1532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1533
+ ] }),
1534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Design" }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-styles" })
1538
+ ] })
1320
1539
  ] })
1321
1540
  ] });
1322
1541
  }
@@ -555,7 +555,77 @@ var updateJsonListAttribute = ({
555
555
  [listAttr]: JSON.stringify(list)
556
556
  });
557
557
  };
558
- var chooseAsset = (editor, callback) => {
558
+ var addJsonListItem = ({
559
+ listAttr,
560
+ model,
561
+ template
562
+ }) => {
563
+ const attrs = model.getAttributes?.() || {};
564
+ const list = parseJsonArray(attrs[listAttr]);
565
+ list.push(template);
566
+ model.addAttributes?.({
567
+ [listAttr]: JSON.stringify(list)
568
+ });
569
+ };
570
+ var duplicateJsonListItem = ({
571
+ index,
572
+ listAttr,
573
+ model
574
+ }) => {
575
+ const attrs = model.getAttributes?.() || {};
576
+ const list = parseJsonArray(attrs[listAttr]);
577
+ const item = list[index];
578
+ if (!item) {
579
+ return;
580
+ }
581
+ list.splice(index + 1, 0, {
582
+ ...item,
583
+ title: typeof item.title === "string" ? `${item.title} copy` : item.title
584
+ });
585
+ model.addAttributes?.({
586
+ [listAttr]: JSON.stringify(list)
587
+ });
588
+ };
589
+ var removeJsonListItem = ({
590
+ index,
591
+ listAttr,
592
+ model
593
+ }) => {
594
+ const attrs = model.getAttributes?.() || {};
595
+ const list = parseJsonArray(attrs[listAttr]);
596
+ if (!list[index]) {
597
+ return;
598
+ }
599
+ list.splice(index, 1);
600
+ model.addAttributes?.({
601
+ [listAttr]: JSON.stringify(list)
602
+ });
603
+ };
604
+ var parseTemplateAttribute = (value) => {
605
+ const decoded = decodeHtmlAttribute2(value);
606
+ if (!decoded) {
607
+ return {
608
+ description: "Describe this item.",
609
+ imageURL: "",
610
+ title: "New item"
611
+ };
612
+ }
613
+ try {
614
+ const parsed = JSON.parse(decoded);
615
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
616
+ description: "Describe this item.",
617
+ imageURL: "",
618
+ title: "New item"
619
+ };
620
+ } catch {
621
+ return {
622
+ description: "Describe this item.",
623
+ imageURL: "",
624
+ title: "New item"
625
+ };
626
+ }
627
+ };
628
+ var chooseAsset = (editor, currentSrc, callback) => {
559
629
  const assetManager = editor?.AssetManager;
560
630
  if (!assetManager?.open) {
561
631
  const src = window.prompt("Image URL");
@@ -564,6 +634,13 @@ var chooseAsset = (editor, callback) => {
564
634
  }
565
635
  return;
566
636
  }
637
+ if (currentSrc) {
638
+ assetManager.add?.({
639
+ name: `Current image - ${currentSrc.split("/").pop() || currentSrc}`,
640
+ src: currentSrc,
641
+ type: "image"
642
+ });
643
+ }
567
644
  assetManager.open({
568
645
  select(asset) {
569
646
  const src = typeof asset === "string" ? asset : asset && typeof asset === "object" && "get" in asset && typeof asset.get === "function" ? String(asset.get("src") || "") : asset && typeof asset === "object" && "src" in asset ? String(asset.src || "") : "";
@@ -579,12 +656,54 @@ var bindEditablePreview = (view, editor) => {
579
656
  if (!root) {
580
657
  return;
581
658
  }
659
+ root.querySelectorAll("[data-orion-action]").forEach((element) => {
660
+ const action = element.dataset.orionAction || "";
661
+ const listName = element.dataset.orionEditList || "";
662
+ const listIndex = Number(element.dataset.orionEditIndex);
663
+ const listAttr = listName ? propToAttrName(listName) : "";
664
+ element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
665
+ element.addEventListener("click", (event) => {
666
+ event.preventDefault();
667
+ event.stopPropagation();
668
+ editor.select?.(view.model);
669
+ if (!listAttr) {
670
+ return;
671
+ }
672
+ if (action === "add-list-item") {
673
+ addJsonListItem({
674
+ listAttr,
675
+ model: view.model,
676
+ template: parseTemplateAttribute(element.dataset.orionItemTemplate)
677
+ });
678
+ return;
679
+ }
680
+ if (!Number.isInteger(listIndex)) {
681
+ return;
682
+ }
683
+ if (action === "duplicate-list-item") {
684
+ duplicateJsonListItem({
685
+ index: listIndex,
686
+ listAttr,
687
+ model: view.model
688
+ });
689
+ return;
690
+ }
691
+ if (action === "remove-list-item") {
692
+ removeJsonListItem({
693
+ index: listIndex,
694
+ listAttr,
695
+ model: view.model
696
+ });
697
+ }
698
+ });
699
+ });
582
700
  root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
583
701
  const field = element.dataset.orionEditField || "";
584
702
  const listName = element.dataset.orionEditList || "";
585
703
  const listIndex = Number(element.dataset.orionEditIndex);
586
704
  const attrName = listName ? propToAttrName(listName) : propToAttrName(field);
587
705
  const isImage = element.dataset.orionEditKind === "image" || element instanceof HTMLImageElement;
706
+ const placeholder = element.dataset.orionPlaceholder || "";
588
707
  element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
589
708
  element.style.cursor = "text";
590
709
  element.addEventListener("click", (event) => {
@@ -594,7 +713,8 @@ var bindEditablePreview = (view, editor) => {
594
713
  return;
595
714
  }
596
715
  element.style.cursor = "pointer";
597
- chooseAsset(editor, (src) => {
716
+ const currentSrc = element instanceof HTMLImageElement ? element.currentSrc || element.src || "" : element.style.backgroundImage.replace(/^url\(["']?/, "").replace(/["']?\)$/, "");
717
+ chooseAsset(editor, currentSrc, (src) => {
598
718
  if (listName && Number.isInteger(listIndex)) {
599
719
  updateJsonListAttribute({
600
720
  field,
@@ -616,7 +736,8 @@ var bindEditablePreview = (view, editor) => {
616
736
  element.setAttribute("contenteditable", "true");
617
737
  element.setAttribute("spellcheck", "true");
618
738
  const commit = () => {
619
- const value = element.innerText.trim();
739
+ const typedValue = element.innerText.trim();
740
+ const value = placeholder && typedValue === placeholder ? "" : typedValue;
620
741
  if (listName && Number.isInteger(listIndex)) {
621
742
  updateJsonListAttribute({
622
743
  field,
@@ -734,10 +855,58 @@ var getRelationID = (value) => {
734
855
  const id = value.id;
735
856
  return typeof id === "number" || typeof id === "string" ? id : null;
736
857
  };
858
+ var normalizeAssetSrc = (value) => {
859
+ const trimmed = value.trim();
860
+ if (!trimmed) {
861
+ return "";
862
+ }
863
+ if (/^(https?:)?\/\//i.test(trimmed) || trimmed.startsWith("/")) {
864
+ return trimmed;
865
+ }
866
+ return `/${trimmed}`;
867
+ };
868
+ var mediaDocImageCandidates = (doc) => {
869
+ const candidates = [];
870
+ if (doc.sizes && typeof doc.sizes === "object") {
871
+ Object.values(doc.sizes).forEach((size) => {
872
+ if (size?.url) {
873
+ candidates.push({ src: normalizeAssetSrc(size.url), width: size.width });
874
+ } else if (size?.filename) {
875
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(size.filename)}`, width: size.width });
876
+ }
877
+ });
878
+ }
879
+ if (typeof doc.thumbnailURL === "string" && doc.thumbnailURL.length > 0) {
880
+ candidates.push({ src: normalizeAssetSrc(doc.thumbnailURL), width: 360 });
881
+ }
882
+ if (typeof doc.url === "string" && doc.url.length > 0) {
883
+ candidates.push({ src: normalizeAssetSrc(doc.url), width: doc.width });
884
+ }
885
+ if (typeof doc.filename === "string" && doc.filename.length > 0) {
886
+ candidates.push({ src: `/api/media/file/${encodeURIComponent(doc.filename)}`, width: doc.width });
887
+ }
888
+ return candidates.filter((candidate, index, all) => candidate.src && all.findIndex((item) => item.src === candidate.src) === index);
889
+ };
890
+ var pickMediaAssetSrc = (doc) => {
891
+ const candidates = mediaDocImageCandidates(doc);
892
+ if (candidates.length === 0) {
893
+ return "";
894
+ }
895
+ const absolute = candidates.find((candidate) => /^(https?:)?\/\//i.test(candidate.src));
896
+ if (absolute) {
897
+ return absolute.src;
898
+ }
899
+ const fullSize = candidates.find((candidate) => candidate.src === doc.url);
900
+ if (fullSize) {
901
+ return fullSize.src;
902
+ }
903
+ const widest = [...candidates].sort((left, right) => (right.width || 0) - (left.width || 0))[0];
904
+ return widest?.src || candidates[0]?.src || "";
905
+ };
737
906
  var mediaDocToAsset = (doc) => {
738
907
  const id = getRelationID(doc);
739
908
  const filename = typeof doc.filename === "string" ? doc.filename : "";
740
- const src = typeof doc.url === "string" && doc.url.length > 0 ? doc.url : filename ? `/api/media/file/${encodeURIComponent(filename)}` : "";
909
+ const src = pickMediaAssetSrc(doc);
741
910
  if (id === null || !src) {
742
911
  return null;
743
912
  }
@@ -749,6 +918,26 @@ var mediaDocToAsset = (doc) => {
749
918
  type: "image"
750
919
  };
751
920
  };
921
+ var imageCanLoad = (src) => new Promise((resolve) => {
922
+ if (!src) {
923
+ resolve(false);
924
+ return;
925
+ }
926
+ const image = new Image();
927
+ image.onload = () => resolve(true);
928
+ image.onerror = () => resolve(false);
929
+ image.src = src;
930
+ });
931
+ var pruneBrokenImageAssets = async (editor) => {
932
+ const assetManager = editor.AssetManager;
933
+ const existingAssets = assetManager.getAll?.() || [];
934
+ for (const asset of existingAssets) {
935
+ const src = typeof asset.get === "function" ? String(asset.get("src") || "") : "";
936
+ if (src && !await imageCanLoad(src)) {
937
+ assetManager.remove?.(asset);
938
+ }
939
+ }
940
+ };
752
941
  var extractUploadedMedia = (value) => {
753
942
  const candidate = value && typeof value === "object" && "doc" in value ? value.doc : value;
754
943
  if (!candidate || typeof candidate !== "object") {
@@ -763,7 +952,10 @@ var extractUploadedMedia = (value) => {
763
952
  alt: typeof typed.alt === "string" ? typed.alt : "",
764
953
  filename: typeof typed.filename === "string" ? typed.filename : "",
765
954
  id,
766
- url: typeof typed.url === "string" ? typed.url : ""
955
+ sizes: typed.sizes && typeof typed.sizes === "object" ? typed.sizes : void 0,
956
+ thumbnailURL: typeof typed.thumbnailURL === "string" ? typed.thumbnailURL : "",
957
+ url: typeof typed.url === "string" ? typed.url : "",
958
+ width: typeof typed.width === "number" ? typed.width : void 0
767
959
  };
768
960
  };
769
961
  var loadPayloadMediaAssets = async (editor) => {
@@ -775,8 +967,17 @@ var loadPayloadMediaAssets = async (editor) => {
775
967
  return;
776
968
  }
777
969
  const json = await response.json();
778
- const assets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
779
- editor.AssetManager.add(assets);
970
+ const candidateAssets = (Array.isArray(json.docs) ? json.docs : []).map((doc) => mediaDocToAsset(doc)).filter((asset) => asset !== null);
971
+ const assets = [];
972
+ for (const asset of candidateAssets) {
973
+ if (await imageCanLoad(asset.src)) {
974
+ assets.push(asset);
975
+ }
976
+ }
977
+ if (assets.length > 0) {
978
+ editor.AssetManager.add(assets);
979
+ }
980
+ await pruneBrokenImageAssets(editor);
780
981
  };
781
982
  var uploadPayloadMediaAssets = async (editor, files) => {
782
983
  const fileArray = Array.from(files);
@@ -826,6 +1027,7 @@ function GrapesPageEditor({
826
1027
  const [selectedDevice, setSelectedDevice] = useState("desktop");
827
1028
  const [saving, setSaving] = useState(null);
828
1029
  const [saveMessage, setSaveMessage] = useState("");
1030
+ const [selectionSummary, setSelectionSummary] = useState(null);
829
1031
  const [validationIssues, setValidationIssues] = useState([]);
830
1032
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
831
1033
  const pageTree = initialData?.meta?.pageTree || [];
@@ -904,6 +1106,7 @@ function GrapesPageEditor({
904
1106
  },
905
1107
  storageManager: false,
906
1108
  styleManager: {
1109
+ appendTo: "#orion-builder-v2-styles",
907
1110
  sectors: [
908
1111
  {
909
1112
  name: "Layout",
@@ -951,7 +1154,16 @@ function GrapesPageEditor({
951
1154
  }
952
1155
  }, autosaveIntervalMs);
953
1156
  });
954
- editor.on("component:selected", () => {
1157
+ editor.on("component:selected", (component) => {
1158
+ const typed = component;
1159
+ const attrs = typed?.getAttributes?.() || {};
1160
+ const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1161
+ const rawName = String(typed?.get?.("name") || componentType || "Section");
1162
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1163
+ setSelectionSummary({
1164
+ label: label || "Section",
1165
+ type: componentType
1166
+ });
955
1167
  setSaveMessage("Selection ready");
956
1168
  });
957
1169
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1169,11 +1381,6 @@ function GrapesPageEditor({
1169
1381
  /* @__PURE__ */ jsx("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
1170
1382
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-blocks" })
1171
1383
  ] }),
1172
- /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1173
- /* @__PURE__ */ jsx("h2", { children: "Inspector" }),
1174
- /* @__PURE__ */ jsx("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
1175
- /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
1176
- ] }),
1177
1384
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1178
1385
  /* @__PURE__ */ jsx("h2", { children: "Validation" }),
1179
1386
  /* @__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: [
@@ -1185,6 +1392,9 @@ function GrapesPageEditor({
1185
1392
  /* @__PURE__ */ jsxs("main", { className: "orion-builder-v2-main", children: [
1186
1393
  loading ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
1187
1394
  error ? /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-error", children: error }) : null,
1395
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-canvas", ref: containerRef })
1396
+ ] }),
1397
+ /* @__PURE__ */ jsxs("aside", { className: "orion-builder-v2-inspector", "aria-label": "Selected section settings", children: [
1188
1398
  saveMessage || lastSavedAt ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-save-status", children: [
1189
1399
  /* @__PURE__ */ jsx("strong", { children: saveMessage || "Ready" }),
1190
1400
  lastSavedAt ? /* @__PURE__ */ jsxs("span", { children: [
@@ -1192,7 +1402,16 @@ function GrapesPageEditor({
1192
1402
  lastSavedAt
1193
1403
  ] }) : null
1194
1404
  ] }) : null,
1195
- /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-canvas", ref: containerRef })
1405
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1406
+ /* @__PURE__ */ jsx("h2", { children: selectionSummary ? selectionSummary.label : "No section selected" }),
1407
+ /* @__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." }),
1408
+ /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
1409
+ ] }),
1410
+ /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1411
+ /* @__PURE__ */ jsx("h2", { children: "Design" }),
1412
+ /* @__PURE__ */ jsx("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
1413
+ /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-styles" })
1414
+ ] })
1196
1415
  ] })
1197
1416
  ] });
1198
1417
  }
@@ -12,7 +12,7 @@
12
12
  color: var(--builder-v2-ink);
13
13
  display: grid;
14
14
  font-family: var(--font-xo-sans, var(--font-body, ui-sans-serif, system-ui, sans-serif));
15
- grid-template-columns: minmax(292px, 336px) 1fr;
15
+ grid-template-columns: minmax(292px, 336px) minmax(0, 1fr) minmax(300px, 360px);
16
16
  grid-template-rows: auto minmax(0, 1fr);
17
17
  height: 100dvh;
18
18
  min-height: 640px;
@@ -94,9 +94,9 @@
94
94
  color: #ffffff;
95
95
  }
96
96
 
97
- .orion-builder-v2-sidebar {
97
+ .orion-builder-v2-sidebar,
98
+ .orion-builder-v2-inspector {
98
99
  background: #fbf7f1;
99
- border-right: 1px solid var(--builder-v2-border);
100
100
  display: grid;
101
101
  gap: 14px;
102
102
  grid-auto-rows: max-content;
@@ -104,6 +104,14 @@
104
104
  padding: 14px;
105
105
  }
106
106
 
107
+ .orion-builder-v2-sidebar {
108
+ border-right: 1px solid var(--builder-v2-border);
109
+ }
110
+
111
+ .orion-builder-v2-inspector {
112
+ border-left: 1px solid var(--builder-v2-border);
113
+ }
114
+
107
115
  .orion-builder-v2-panel {
108
116
  background: var(--builder-v2-panel);
109
117
  border: 1px solid var(--builder-v2-border);
@@ -201,7 +209,8 @@
201
209
  display: grid;
202
210
  gap: 2px;
203
211
  left: auto;
204
- right: 16px;
212
+ position: static;
213
+ right: auto;
205
214
  }
206
215
 
207
216
  .orion-builder-v2-save-status span {
@@ -330,6 +339,77 @@
330
339
  font-weight: 800;
331
340
  }
332
341
 
342
+ .orion-builder-v2-editor .gjs-traits-label {
343
+ display: none;
344
+ }
345
+
346
+ .orion-builder-v2-editor .gjs-trt-traits {
347
+ display: grid;
348
+ gap: 10px;
349
+ }
350
+
351
+ .orion-builder-v2-editor .gjs-trt-trait {
352
+ display: grid;
353
+ gap: 6px;
354
+ }
355
+
356
+ .orion-builder-v2-editor .gjs-sm-sectors {
357
+ display: grid;
358
+ gap: 10px;
359
+ }
360
+
361
+ .orion-builder-v2-editor .gjs-sm-sector {
362
+ border: 1px solid var(--builder-v2-border);
363
+ border-radius: 12px;
364
+ overflow: hidden;
365
+ }
366
+
367
+ .orion-builder-v2-editor .gjs-sm-properties {
368
+ display: grid;
369
+ gap: 8px;
370
+ padding: 10px;
371
+ }
372
+
373
+ .orion-builder-v2-editor .gjs-sm-property {
374
+ float: none;
375
+ margin: 0;
376
+ width: 100%;
377
+ }
378
+
379
+ .orion-builder-v2-editor .gjs-field input,
380
+ .orion-builder-v2-editor .gjs-field select,
381
+ .orion-builder-v2-editor .gjs-field textarea {
382
+ min-height: 36px;
383
+ }
384
+
385
+ .orion-builder-v2-editor .gjs-field textarea {
386
+ min-height: 96px;
387
+ }
388
+
389
+ .orion-builder-v2-editor [data-orion-placeholder] {
390
+ outline: 1px dashed rgba(199, 100, 61, 0.45);
391
+ outline-offset: 4px;
392
+ }
393
+
394
+ .orion-builder-v2-editor [data-orion-action] {
395
+ background: #fffaf4;
396
+ border: 1px solid #e7cbb9;
397
+ border-radius: 999px;
398
+ color: var(--builder-v2-ink);
399
+ cursor: pointer;
400
+ font: inherit;
401
+ font-size: 0.76rem;
402
+ font-weight: 900;
403
+ min-height: 32px;
404
+ padding: 6px 10px;
405
+ }
406
+
407
+ .orion-builder-v2-editor [data-orion-action]:hover {
408
+ background: var(--builder-v2-ink);
409
+ border-color: var(--builder-v2-ink);
410
+ color: #ffffff;
411
+ }
412
+
333
413
  .orion-builder-v2-html-chunk {
334
414
  display: contents;
335
415
  }
@@ -939,7 +1019,7 @@
939
1019
  @media (max-width: 800px) {
940
1020
  .orion-builder-v2-editor {
941
1021
  grid-template-columns: 1fr;
942
- grid-template-rows: auto auto 1fr;
1022
+ grid-template-rows: auto auto 1fr auto;
943
1023
  }
944
1024
 
945
1025
  .orion-builder-v2-topbar {
@@ -947,7 +1027,8 @@
947
1027
  flex-direction: column;
948
1028
  }
949
1029
 
950
- .orion-builder-v2-sidebar {
1030
+ .orion-builder-v2-sidebar,
1031
+ .orion-builder-v2-inspector {
951
1032
  max-height: 260px;
952
1033
  }
953
1034
 
package/dist/index.mjs CHANGED
@@ -1,25 +1,25 @@
1
1
  import {
2
2
  admin_exports
3
- } from "./chunk-KHK6RTGC.mjs";
3
+ } from "./chunk-JC3UV74N.mjs";
4
+ import {
5
+ admin_app_exports
6
+ } from "./chunk-RKTIFEUY.mjs";
7
+ import "./chunk-W2UOCJDX.mjs";
8
+ import {
9
+ blocks_exports
10
+ } from "./chunk-JQAHXYAM.mjs";
4
11
  import {
5
12
  nextjs_exports
6
13
  } from "./chunk-ZADL33R6.mjs";
7
14
  import "./chunk-ZTXJG4K5.mjs";
8
15
  import {
9
16
  studio_pages_exports
10
- } from "./chunk-7HME6R2V.mjs";
17
+ } from "./chunk-NGLIA2OE.mjs";
18
+ import "./chunk-OQSEJXC4.mjs";
11
19
  import "./chunk-7ZMXZRBP.mjs";
12
20
  import {
13
21
  studio_exports
14
22
  } from "./chunk-ADIIWIYL.mjs";
15
- import {
16
- blocks_exports
17
- } from "./chunk-JQAHXYAM.mjs";
18
- import "./chunk-OQSEJXC4.mjs";
19
- import {
20
- admin_app_exports
21
- } from "./chunk-RKTIFEUY.mjs";
22
- import "./chunk-W2UOCJDX.mjs";
23
23
  import "./chunk-6BWS3CLP.mjs";
24
24
  export {
25
25
  admin_exports as admin,
@@ -7,7 +7,8 @@ import {
7
7
  pageStudioModuleManifest,
8
8
  resolveBuilderThemeTokens,
9
9
  toEditorInitialDoc
10
- } from "../chunk-7HME6R2V.mjs";
10
+ } from "../chunk-NGLIA2OE.mjs";
11
+ import "../chunk-OQSEJXC4.mjs";
11
12
  import {
12
13
  createDefaultStudioDocument,
13
14
  defaultBuilderThemeTokens,
@@ -15,7 +16,6 @@ import {
15
16
  studioDocumentToLayout
16
17
  } from "../chunk-7ZMXZRBP.mjs";
17
18
  import "../chunk-ADIIWIYL.mjs";
18
- import "../chunk-OQSEJXC4.mjs";
19
19
  import "../chunk-6BWS3CLP.mjs";
20
20
  export {
21
21
  createDefaultStudioDocument,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.6.0-beta.54",
3
+ "version": "0.6.0-beta.56",
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",
@@ -1,12 +1,12 @@
1
+ import {
2
+ adminNavIcons
3
+ } from "./chunk-W2UOCJDX.mjs";
1
4
  import {
2
5
  SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
3
6
  SOCIAL_MEDIA_ICON_OPTIONS,
4
7
  SOCIAL_MEDIA_PLATFORMS,
5
8
  SOCIAL_MEDIA_PLATFORM_LABELS
6
9
  } from "./chunk-ZTXJG4K5.mjs";
7
- import {
8
- adminNavIcons
9
- } from "./chunk-W2UOCJDX.mjs";
10
10
  import {
11
11
  __export,
12
12
  __require
@@ -1,3 +1,6 @@
1
+ import {
2
+ sectionStyleDefaults
3
+ } from "./chunk-OQSEJXC4.mjs";
1
4
  import {
2
5
  createDefaultStudioDocument,
3
6
  defaultBuilderThemeTokens,
@@ -11,9 +14,6 @@ import {
11
14
  createEmptyStudioDocument,
12
15
  validateStudioDocument
13
16
  } from "./chunk-ADIIWIYL.mjs";
14
- import {
15
- sectionStyleDefaults
16
- } from "./chunk-OQSEJXC4.mjs";
17
17
  import {
18
18
  __export
19
19
  } from "./chunk-6BWS3CLP.mjs";