@accelerated-agency/visual-editor 0.6.2 → 0.7.1
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.
- package/dist/images/cro-sections/cro-cta-block.png +0 -0
- package/dist/images/cro-sections/cro-exit-intent-bar.png +0 -0
- package/dist/images/cro-sections/cro-feature-grid.png +0 -0
- package/dist/images/cro-sections/cro-hero.png +0 -0
- package/dist/images/cro-sections/cro-pricing-table.png +0 -0
- package/dist/images/cro-sections/cro-social-proof.png +0 -0
- package/dist/images/cro-sections/cro-trust-badges.png +0 -0
- package/dist/images/cro-sections/cro-urgency-banner.png +0 -0
- package/dist/index.js +256 -4
- package/dist/vite.cjs +25049 -5756
- package/dist/vite.js +25049 -5756
- package/package.json +8 -4
- package/src/images/cro-sections/cro-cta-block.png +0 -0
- package/src/images/cro-sections/cro-exit-intent-bar.png +0 -0
- package/src/images/cro-sections/cro-feature-grid.png +0 -0
- package/src/images/cro-sections/cro-hero.png +0 -0
- package/src/images/cro-sections/cro-pricing-table.png +0 -0
- package/src/images/cro-sections/cro-social-proof.png +0 -0
- package/src/images/cro-sections/cro-trust-badges.png +0 -0
- package/src/images/cro-sections/cro-urgency-banner.png +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -4801,7 +4801,110 @@ function PlatformVisualEditor({
|
|
|
4801
4801
|
/* @__PURE__ */ jsx("div", { className: editorClassName, children: /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(EditorShell, { initialExperiment: experiment, embeddedMode: true, proxyBaseUrl }) }) })
|
|
4802
4802
|
] });
|
|
4803
4803
|
}
|
|
4804
|
+
|
|
4805
|
+
// src/lib/uploadImageMessage.ts
|
|
4806
|
+
var VISUAL_EDITOR_MAX_UPLOAD_BYTES = 1e7;
|
|
4807
|
+
var ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
4808
|
+
"image/jpeg",
|
|
4809
|
+
"image/png",
|
|
4810
|
+
"image/gif",
|
|
4811
|
+
"image/webp"
|
|
4812
|
+
]);
|
|
4813
|
+
var EXT_TO_TYPE = {
|
|
4814
|
+
jpg: "image/jpeg",
|
|
4815
|
+
jpeg: "image/jpeg",
|
|
4816
|
+
png: "image/png",
|
|
4817
|
+
gif: "image/gif",
|
|
4818
|
+
webp: "image/webp"
|
|
4819
|
+
};
|
|
4820
|
+
function uploadImageErrorMessage(err, fallback = "The upload was refused") {
|
|
4821
|
+
if (err instanceof Error && err.message) return err.message;
|
|
4822
|
+
if (typeof err === "string" && err.trim()) return err;
|
|
4823
|
+
return fallback;
|
|
4824
|
+
}
|
|
4825
|
+
function fileFromUploadImagePayload(payload) {
|
|
4826
|
+
if (!payload || typeof payload !== "object") {
|
|
4827
|
+
throw new Error("No image was sent");
|
|
4828
|
+
}
|
|
4829
|
+
const existing = asBlob(payload.file);
|
|
4830
|
+
const name = sanitizeName(payload.name, existing instanceof File ? existing.name : "");
|
|
4831
|
+
const type = normalizeType(
|
|
4832
|
+
typeof payload.type === "string" ? payload.type : existing?.type,
|
|
4833
|
+
name
|
|
4834
|
+
);
|
|
4835
|
+
const bytes = existing ? void 0 : bytesFromUnknown(payload.bytes !== void 0 ? payload.bytes : payload.data);
|
|
4836
|
+
if (!existing && !bytes) {
|
|
4837
|
+
throw new Error("No image bytes were sent");
|
|
4838
|
+
}
|
|
4839
|
+
const size = existing?.size ?? bytes.byteLength;
|
|
4840
|
+
if (typeof payload.size === "number" && payload.size > VISUAL_EDITOR_MAX_UPLOAD_BYTES) {
|
|
4841
|
+
throw new Error("Image is larger than 10MB");
|
|
4842
|
+
}
|
|
4843
|
+
if (size > VISUAL_EDITOR_MAX_UPLOAD_BYTES) {
|
|
4844
|
+
throw new Error("Image is larger than 10MB");
|
|
4845
|
+
}
|
|
4846
|
+
if (!ALLOWED_TYPES.has(type)) {
|
|
4847
|
+
throw new Error("Use a JPEG, PNG, GIF, or WebP image");
|
|
4848
|
+
}
|
|
4849
|
+
if (existing instanceof File) {
|
|
4850
|
+
if (existing.type && !ALLOWED_TYPES.has(normalizeType(existing.type, existing.name))) {
|
|
4851
|
+
throw new Error("Use a JPEG, PNG, GIF, or WebP image");
|
|
4852
|
+
}
|
|
4853
|
+
return existing;
|
|
4854
|
+
}
|
|
4855
|
+
if (existing) {
|
|
4856
|
+
return new File([existing], name, { type });
|
|
4857
|
+
}
|
|
4858
|
+
return new File([bytes], name, { type });
|
|
4859
|
+
}
|
|
4860
|
+
function sanitizeName(value, fallback) {
|
|
4861
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
4862
|
+
const name = raw || fallback || "image.png";
|
|
4863
|
+
return name.replace(/[/\\]/g, "").slice(0, 180) || "image.png";
|
|
4864
|
+
}
|
|
4865
|
+
function normalizeType(value, name) {
|
|
4866
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
4867
|
+
if (raw === "image/jpg") return "image/jpeg";
|
|
4868
|
+
if (ALLOWED_TYPES.has(raw)) return raw;
|
|
4869
|
+
const ext = name.split(".").pop()?.toLowerCase() || "";
|
|
4870
|
+
return EXT_TO_TYPE[ext] || raw;
|
|
4871
|
+
}
|
|
4872
|
+
function asBlob(value) {
|
|
4873
|
+
if (typeof File !== "undefined" && value instanceof File) return value;
|
|
4874
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return value;
|
|
4875
|
+
return null;
|
|
4876
|
+
}
|
|
4877
|
+
function bytesFromUnknown(value) {
|
|
4878
|
+
if (!value) return null;
|
|
4879
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
4880
|
+
if (ArrayBuffer.isView(value)) {
|
|
4881
|
+
const view = value;
|
|
4882
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
4883
|
+
}
|
|
4884
|
+
if (Array.isArray(value)) {
|
|
4885
|
+
return Uint8Array.from(value.map((n) => Number(n) & 255));
|
|
4886
|
+
}
|
|
4887
|
+
if (typeof value === "string") {
|
|
4888
|
+
return bytesFromBase64(value);
|
|
4889
|
+
}
|
|
4890
|
+
return null;
|
|
4891
|
+
}
|
|
4892
|
+
function bytesFromBase64(value) {
|
|
4893
|
+
const comma = value.indexOf(",");
|
|
4894
|
+
const raw = value.startsWith("data:") && comma !== -1 ? value.slice(comma + 1) : value;
|
|
4895
|
+
const binary = atob(raw);
|
|
4896
|
+
const out = new Uint8Array(binary.length);
|
|
4897
|
+
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
4898
|
+
return out;
|
|
4899
|
+
}
|
|
4804
4900
|
var VVVEB_CHANNEL = "vvveb-bridge";
|
|
4901
|
+
function frameOrigin(frame) {
|
|
4902
|
+
try {
|
|
4903
|
+
return new URL(frame?.src || "", window.location.href).origin;
|
|
4904
|
+
} catch {
|
|
4905
|
+
return window.location.origin;
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4805
4908
|
function PlatformVisualEditorV2({
|
|
4806
4909
|
// channel kept for API compatibility; VvvebJs uses its own internal channel
|
|
4807
4910
|
embeddedGlobalKey = "__CONVERSION_EMBEDDED__",
|
|
@@ -4832,6 +4935,10 @@ function PlatformVisualEditorV2({
|
|
|
4832
4935
|
onSaveSuccess,
|
|
4833
4936
|
onSaveError,
|
|
4834
4937
|
onRequestSave,
|
|
4938
|
+
onUploadImage,
|
|
4939
|
+
onListImages,
|
|
4940
|
+
onAiAssist,
|
|
4941
|
+
onAiApplied,
|
|
4835
4942
|
onNavigateRequested,
|
|
4836
4943
|
onDiscardDirty,
|
|
4837
4944
|
renderHeader,
|
|
@@ -4860,9 +4967,88 @@ function PlatformVisualEditorV2({
|
|
|
4860
4967
|
const sendToVvveb = useCallback((type, payload) => {
|
|
4861
4968
|
iframeRef.current?.contentWindow?.postMessage(
|
|
4862
4969
|
{ channel: VVVEB_CHANNEL, type, payload },
|
|
4863
|
-
|
|
4970
|
+
frameOrigin(iframeRef.current)
|
|
4864
4971
|
);
|
|
4865
4972
|
}, []);
|
|
4973
|
+
const sendSaveResult = useCallback(
|
|
4974
|
+
(payload, err) => {
|
|
4975
|
+
const requestId = typeof payload?.requestId === "string" ? payload.requestId : null;
|
|
4976
|
+
if (!err) {
|
|
4977
|
+
sendToVvveb("save-result", { requestId, ok: true, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : "The save was refused";
|
|
4981
|
+
sendToVvveb("save-result", { requestId, ok: false, error: message });
|
|
4982
|
+
},
|
|
4983
|
+
[sendToVvveb]
|
|
4984
|
+
);
|
|
4985
|
+
const sendUploadImageResult = useCallback(
|
|
4986
|
+
(payload, result, err) => {
|
|
4987
|
+
const requestId = typeof payload?.requestId === "string" ? payload.requestId : null;
|
|
4988
|
+
if (!err) {
|
|
4989
|
+
sendToVvveb("upload-image-result", { requestId, ok: true, url: result?.url || "" });
|
|
4990
|
+
return;
|
|
4991
|
+
}
|
|
4992
|
+
sendToVvveb("upload-image-result", {
|
|
4993
|
+
requestId,
|
|
4994
|
+
ok: false,
|
|
4995
|
+
error: uploadImageErrorMessage(err)
|
|
4996
|
+
});
|
|
4997
|
+
},
|
|
4998
|
+
[sendToVvveb]
|
|
4999
|
+
);
|
|
5000
|
+
const handleListImages = useCallback(
|
|
5001
|
+
async (payload) => {
|
|
5002
|
+
const requestId = typeof payload?.requestId === "string" ? payload.requestId : null;
|
|
5003
|
+
if (!onListImages) {
|
|
5004
|
+
sendToVvveb("list-images-result", {
|
|
5005
|
+
requestId,
|
|
5006
|
+
ok: false,
|
|
5007
|
+
error: "Image list is not available"
|
|
5008
|
+
});
|
|
5009
|
+
return;
|
|
5010
|
+
}
|
|
5011
|
+
try {
|
|
5012
|
+
const result = await onListImages();
|
|
5013
|
+
const images = Array.isArray(result?.images) ? result.images.filter((img) => img && typeof img.url === "string" && img.url) : [];
|
|
5014
|
+
sendToVvveb("list-images-result", { requestId, ok: true, images });
|
|
5015
|
+
} catch (err) {
|
|
5016
|
+
sendToVvveb("list-images-result", {
|
|
5017
|
+
requestId,
|
|
5018
|
+
ok: false,
|
|
5019
|
+
error: uploadImageErrorMessage(err, "The list was refused")
|
|
5020
|
+
});
|
|
5021
|
+
}
|
|
5022
|
+
},
|
|
5023
|
+
[onListImages, sendToVvveb]
|
|
5024
|
+
);
|
|
5025
|
+
const handleAiRequest = useCallback(
|
|
5026
|
+
async (payload) => {
|
|
5027
|
+
const requestId = typeof payload?.requestId === "string" ? payload.requestId : null;
|
|
5028
|
+
if (!onAiAssist) {
|
|
5029
|
+
sendToVvveb("ai-result", {
|
|
5030
|
+
requestId,
|
|
5031
|
+
ok: false,
|
|
5032
|
+
error: "The assistant is not switched on for this workspace"
|
|
5033
|
+
});
|
|
5034
|
+
return;
|
|
5035
|
+
}
|
|
5036
|
+
try {
|
|
5037
|
+
const result = await onAiAssist(payload ?? {});
|
|
5038
|
+
sendToVvveb("ai-result", {
|
|
5039
|
+
...result,
|
|
5040
|
+
requestId: result?.requestId || requestId
|
|
5041
|
+
});
|
|
5042
|
+
} catch (err) {
|
|
5043
|
+
sendToVvveb("ai-result", {
|
|
5044
|
+
requestId,
|
|
5045
|
+
ok: false,
|
|
5046
|
+
error: uploadImageErrorMessage(err, "The assistant did not answer. Try again.")
|
|
5047
|
+
});
|
|
5048
|
+
}
|
|
5049
|
+
},
|
|
5050
|
+
[onAiAssist, sendToVvveb]
|
|
5051
|
+
);
|
|
4866
5052
|
const loadPayload = useMemo(
|
|
4867
5053
|
() => ({
|
|
4868
5054
|
iid: experiment?.iid,
|
|
@@ -4874,9 +5060,26 @@ function PlatformVisualEditorV2({
|
|
|
4874
5060
|
conversionProxyBaseUrl: typeof conversionProxyBaseUrl === "string" ? conversionProxyBaseUrl.trim().replace(/\/+$/, "") : "",
|
|
4875
5061
|
strictObserverFreeze: !!strictObserverFreeze,
|
|
4876
5062
|
trackingMarkers: Array.isArray(trackingMarkers) ? trackingMarkers.filter((m) => typeof m === "string" && m.trim().length > 0) : [],
|
|
4877
|
-
variations: experiment?.variations ?? []
|
|
5063
|
+
variations: experiment?.variations ?? [],
|
|
5064
|
+
// S1: this package answers every save with "save-result". A shell that
|
|
5065
|
+
// knows the field waits for the answer before it clears its working
|
|
5066
|
+
// state; an older shell ignores the field and behaves as it did before.
|
|
5067
|
+
capabilities: {
|
|
5068
|
+
saveResult: true,
|
|
5069
|
+
...onUploadImage ? { uploadImage: true } : {},
|
|
5070
|
+
...onListImages ? { listImages: true } : {},
|
|
5071
|
+
...onAiAssist ? { ai: true } : {}
|
|
5072
|
+
}
|
|
4878
5073
|
}),
|
|
4879
|
-
[
|
|
5074
|
+
[
|
|
5075
|
+
conversionProxyBaseUrl,
|
|
5076
|
+
experiment,
|
|
5077
|
+
onAiAssist,
|
|
5078
|
+
onListImages,
|
|
5079
|
+
onUploadImage,
|
|
5080
|
+
strictObserverFreeze,
|
|
5081
|
+
trackingMarkers
|
|
5082
|
+
]
|
|
4880
5083
|
);
|
|
4881
5084
|
const editorSrc = useMemo(() => {
|
|
4882
5085
|
const workerBase = normalizeProxyBaseUrl(conversionProxyBaseUrl);
|
|
@@ -4895,6 +5098,8 @@ function PlatformVisualEditorV2({
|
|
|
4895
5098
|
const handleMessage = useCallback(
|
|
4896
5099
|
async (e) => {
|
|
4897
5100
|
if (!e.data || e.data.channel !== VVVEB_CHANNEL) return;
|
|
5101
|
+
if (!iframeRef.current || e.source !== iframeRef.current.contentWindow) return;
|
|
5102
|
+
if (e.origin !== frameOrigin(iframeRef.current)) return;
|
|
4898
5103
|
const { type, payload } = e.data;
|
|
4899
5104
|
switch (type) {
|
|
4900
5105
|
case "editor-ready":
|
|
@@ -4922,8 +5127,10 @@ function PlatformVisualEditorV2({
|
|
|
4922
5127
|
try {
|
|
4923
5128
|
await onRequestSave(payload ?? {});
|
|
4924
5129
|
setDirty(false);
|
|
5130
|
+
sendSaveResult(payload, null);
|
|
4925
5131
|
onSaveSuccess?.(payload ?? {});
|
|
4926
5132
|
} catch (err) {
|
|
5133
|
+
sendSaveResult(payload, err);
|
|
4927
5134
|
onSaveError?.(err);
|
|
4928
5135
|
}
|
|
4929
5136
|
break;
|
|
@@ -4932,13 +5139,50 @@ function PlatformVisualEditorV2({
|
|
|
4932
5139
|
try {
|
|
4933
5140
|
await onRequestSave(payload ?? {});
|
|
4934
5141
|
setDirty(false);
|
|
5142
|
+
sendSaveResult(payload, null);
|
|
4935
5143
|
onSaveSuccess?.(payload ?? {});
|
|
4936
5144
|
if (payload?.hash) onNavigateRequested?.(payload.hash);
|
|
4937
5145
|
} catch (err) {
|
|
5146
|
+
sendSaveResult(payload, err);
|
|
4938
5147
|
onSaveError?.(err);
|
|
4939
5148
|
}
|
|
4940
5149
|
break;
|
|
5150
|
+
case "upload-image":
|
|
5151
|
+
if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation();
|
|
5152
|
+
if (!onUploadImage) {
|
|
5153
|
+
sendUploadImageResult(payload, null, "Image upload is not available");
|
|
5154
|
+
return;
|
|
5155
|
+
}
|
|
5156
|
+
try {
|
|
5157
|
+
const file = fileFromUploadImagePayload(payload);
|
|
5158
|
+
const result = await onUploadImage(file);
|
|
5159
|
+
const url = typeof result?.url === "string" ? result.url.trim() : "";
|
|
5160
|
+
if (!url) throw new Error("The upload did not return a URL");
|
|
5161
|
+
sendUploadImageResult(payload, { url }, null);
|
|
5162
|
+
} catch (err) {
|
|
5163
|
+
sendUploadImageResult(payload, null, err);
|
|
5164
|
+
}
|
|
5165
|
+
break;
|
|
5166
|
+
case "list-images":
|
|
5167
|
+
if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation();
|
|
5168
|
+
await handleListImages(payload);
|
|
5169
|
+
break;
|
|
5170
|
+
case "ai-request":
|
|
5171
|
+
if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation();
|
|
5172
|
+
await handleAiRequest(payload);
|
|
5173
|
+
break;
|
|
5174
|
+
case "ai-applied":
|
|
5175
|
+
if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation();
|
|
5176
|
+
try {
|
|
5177
|
+
await onAiApplied?.(payload ?? {});
|
|
5178
|
+
} catch {
|
|
5179
|
+
}
|
|
5180
|
+
break;
|
|
4941
5181
|
case "close-editor":
|
|
5182
|
+
if (payload?.discard === true) {
|
|
5183
|
+
onClose?.();
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
4942
5186
|
if (!dirtyRef.current) {
|
|
4943
5187
|
onClose?.();
|
|
4944
5188
|
return;
|
|
@@ -4959,12 +5203,20 @@ function PlatformVisualEditorV2({
|
|
|
4959
5203
|
onEditorReady,
|
|
4960
5204
|
onEditorUrlChanged,
|
|
4961
5205
|
onRequestSave,
|
|
5206
|
+
onUploadImage,
|
|
5207
|
+
onListImages,
|
|
5208
|
+
onAiAssist,
|
|
5209
|
+
onAiApplied,
|
|
5210
|
+
handleListImages,
|
|
5211
|
+
handleAiRequest,
|
|
4962
5212
|
onSaveSuccess,
|
|
4963
5213
|
onSaveError,
|
|
4964
5214
|
onNavigateRequested,
|
|
4965
5215
|
onClose,
|
|
4966
5216
|
onDiscardDirty,
|
|
4967
|
-
sendToVvveb
|
|
5217
|
+
sendToVvveb,
|
|
5218
|
+
sendSaveResult,
|
|
5219
|
+
sendUploadImageResult
|
|
4968
5220
|
]
|
|
4969
5221
|
);
|
|
4970
5222
|
useLayoutEffect(() => {
|