@accelerated-agency/visual-editor 0.4.9 → 0.5.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/index.js CHANGED
@@ -5012,15 +5012,15 @@ function PlatformVisualEditorV2({
5012
5012
  ) }) });
5013
5013
  }
5014
5014
  var AI_EDITOR_CHANNEL = "ve-ai-editor";
5015
- var AI_VIEWPORT_WIDTH = {
5016
- desktop: "100%",
5017
- tablet: "768px",
5018
- mobile: "390px"
5019
- };
5020
- var AI_VIEWPORT_LABEL = {
5021
- desktop: "1440px",
5022
- tablet: "768px",
5023
- mobile: "390px"
5015
+ var AI_LOCAL_STORAGE_PREFIX = "conversion-vvveb-v2::";
5016
+ var DEVICE_PRESETS = {
5017
+ desktop: { label: "Desktop", width: 1440, height: 1024, device: "desktop" },
5018
+ tablet: { label: "iPad", width: 768, height: 1024, device: "tablet" },
5019
+ mobile: { label: "iPhone 13", width: 390, height: 844, device: "mobile" },
5020
+ "galaxy-s22": { label: "Galaxy S22", width: 360, height: 780, device: "mobile" },
5021
+ "iphone-7": { label: "iPhone 7", width: 375, height: 667, device: "mobile" },
5022
+ "galaxy-j1": { label: "Galaxy J1", width: 360, height: 640, device: "mobile" },
5023
+ responsive: { label: "Responsive", width: 1280, height: 800, device: "desktop" }
5024
5024
  };
5025
5025
  function fingerprint(value) {
5026
5026
  let hash = 5381;
@@ -5029,9 +5029,17 @@ function fingerprint(value) {
5029
5029
  }
5030
5030
  return (hash >>> 0).toString(36);
5031
5031
  }
5032
+ function activeVariationStorageKeyFromExperiment(experiment) {
5033
+ const experimentId = String(experiment?.experimentId || "");
5034
+ const pageUrl = String(experiment?.pageUrl || "");
5035
+ const key = `${AI_LOCAL_STORAGE_PREFIX}activeVar:${experimentId}:${pageUrl}`;
5036
+ if (key === `${AI_LOCAL_STORAGE_PREFIX}activeVar::`) return null;
5037
+ return key;
5038
+ }
5032
5039
  function PlatformAiEditor({
5033
5040
  embeddedGlobalKey = "__CONVERSION_EMBEDDED__",
5034
5041
  proxyBaseUrl = "",
5042
+ trackingMarkers = [],
5035
5043
  chatComponent,
5036
5044
  className = "fixed inset-0 z-[9999] flex flex-col bg-white",
5037
5045
  editorClassName = "flex-1 min-h-0",
@@ -5061,7 +5069,20 @@ function PlatformAiEditor({
5061
5069
  const [localActiveVariationId, setLocalActiveVariationId] = useState(null);
5062
5070
  const [isSaving, setIsSaving] = useState(false);
5063
5071
  const [isIframeLoading, setIsIframeLoading] = useState(false);
5064
- const [viewport, setViewport] = useState("desktop");
5072
+ const [currentDevice, setCurrentDevice] = useState("desktop");
5073
+ const [viewportWidth, setViewportWidth] = useState(1440);
5074
+ const [viewportHeight, setViewportHeight] = useState(1024);
5075
+ const [viewportZoom, setViewportZoom] = useState(1);
5076
+ const [viewportPreset, setViewportPreset] = useState("desktop");
5077
+ const [isViewportMenuOpen, setIsViewportMenuOpen] = useState(false);
5078
+ const [menuWidth, setMenuWidth] = useState("1440");
5079
+ const [menuHeight, setMenuHeight] = useState("1024");
5080
+ const [menuZoom, setMenuZoom] = useState("100");
5081
+ const [iframePanelWidth, setIframePanelWidth] = useState(null);
5082
+ const iframePanelRef = useRef(null);
5083
+ const viewportMenuRef = useRef(null);
5084
+ const viewportMenuButtonRef = useRef(null);
5085
+ const viewportLabelRef = useRef(null);
5065
5086
  useEffect(() => {
5066
5087
  window[embeddedGlobalKey] = true;
5067
5088
  return () => {
@@ -5074,6 +5095,32 @@ function PlatformAiEditor({
5074
5095
  const variations = experiment?.variations ?? [];
5075
5096
  const activeVariationId = controlledActiveVariationId ?? localActiveVariationId;
5076
5097
  const setActiveVariationId = controlledSetActiveVariationId ?? setLocalActiveVariationId;
5098
+ const persistedVariationStorageKey = useMemo(
5099
+ () => activeVariationStorageKeyFromExperiment(experiment),
5100
+ [experiment?.experimentId, experiment?.pageUrl]
5101
+ );
5102
+ const readPersistedVariationId = useCallback(() => {
5103
+ if (!persistedVariationStorageKey) return null;
5104
+ try {
5105
+ return sessionStorage.getItem(persistedVariationStorageKey);
5106
+ } catch {
5107
+ return null;
5108
+ }
5109
+ }, [persistedVariationStorageKey]);
5110
+ const persistVariationId = useCallback(
5111
+ (variationId) => {
5112
+ if (!persistedVariationStorageKey) return;
5113
+ try {
5114
+ if (variationId) {
5115
+ sessionStorage.setItem(persistedVariationStorageKey, variationId);
5116
+ } else {
5117
+ sessionStorage.removeItem(persistedVariationStorageKey);
5118
+ }
5119
+ } catch {
5120
+ }
5121
+ },
5122
+ [persistedVariationStorageKey]
5123
+ );
5077
5124
  useEffect(() => {
5078
5125
  if (!variations.length) {
5079
5126
  setActiveVariationId(null);
@@ -5081,10 +5128,12 @@ function PlatformAiEditor({
5081
5128
  }
5082
5129
  setActiveVariationId((current) => {
5083
5130
  if (current && variations.some((variation) => variation._id === current)) return current;
5084
- const baseline = variations.find((variation) => variation.baseline);
5085
- return baseline?._id ?? variations[0]._id;
5131
+ const stored = readPersistedVariationId();
5132
+ if (stored && variations.some((variation) => variation._id === stored)) return stored;
5133
+ const firstNonBaseline = variations.find((variation) => !variation.baseline);
5134
+ return firstNonBaseline?._id ?? variations[0]._id;
5086
5135
  });
5087
- }, [variations]);
5136
+ }, [variations, readPersistedVariationId]);
5088
5137
  const activeVariation = useMemo(
5089
5138
  () => variations.find((variation) => variation._id === activeVariationId) ?? null,
5090
5139
  [variations, activeVariationId]
@@ -5105,18 +5154,52 @@ function PlatformAiEditor({
5105
5154
  () => `ai-iframe-${activeVariationId ?? "default"}-${cssFingerprint}-${jsFingerprint}`,
5106
5155
  [activeVariationId, cssFingerprint, jsFingerprint]
5107
5156
  );
5108
- const iframeWidth = AI_VIEWPORT_WIDTH[viewport];
5157
+ useEffect(() => {
5158
+ const panel = iframePanelRef.current;
5159
+ if (!panel) return;
5160
+ const updateWidth = () => {
5161
+ setIframePanelWidth(panel.clientWidth);
5162
+ };
5163
+ updateWidth();
5164
+ const observer = new ResizeObserver(() => {
5165
+ updateWidth();
5166
+ });
5167
+ observer.observe(panel);
5168
+ return () => {
5169
+ observer.disconnect();
5170
+ };
5171
+ }, []);
5172
+ const viewportFitZoom = useMemo(() => {
5173
+ const available = iframePanelWidth ? Math.max(260, iframePanelWidth - 24) : viewportWidth;
5174
+ if (!viewportWidth || viewportWidth <= 0) return 1;
5175
+ return Math.min(1, available / viewportWidth);
5176
+ }, [viewportWidth, iframePanelWidth]);
5177
+ const appliedViewportZoom = useMemo(() => {
5178
+ let z = Number(viewportZoom);
5179
+ if (!Number.isFinite(z) || z <= 0) z = 1;
5180
+ z = Math.max(0.25, Math.min(2, z));
5181
+ return Math.min(z, viewportFitZoom);
5182
+ }, [viewportZoom, viewportFitZoom]);
5183
+ const viewportLabel = useMemo(
5184
+ () => `${viewportWidth}x${viewportHeight} \xB7 ${Math.round(appliedViewportZoom * 100)}%`,
5185
+ [viewportWidth, viewportHeight, appliedViewportZoom]
5186
+ );
5109
5187
  const editorSrc = useMemo(() => {
5110
5188
  const pageUrl = experiment?.pageUrl;
5111
5189
  if (!pageUrl) return "about:blank";
5112
5190
  const safeBaseUrl = normalizeProxyBaseUrl(proxyBaseUrl);
5113
- const query = new URLSearchParams({
5191
+ const safeTrackingMarkers = Array.isArray(trackingMarkers) ? trackingMarkers.filter((marker) => typeof marker === "string" && marker.trim().length > 0) : [];
5192
+ const queryParams = new URLSearchParams({
5114
5193
  password: experiment?.editorPassword ?? "",
5115
5194
  url: pageUrl
5116
- }).toString();
5195
+ });
5196
+ if (safeTrackingMarkers.length) {
5197
+ queryParams.set("trackingMarkers", JSON.stringify(safeTrackingMarkers));
5198
+ }
5199
+ const query = queryParams.toString();
5117
5200
  const previewPath = `/api/conversion-proxy?${query}`;
5118
5201
  return safeBaseUrl ? `${safeBaseUrl}${previewPath}` : previewPath;
5119
- }, [proxyBaseUrl, experiment?.pageUrl, experiment?.editorPassword]);
5202
+ }, [proxyBaseUrl, experiment?.pageUrl, experiment?.editorPassword, trackingMarkers]);
5120
5203
  const postAiCodeToIframe = useCallback(() => {
5121
5204
  const targetWindow = iframeRef.current?.contentWindow;
5122
5205
  if (!targetWindow) return;
@@ -5233,6 +5316,90 @@ function PlatformAiEditor({
5233
5316
  onSaveSuccess,
5234
5317
  onSaveError
5235
5318
  ]);
5319
+ const clampViewportNumber = useCallback(
5320
+ (value, fallback, min, max) => {
5321
+ const parsed = Number.parseInt(value, 10);
5322
+ if (!Number.isFinite(parsed)) return fallback;
5323
+ return Math.max(min, Math.min(max, parsed));
5324
+ },
5325
+ []
5326
+ );
5327
+ const setDevicePreset = useCallback((device) => {
5328
+ const preset = DEVICE_PRESETS[device];
5329
+ setViewportPreset(device);
5330
+ setCurrentDevice(device);
5331
+ setViewportWidth(preset.width);
5332
+ setViewportHeight(preset.height);
5333
+ setMenuWidth(String(preset.width));
5334
+ setMenuHeight(String(preset.height));
5335
+ }, []);
5336
+ const setViewportFromPreset = useCallback((presetKey) => {
5337
+ const preset = DEVICE_PRESETS[presetKey];
5338
+ setViewportPreset(presetKey);
5339
+ setViewportWidth(preset.width);
5340
+ setViewportHeight(preset.height);
5341
+ setCurrentDevice(preset.device);
5342
+ setMenuWidth(String(preset.width));
5343
+ setMenuHeight(String(preset.height));
5344
+ }, []);
5345
+ const applyCustomViewport = useCallback(() => {
5346
+ const nextWidth = clampViewportNumber(menuWidth, viewportWidth, 240, 3840);
5347
+ const nextHeight = clampViewportNumber(menuHeight, viewportHeight, 320, 3840);
5348
+ const nextZoomPct = clampViewportNumber(
5349
+ menuZoom,
5350
+ Math.round(appliedViewportZoom * 100),
5351
+ 25,
5352
+ 200
5353
+ );
5354
+ setViewportWidth(nextWidth);
5355
+ setViewportHeight(nextHeight);
5356
+ setViewportZoom(nextZoomPct / 100);
5357
+ setViewportPreset("custom");
5358
+ setCurrentDevice(nextWidth <= 480 ? "mobile" : nextWidth <= 1024 ? "tablet" : "desktop");
5359
+ setMenuWidth(String(nextWidth));
5360
+ setMenuHeight(String(nextHeight));
5361
+ setMenuZoom(String(nextZoomPct));
5362
+ setIsViewportMenuOpen(false);
5363
+ }, [
5364
+ clampViewportNumber,
5365
+ menuWidth,
5366
+ menuHeight,
5367
+ menuZoom,
5368
+ viewportWidth,
5369
+ viewportHeight,
5370
+ appliedViewportZoom
5371
+ ]);
5372
+ useEffect(() => {
5373
+ setMenuWidth(String(viewportWidth));
5374
+ setMenuHeight(String(viewportHeight));
5375
+ setMenuZoom(String(Math.round(appliedViewportZoom * 100)));
5376
+ }, [viewportWidth, viewportHeight, appliedViewportZoom]);
5377
+ useEffect(() => {
5378
+ const onWindowResize = () => {
5379
+ setMenuZoom(String(Math.round(Math.min(viewportZoom, viewportFitZoom) * 100)));
5380
+ };
5381
+ window.addEventListener("resize", onWindowResize);
5382
+ return () => window.removeEventListener("resize", onWindowResize);
5383
+ }, [viewportZoom, viewportFitZoom]);
5384
+ useEffect(() => {
5385
+ if (!isViewportMenuOpen) return;
5386
+ const onDocClick = (event) => {
5387
+ const target = event.target;
5388
+ if (viewportMenuRef.current?.contains(target)) return;
5389
+ if (viewportMenuButtonRef.current?.contains(target)) return;
5390
+ if (viewportLabelRef.current?.contains(target)) return;
5391
+ setIsViewportMenuOpen(false);
5392
+ };
5393
+ const onDocKeyDown = (event) => {
5394
+ if (event.key === "Escape") setIsViewportMenuOpen(false);
5395
+ };
5396
+ document.addEventListener("click", onDocClick);
5397
+ document.addEventListener("keydown", onDocKeyDown);
5398
+ return () => {
5399
+ document.removeEventListener("click", onDocClick);
5400
+ document.removeEventListener("keydown", onDocKeyDown);
5401
+ };
5402
+ }, [isViewportMenuOpen]);
5236
5403
  if (error) {
5237
5404
  if (renderError) return renderError(error);
5238
5405
  return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9999] flex items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 text-center px-8", children: [
@@ -5241,112 +5408,413 @@ function PlatformAiEditor({
5241
5408
  /* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: error })
5242
5409
  ] }) });
5243
5410
  }
5244
- return /* @__PURE__ */ jsxs("div", { className, children: [
5245
- showHeader ? renderHeader?.({
5246
- title: title ?? experiment?.name,
5247
- status: status ?? experiment?.status,
5248
- tabs,
5249
- activeTab,
5250
- onTabClick,
5251
- onClose
5252
- }) ?? /* @__PURE__ */ jsxs("div", { className: "h-12 border-b border-slate-200 bg-white flex items-center justify-between px-4 shadow-[0_1px_3px_rgba(0,0,0,0.04)] shrink-0", children: [
5253
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
5254
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-indigo-50 text-indigo-600 border border-indigo-100 shrink-0", children: "AI" }),
5255
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-slate-800 truncate", children: title ?? experiment?.name ?? "AI Editor" }),
5256
- status ? /* @__PURE__ */ jsx("span", { className: "hidden sm:inline-flex text-[10px] px-2 py-0.5 rounded-full border border-slate-200 text-slate-500 font-medium bg-slate-50 capitalize", children: status }) : null
5257
- ] }),
5258
- tabs.length > 0 ? /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-1 absolute left-1/2 -translate-x-1/2", children: tabs.map((tab) => /* @__PURE__ */ jsx(
5259
- "button",
5260
- {
5261
- type: "button",
5262
- onClick: () => onTabClick(tab),
5263
- className: `text-[11px] font-semibold px-3 py-1 rounded-full border transition-all ${tab.label === activeTab ? "bg-indigo-600 text-white border-indigo-600 shadow-sm" : "bg-white text-slate-500 border-slate-200 hover:border-slate-300 hover:text-slate-700"}`,
5264
- children: tab.label
5265
- },
5266
- tab.hash
5267
- )) }) : null,
5268
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5269
- /* @__PURE__ */ jsxs("div", { className: "h-8 px-3 rounded-md border border-slate-200 bg-white flex items-center gap-2 text-xs font-medium text-slate-700", children: [
5270
- /* @__PURE__ */ jsx("span", { children: AI_VIEWPORT_LABEL[viewport] }),
5271
- /* @__PURE__ */ jsx("i", { className: "bi bi-chevron-down text-[10px] text-slate-500" })
5411
+ const handleVariationSelectChange = useCallback(
5412
+ (nextVariationId) => {
5413
+ persistVariationId(nextVariationId);
5414
+ setActiveVariationId(nextVariationId);
5415
+ },
5416
+ [persistVariationId, setActiveVariationId]
5417
+ );
5418
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5419
+ /* @__PURE__ */ jsx("style", { children: `
5420
+ .ai-dev-toolbar {
5421
+ display: flex;
5422
+ align-items: center;
5423
+ gap: 8px;
5424
+ }
5425
+ .ai-tb-viewport {
5426
+ display: flex;
5427
+ align-items: center;
5428
+ gap: 4px;
5429
+ padding: 6px 10px;
5430
+ border-radius: 6px;
5431
+ border: 1px solid #e5e7eb;
5432
+ background: #fff;
5433
+ height: 30px;
5434
+ flex-shrink: 0;
5435
+ cursor: pointer;
5436
+ color: #404040;
5437
+ font-size: 14px;
5438
+ font-weight: 500;
5439
+ line-height: 1;
5440
+ }
5441
+ .ai-tb-viewport > i {
5442
+ font-size: 9px;
5443
+ }
5444
+ .ai-tb-dev-wrap {
5445
+ position: relative;
5446
+ }
5447
+ .ai-tb-dev-btns {
5448
+ display: flex;
5449
+ align-items: center;
5450
+ gap: 1px;
5451
+ padding: 8px 4px;
5452
+ background: #f0f0f0;
5453
+ border-radius: 7px;
5454
+ height: 30px;
5455
+ width: auto;
5456
+ }
5457
+ .ai-tb-dk-btn {
5458
+ width: 28px;
5459
+ height: 28px;
5460
+ background: transparent;
5461
+ border: none;
5462
+ border-radius: 5px;
5463
+ cursor: pointer;
5464
+ color: #71717a;
5465
+ display: flex;
5466
+ align-items: center;
5467
+ justify-content: center;
5468
+ font-size: 13px;
5469
+ transition: all 0.12s;
5470
+ flex-shrink: 0;
5471
+ }
5472
+ .ai-tb-dk-btn.active {
5473
+ background: #fff;
5474
+ color: #27272a;
5475
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
5476
+ }
5477
+ .ai-tb-dev-menu {
5478
+ position: absolute;
5479
+ top: calc(100% + 8px);
5480
+ right: 0;
5481
+ z-index: 12040;
5482
+ width: 264px;
5483
+ background: #fff;
5484
+ border: 1px solid #e5e7eb;
5485
+ border-radius: 8px;
5486
+ padding: 4px 4px;
5487
+ box-shadow: 0 12px 28px rgba(2, 6, 23, 0.18);
5488
+ color: #27272a;
5489
+ }
5490
+ .ai-tb-dev-menu > .row {
5491
+ display: flex;
5492
+ align-items: center;
5493
+ gap: 8px;
5494
+ margin: 0 0 8px;
5495
+ justify-content: space-between;
5496
+ padding: 4px 12px;
5497
+ }
5498
+ .ai-tb-dev-menu .row-split {
5499
+ display: flex;
5500
+ gap: 8px;
5501
+ }
5502
+ .ai-tb-dev-menu .row-split > .row{
5503
+ flex-direction: column;
5504
+ align-items: flex-start;
5505
+ }
5506
+ .ai-tb-dev-menu .row-split > * {
5507
+ flex: 1;
5508
+ }
5509
+ .ai-tb-dev-menu .row-split > row {
5510
+ display: flex;
5511
+ flex-direction: column;
5512
+ align-items: flex-start;
5513
+ }
5514
+ .ai-tb-dev-menu label {
5515
+ min-width: fit-content;
5516
+ color: var(--content-subtle, #737373);
5517
+ font-size: 12px;
5518
+ font-style: normal;
5519
+ font-weight: 500;
5520
+ line-height: 14px;
5521
+ }
5522
+ .ai-tb-dev-menu input {
5523
+ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 1px 0 rgba(0, 0, 0, 0.01);
5524
+ width: 100%;
5525
+ height: 30px;
5526
+ border-radius: 6px;
5527
+ padding: 0 8px;
5528
+ font-size: 12px;
5529
+ color: #18181b;
5530
+ background: #fff;
5531
+ outline: none;
5532
+ color: #404040;
5533
+ border-color: transparent;
5534
+ }
5535
+ .ai-tb-dev-menu input#ai-dev-zoom-level {
5536
+ max-width: fit-content;
5537
+ width: 72px;
5538
+ }
5539
+ .ai-tb-dev-menu input:focus {
5540
+ border-color: #818cf8;
5541
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.15);
5542
+ }
5543
+ .ai-vp-presets {
5544
+ display: flex;
5545
+ flex-wrap: wrap;
5546
+ gap: 6px;
5547
+ padding: 4px 12px;
5548
+ }
5549
+ .ai-vp-preset-btn {
5550
+ border: 1px solid #e4e4e7;
5551
+ background: #fff;
5552
+ color: #3f3f46;
5553
+ border-radius: 6px;
5554
+ font-size: 11px;
5555
+ line-height: 1;
5556
+ padding: 7px 8px;
5557
+ cursor: pointer;
5558
+ }
5559
+ .ai-vp-preset-btn:hover,
5560
+ .ai-vp-preset-btn.active {
5561
+ background: #f4f4f5;
5562
+ }
5563
+ .ai-dev-ft {
5564
+ display: flex;
5565
+ justify-content: flex-end;
5566
+ padding: 4px 12px;
5567
+ }
5568
+ .ai-apply-btn {
5569
+ border: 1px solid #c7d2fe;
5570
+ background: #eef2ff;
5571
+ color: #3730a3;
5572
+ border-radius: 6px;
5573
+ height: 30px;
5574
+ padding: 0 10px;
5575
+ font-size: 12px;
5576
+ font-weight: 600;
5577
+ cursor: pointer;
5578
+ }
5579
+ .ai-apply-btn:hover {
5580
+ background: #e0e7ff;
5581
+ border-color: #a5b4fc;
5582
+ }
5583
+
5584
+ ` }),
5585
+ /* @__PURE__ */ jsxs("div", { className, children: [
5586
+ showHeader ? renderHeader?.({
5587
+ title: title ?? experiment?.name,
5588
+ status: status ?? experiment?.status,
5589
+ tabs,
5590
+ activeTab,
5591
+ onTabClick,
5592
+ onClose
5593
+ }) ?? /* @__PURE__ */ jsxs("div", { className: "h-12 border-b border-slate-200 bg-white flex items-center justify-between px-4 shadow-[0_1px_3px_rgba(0,0,0,0.04)] shrink-0", children: [
5594
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
5595
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-indigo-50 text-indigo-600 border border-indigo-100 shrink-0", children: "AI" }),
5596
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-slate-800 truncate", children: title ?? experiment?.name ?? "AI Editor" }),
5597
+ status ? /* @__PURE__ */ jsx("span", { className: "hidden sm:inline-flex text-[10px] px-2 py-0.5 rounded-full border border-slate-200 text-slate-500 font-medium bg-slate-50 capitalize", children: status }) : null
5272
5598
  ] }),
5273
- /* @__PURE__ */ jsx("div", { className: "h-8 rounded-lg bg-slate-100 px-1 flex items-center gap-1", style: { backgroundColor: "#F0F0F0" }, children: [
5274
- { key: "desktop", icon: "bi-display", title: "Desktop \u2014 full width" },
5275
- { key: "tablet", icon: "bi-tablet", title: "Tablet \u2014 768px" },
5276
- { key: "mobile", icon: "bi-phone", title: "Mobile \u2014 390px" }
5277
- ].map((vp) => /* @__PURE__ */ jsx(
5599
+ tabs.length > 0 ? /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-1 absolute left-1/2 -translate-x-1/2", children: tabs.map((tab) => /* @__PURE__ */ jsx(
5278
5600
  "button",
5279
5601
  {
5280
5602
  type: "button",
5281
- onClick: () => setViewport(vp.key),
5282
- title: vp.title,
5283
- className: `w-7 h-7 rounded-md border flex items-center justify-center transition-all ${viewport === vp.key ? "bg-white border-slate-300 text-slate-700 shadow-sm" : "bg-transparent border-transparent text-slate-500 hover:text-slate-700 hover:bg-white/70"}`,
5284
- children: /* @__PURE__ */ jsx("i", { className: `bi ${vp.icon} text-[12px]` })
5603
+ onClick: () => onTabClick(tab),
5604
+ className: `text-[11px] font-semibold px-3 py-1 rounded-full border transition-all ${tab.label === activeTab ? "bg-indigo-600 text-white border-indigo-600 shadow-sm" : "bg-white text-slate-500 border-slate-200 hover:border-slate-300 hover:text-slate-700"}`,
5605
+ children: tab.label
5285
5606
  },
5286
- vp.key
5287
- )) })
5288
- ] }),
5289
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5290
- variations.length > 0 ? /* @__PURE__ */ jsx(
5291
- "select",
5292
- {
5293
- className: "h-8 max-w-[220px] rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-700",
5294
- value: activeVariationId ?? "",
5295
- onChange: (event) => setActiveVariationId(event.target.value || null),
5296
- "aria-label": "Select variation",
5297
- children: variations.map((variation) => /* @__PURE__ */ jsx("option", { value: variation._id, children: variation.name }, variation._id))
5298
- }
5299
- ) : null,
5300
- onRequestSave ? /* @__PURE__ */ jsx(
5301
- "button",
5302
- {
5303
- type: "button",
5304
- className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-indigo-200 text-indigo-700 bg-indigo-50 hover:bg-indigo-100 hover:border-indigo-300 transition-all disabled:opacity-60 disabled:cursor-not-allowed",
5305
- onClick: () => void handleSave(),
5306
- disabled: isSaving,
5307
- children: isSaving ? "Saving..." : "Save"
5308
- }
5309
- ) : null,
5310
- showCloseButton ? /* @__PURE__ */ jsx(
5311
- "button",
5607
+ tab.hash
5608
+ )) }) : null,
5609
+ /* @__PURE__ */ jsxs("div", { className: "ai-dev-toolbar", children: [
5610
+ /* @__PURE__ */ jsxs(
5611
+ "div",
5612
+ {
5613
+ ref: viewportLabelRef,
5614
+ role: "button",
5615
+ tabIndex: 0,
5616
+ className: "ai-tb-viewport",
5617
+ onClick: () => setIsViewportMenuOpen((prev) => !prev),
5618
+ onKeyDown: (event) => {
5619
+ if (event.key === "Enter" || event.key === " ") {
5620
+ event.preventDefault();
5621
+ setIsViewportMenuOpen((prev) => !prev);
5622
+ }
5623
+ },
5624
+ children: [
5625
+ /* @__PURE__ */ jsx("span", { children: viewportLabel }),
5626
+ /* @__PURE__ */ jsx("i", { className: "bi bi-chevron-down" })
5627
+ ]
5628
+ }
5629
+ ),
5630
+ /* @__PURE__ */ jsxs("div", { className: "ai-tb-dev-wrap", children: [
5631
+ /* @__PURE__ */ jsxs("div", { className: "ai-tb-dev-btns", children: [
5632
+ [
5633
+ { key: "desktop", icon: "bi-display", title: "Desktop \u2014 full width" },
5634
+ { key: "tablet", icon: "bi-tablet", title: "Tablet \u2014 768px" },
5635
+ { key: "mobile", icon: "bi-phone", title: "Mobile \u2014 390px" }
5636
+ ].map((vp) => /* @__PURE__ */ jsx(
5637
+ "button",
5638
+ {
5639
+ type: "button",
5640
+ onClick: () => setDevicePreset(vp.key),
5641
+ title: vp.title,
5642
+ className: `ai-tb-dk-btn ${currentDevice === vp.key ? "active" : ""}`,
5643
+ children: /* @__PURE__ */ jsx("i", { className: `bi ${vp.icon}` })
5644
+ },
5645
+ vp.key
5646
+ )),
5647
+ /* @__PURE__ */ jsx(
5648
+ "button",
5649
+ {
5650
+ ref: viewportMenuButtonRef,
5651
+ type: "button",
5652
+ onClick: () => setIsViewportMenuOpen((prev) => !prev),
5653
+ title: "More options",
5654
+ className: `ai-tb-dk-btn ${isViewportMenuOpen ? "active" : ""}`,
5655
+ children: /* @__PURE__ */ jsx("i", { className: "bi bi-three-dots" })
5656
+ }
5657
+ )
5658
+ ] }),
5659
+ isViewportMenuOpen ? /* @__PURE__ */ jsxs(
5660
+ "div",
5661
+ {
5662
+ ref: viewportMenuRef,
5663
+ className: "ai-tb-dev-menu",
5664
+ children: [
5665
+ /* @__PURE__ */ jsxs("div", { className: "row row-zoom", children: [
5666
+ /* @__PURE__ */ jsx("label", { htmlFor: "ai-dev-zoom-level", children: "Zoom" }),
5667
+ /* @__PURE__ */ jsx(
5668
+ "input",
5669
+ {
5670
+ id: "ai-dev-zoom-level",
5671
+ type: "number",
5672
+ min: 25,
5673
+ max: 200,
5674
+ step: 5,
5675
+ value: menuZoom,
5676
+ onChange: (event) => setMenuZoom(event.target.value),
5677
+ onBlur: (event) => {
5678
+ const nextZoomPct = clampViewportNumber(
5679
+ event.target.value,
5680
+ Math.round(appliedViewportZoom * 100),
5681
+ 25,
5682
+ 200
5683
+ );
5684
+ setViewportZoom(nextZoomPct / 100);
5685
+ setMenuZoom(String(nextZoomPct));
5686
+ }
5687
+ }
5688
+ )
5689
+ ] }),
5690
+ /* @__PURE__ */ jsxs("div", { className: "row row-split row-width height-width-row", children: [
5691
+ /* @__PURE__ */ jsxs("div", { className: "row", style: { margin: 0 }, children: [
5692
+ /* @__PURE__ */ jsx("label", { htmlFor: "ai-dev-custom-width", children: "Width" }),
5693
+ /* @__PURE__ */ jsx(
5694
+ "input",
5695
+ {
5696
+ id: "ai-dev-custom-width",
5697
+ type: "number",
5698
+ min: 240,
5699
+ max: 3840,
5700
+ step: 1,
5701
+ value: menuWidth,
5702
+ onChange: (event) => setMenuWidth(event.target.value)
5703
+ }
5704
+ )
5705
+ ] }),
5706
+ /* @__PURE__ */ jsxs("div", { className: "row", style: { margin: 0 }, children: [
5707
+ /* @__PURE__ */ jsx("label", { htmlFor: "ai-dev-custom-height", children: "Height" }),
5708
+ /* @__PURE__ */ jsx(
5709
+ "input",
5710
+ {
5711
+ id: "ai-dev-custom-height",
5712
+ type: "number",
5713
+ min: 320,
5714
+ max: 3840,
5715
+ step: 1,
5716
+ value: menuHeight,
5717
+ onChange: (event) => setMenuHeight(event.target.value)
5718
+ }
5719
+ )
5720
+ ] })
5721
+ ] }),
5722
+ /* @__PURE__ */ jsx("div", { className: "ai-vp-presets vp-presets", id: "dev-preset-list", children: [
5723
+ { key: "desktop", label: "Desktop" },
5724
+ { key: "mobile", label: "iPhone 13" },
5725
+ { key: "galaxy-s22", label: "Galaxy S22" },
5726
+ { key: "tablet", label: "iPad" },
5727
+ { key: "galaxy-j1", label: "Galaxy J1" },
5728
+ { key: "iphone-7", label: "iPhone 7" },
5729
+ { key: "responsive", label: "Responsive" }
5730
+ ].map((preset) => /* @__PURE__ */ jsx(
5731
+ "button",
5732
+ {
5733
+ type: "button",
5734
+ onClick: () => setViewportFromPreset(preset.key),
5735
+ className: `ai-vp-preset-btn ${viewportPreset === preset.key ? "active" : ""}`,
5736
+ children: preset.label
5737
+ },
5738
+ preset.key
5739
+ )) }),
5740
+ /* @__PURE__ */ jsx("div", { className: "ai-dev-ft", children: /* @__PURE__ */ jsx(
5741
+ "button",
5742
+ {
5743
+ type: "button",
5744
+ onClick: applyCustomViewport,
5745
+ className: "ai-apply-btn",
5746
+ children: "Apply"
5747
+ }
5748
+ ) })
5749
+ ]
5750
+ }
5751
+ ) : null
5752
+ ] })
5753
+ ] }),
5754
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5755
+ variations.length > 0 ? /* @__PURE__ */ jsx(
5756
+ "select",
5757
+ {
5758
+ className: "h-8 max-w-[220px] rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-700",
5759
+ value: activeVariationId ?? "",
5760
+ onChange: (event) => handleVariationSelectChange(event.target.value || null),
5761
+ "aria-label": "Select variation",
5762
+ children: variations.map((variation) => /* @__PURE__ */ jsx("option", { value: variation._id, children: variation.name }, variation._id))
5763
+ }
5764
+ ) : null,
5765
+ onRequestSave ? /* @__PURE__ */ jsx(
5766
+ "button",
5767
+ {
5768
+ type: "button",
5769
+ className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-indigo-200 text-indigo-700 bg-indigo-50 hover:bg-indigo-100 hover:border-indigo-300 transition-all disabled:opacity-60 disabled:cursor-not-allowed",
5770
+ onClick: () => void handleSave(),
5771
+ disabled: isSaving,
5772
+ children: isSaving ? "Saving..." : "Save"
5773
+ }
5774
+ ) : null,
5775
+ showCloseButton ? /* @__PURE__ */ jsx(
5776
+ "button",
5777
+ {
5778
+ type: "button",
5779
+ className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50 hover:border-slate-300 transition-all",
5780
+ onClick: () => onClose?.(),
5781
+ children: closeLabel
5782
+ }
5783
+ ) : null
5784
+ ] })
5785
+ ] }) : null,
5786
+ /* @__PURE__ */ jsxs("div", { className: `${editorClassName} relative`, children: [
5787
+ /* @__PURE__ */ jsx("div", { ref: iframePanelRef, className: "w-full h-full bg-slate-100 overflow-auto flex justify-center items-start p-3", children: /* @__PURE__ */ jsx(
5788
+ "div",
5312
5789
  {
5313
- type: "button",
5314
- className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50 hover:border-slate-300 transition-all",
5315
- onClick: () => onClose?.(),
5316
- children: closeLabel
5790
+ className: "h-full bg-white border border-slate-200 shadow-sm overflow-hidden",
5791
+ style: {
5792
+ width: `${viewportWidth}px`,
5793
+ minWidth: `${viewportWidth}px`,
5794
+ height: `${viewportHeight}px`,
5795
+ maxWidth: "none",
5796
+ zoom: String(appliedViewportZoom)
5797
+ },
5798
+ children: /* @__PURE__ */ jsx(
5799
+ "iframe",
5800
+ {
5801
+ ref: iframeRef,
5802
+ src: editorSrc,
5803
+ onLoad: handleIframeLoad,
5804
+ className: "w-full h-full border-0",
5805
+ title: "AI Editor Preview",
5806
+ allow: "same-origin"
5807
+ },
5808
+ iframeKey
5809
+ )
5317
5810
  }
5318
- ) : null
5811
+ ) }),
5812
+ isIframeLoading ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-slate-100/95 backdrop-blur-[1px]", children: /* @__PURE__ */ jsxs("div", { className: "w-[560px] max-w-[88vw]", children: [
5813
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-center text-base font-semibold text-slate-700", children: "Loading changes..." }),
5814
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded-full border border-slate-300 bg-white/90 p-1 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden rounded-full bg-slate-200", children: /* @__PURE__ */ jsx("div", { className: "h-full w-1/3 rounded-full bg-gradient-to-r from-emerald-300 via-sky-500 to-indigo-600 animate-pulse" }) }) })
5815
+ ] }) }) : null,
5816
+ chatComponent ?? null
5319
5817
  ] })
5320
- ] }) : null,
5321
- /* @__PURE__ */ jsxs("div", { className: `${editorClassName} relative`, children: [
5322
- /* @__PURE__ */ jsx("div", { className: "w-full h-full bg-slate-100 overflow-auto flex justify-center items-start p-3", children: /* @__PURE__ */ jsx(
5323
- "div",
5324
- {
5325
- className: "h-full bg-white border border-slate-200 shadow-sm overflow-hidden",
5326
- style: {
5327
- width: iframeWidth,
5328
- minWidth: iframeWidth,
5329
- maxWidth: "100%"
5330
- },
5331
- children: /* @__PURE__ */ jsx(
5332
- "iframe",
5333
- {
5334
- ref: iframeRef,
5335
- src: editorSrc,
5336
- onLoad: handleIframeLoad,
5337
- className: "w-full h-full border-0",
5338
- title: "AI Editor Preview",
5339
- allow: "same-origin"
5340
- },
5341
- iframeKey
5342
- )
5343
- }
5344
- ) }),
5345
- isIframeLoading ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-slate-100/95 backdrop-blur-[1px]", children: /* @__PURE__ */ jsxs("div", { className: "w-[560px] max-w-[88vw]", children: [
5346
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-center text-base font-semibold text-slate-700", children: "Loading changes..." }),
5347
- /* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded-full border border-slate-300 bg-white/90 p-1 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden rounded-full bg-slate-200", children: /* @__PURE__ */ jsx("div", { className: "h-full w-1/3 rounded-full bg-gradient-to-r from-emerald-300 via-sky-500 to-indigo-600 animate-pulse" }) }) })
5348
- ] }) }) : null,
5349
- chatComponent ?? null
5350
5818
  ] })
5351
5819
  ] });
5352
5820
  }
package/dist/vite.cjs CHANGED
@@ -17,22 +17,114 @@ var DEFAULT_TRACKING_MARKERS = [
17
17
  "googletagmanager",
18
18
  "google-analytics",
19
19
  "googleads",
20
+ "googleadservices",
21
+ "googlesyndication",
20
22
  "doubleclick",
21
23
  "gtag(",
22
24
  "ga(",
23
25
  "fbq(",
24
- "clarity",
26
+ "connect.facebook.net",
27
+ "clarity.ms",
25
28
  "hotjar",
26
29
  "segment.com",
30
+ "segment.io",
31
+ "segmentapis",
27
32
  "mixpanel",
28
- "amplitude",
33
+ "amplitude.com",
34
+ "api.amplitude",
29
35
  "fullstory",
30
36
  "northbeam",
31
37
  "nb-collector",
38
+ "j.northbeam.io",
32
39
  "/api/collect",
33
40
  "/nb-collector",
34
41
  '"r":"periodic"',
35
- '"r": "periodic"'
42
+ '"r": "periodic"',
43
+ "shopify-pixel",
44
+ "config-security.com",
45
+ "heap.io",
46
+ "heapanalytics",
47
+ "posthog",
48
+ "matomo",
49
+ "piwik",
50
+ "plausible.io",
51
+ "umami",
52
+ "statcounter",
53
+ "kissmetrics",
54
+ "pendo.io",
55
+ "mouseflow",
56
+ "quantserve",
57
+ "quantcast",
58
+ "chartbeat",
59
+ "parse.ly",
60
+ "parsely",
61
+ "optimizely",
62
+ "logrocket",
63
+ "smartlook",
64
+ "inspectlet",
65
+ "crazyegg",
66
+ "lucky-orange",
67
+ "luckyorange",
68
+ "vwo.com",
69
+ "contentsquare",
70
+ "decibel",
71
+ "medallia",
72
+ "bat.bing.com",
73
+ "bing.com/action",
74
+ "analytics.tiktok.com",
75
+ "tiktok-pixel",
76
+ "snap.licdn.com",
77
+ "/li/track",
78
+ "ads-twitter.com",
79
+ "static.ads-twitter",
80
+ "pinimg.com",
81
+ "pinterest-tag",
82
+ "reddit.com/api/v2/conversions",
83
+ "redditstatic",
84
+ "criteo",
85
+ "adroll",
86
+ "outbrain",
87
+ "amazon-adsystem",
88
+ "adsystem.amazon",
89
+ "q.quora.com",
90
+ "quora-pixel",
91
+ "tealium",
92
+ "rudderstack",
93
+ "mparticle",
94
+ "freshpaint",
95
+ "jitsu",
96
+ "customer.io",
97
+ "hs-analytics",
98
+ "hsforms",
99
+ "hubspot",
100
+ "marketo",
101
+ "munchkin",
102
+ "pardot",
103
+ "intercom",
104
+ "drift",
105
+ "klaviyo",
106
+ "omnisend",
107
+ "attentive",
108
+ "sentry",
109
+ "bugsnag",
110
+ "datadog",
111
+ "dd-rum",
112
+ "browser-intake-datadoghq",
113
+ "newrelic",
114
+ "nr-data",
115
+ "rollbar",
116
+ "cloudflareinsights",
117
+ "vercel-insights",
118
+ "_vercel/insights",
119
+ "vercel-speed-insights",
120
+ "plausible",
121
+ "usermaven",
122
+ "freshmarketer",
123
+ "mc.yandex",
124
+ "yandex.ru/metrika",
125
+ "hm.baidu.com",
126
+ "getclicky",
127
+ "clicky.com"
36
128
  ];
37
129
  function normalizeTrackingMarkers(input) {
38
130
  if (!Array.isArray(input)) return [];
@@ -67,8 +159,17 @@ function parseTrackingMarkersParam(raw) {
67
159
  }
68
160
  function hasTrackingMarker(input, markers) {
69
161
  const text = String(input || "").toLowerCase();
162
+ const compactText = text.replace(/[^a-z0-9]/g, "");
70
163
  for (let i = 0; i < markers.length; i += 1) {
71
- if (text.includes(markers[i])) return true;
164
+ const marker = String(markers[i] || "").toLowerCase().trim();
165
+ if (!marker) continue;
166
+ if (text.includes(marker)) return true;
167
+ const markerNoProto = marker.replace(/^https?:\/\//, "");
168
+ const markerNoWww = markerNoProto.replace(/^www\./, "");
169
+ const compactMarker = markerNoWww.replace(/[^a-z0-9]/g, "");
170
+ if (markerNoProto && text.includes(markerNoProto)) return true;
171
+ if (markerNoWww && text.includes(markerNoWww)) return true;
172
+ if (compactMarker && compactText.includes(compactMarker)) return true;
72
173
  }
73
174
  return false;
74
175
  }
@@ -80,8 +181,30 @@ function patchKnownUnsafeEditorPatterns(scriptTag) {
80
181
  );
81
182
  return out;
82
183
  }
184
+ function escapeHtmlAttribute(value) {
185
+ return String(value || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
186
+ }
187
+ function getFirstMatchedTrackingMarker(input, markers) {
188
+ const text = String(input || "").toLowerCase();
189
+ for (let i = 0; i < markers.length; i += 1) {
190
+ if (text.includes(markers[i])) return markers[i];
191
+ }
192
+ return "";
193
+ }
194
+ function describeRemovedTrackingTag(tag, markers) {
195
+ const srcMatch = String(tag || "").match(/\bsrc\s*=\s*(["'])(.*?)\1/i);
196
+ const src = srcMatch?.[2]?.trim();
197
+ if (src) return `script:${src}`;
198
+ const idMatch = String(tag || "").match(/\bid\s*=\s*(["'])(.*?)\1/i);
199
+ const id = idMatch?.[2]?.trim();
200
+ if (id) return `tag#${id}`;
201
+ const marker = getFirstMatchedTrackingMarker(tag, markers);
202
+ if (marker) return `marker:${marker}`;
203
+ return "inline-script";
204
+ }
83
205
  function stripTrackingScriptsFromScrapedHtml(html, markers) {
84
206
  let removedCount = 0;
207
+ const removedScripts = [];
85
208
  let out = html;
86
209
  out = out.replace(/<script\b[\s\S]*?<\/script>/gi, (tag) => {
87
210
  var patchedTag = patchKnownUnsafeEditorPatterns(tag);
@@ -89,6 +212,9 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
89
212
  const src = srcMatch?.[2] || "";
90
213
  if (hasTrackingMarker(src, markers) || hasTrackingMarker(tag, markers)) {
91
214
  removedCount += 1;
215
+ if (removedScripts.length < 25) {
216
+ removedScripts.push(describeRemovedTrackingTag(tag, markers).slice(0, 240));
217
+ }
92
218
  return "";
93
219
  }
94
220
  return patchedTag;
@@ -96,13 +222,18 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
96
222
  out = out.replace(/<noscript\b[\s\S]*?<\/noscript>/gi, (tag) => {
97
223
  if (!hasTrackingMarker(tag, markers)) return tag;
98
224
  removedCount += 1;
225
+ if (removedScripts.length < 25) {
226
+ const marker = getFirstMatchedTrackingMarker(tag, markers);
227
+ removedScripts.push(`noscript:${(marker || "tracking").slice(0, 240)}`);
228
+ }
99
229
  return "";
100
230
  });
101
231
  if (removedCount > 0) {
232
+ const dataScriptsValue = removedScripts.length ? ` data-scripts="${escapeHtmlAttribute(removedScripts.join(" | "))}"` : "";
102
233
  out = out.replace(
103
234
  /<head([^>]*)>/i,
104
235
  `<head$1>
105
- <meta name="conversion-editor-tracking-scripts-stripped" content="${removedCount}">
236
+ <meta name="conversion-editor-tracking-scripts-stripped" content="${removedCount}"${dataScriptsValue}>
106
237
  `
107
238
  );
108
239
  }
@@ -1377,15 +1508,29 @@ function showEditorNotification(message, kind, durationMs) {
1377
1508
  }, Math.max(1200, durationMs || 2600));
1378
1509
  }
1379
1510
 
1511
+ function resolveSimulationId(source, keys) {
1512
+ var obj = source || {};
1513
+ var arr = Array.isArray(keys) ? keys : [];
1514
+ for (var i = 0; i < arr.length; i++) {
1515
+ var key = arr[i];
1516
+ var value = obj[key];
1517
+ if (value === undefined || value === null) continue;
1518
+ // Preserve numeric ids (including 0), but reject empty-string ids.
1519
+ if (typeof value === 'string' && value.trim() === '') continue;
1520
+ return value;
1521
+ }
1522
+ return null;
1523
+ }
1524
+
1380
1525
  function generatePreviewUrlString(args) {
1381
1526
  var baseUrl = (args && args.url) || '';
1382
1527
  var test = (args && args.test) || {};
1383
1528
  var variation = (args && args.variation) || {};
1384
1529
  if (!baseUrl) return '';
1385
- var testId = test.iid || '';
1386
- var variationId = variation.iid || '';
1387
- if (!testId || !variationId) return '';
1388
- var cId = String(testId || '') + '_' + String(variationId || '');
1530
+ var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
1531
+ var variationId = resolveSimulationId(variation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
1532
+ if (testId === null || variationId === null) return '';
1533
+ var cId = String(testId) + '_' + String(variationId);
1389
1534
  var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
1390
1535
  return (
1391
1536
  baseUrl +
@@ -1408,8 +1553,10 @@ function simulateExperiment() {
1408
1553
  test.pageUrl ||
1409
1554
  (test.metadata_1 && test.metadata_1.editor_url) ||
1410
1555
  '';
1411
- if (!test.iid || !activeVariation || !activeVariation.iid) {
1412
- showEditorNotification('Cannot simulate: missing test.iid or variation.iid.', 'error', 3200);
1556
+ var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
1557
+ var variationId = resolveSimulationId(activeVariation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
1558
+ if (testId === null || !activeVariation || variationId === null) {
1559
+ showEditorNotification('Cannot simulate: missing test/variation id.', 'error', 3200);
1413
1560
  return;
1414
1561
  }
1415
1562
  var url = generatePreviewUrlString({
@@ -6243,6 +6390,7 @@ function createVisualEditorMiddleware(options) {
6243
6390
  const trackingMarkersForRequest = mergeTrackingMarkers(
6244
6391
  extraTrackingMarkersForRequest
6245
6392
  );
6393
+ console.log("trackingMarkersForRequest", trackingMarkersForRequest);
6246
6394
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
6247
6395
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
6248
6396
  if (!targetUrl) {
@@ -6434,6 +6582,7 @@ var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
6434
6582
  var PROXY_PASSWORD=${JSON.stringify(password)};
6435
6583
  var PROXY_BASE_URL="";
6436
6584
  var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
6585
+ var EXTRA_TRACKING_MARKERS=${JSON.stringify(trackingMarkersForRequest)};
6437
6586
  var PARENT_URL_CHANNEL="vvveb-proxy-url";
6438
6587
  window.__CONVERSION_EDITOR_ACTIVE__=true;
6439
6588
  window.__EDITOR_MODE__=true;
@@ -6474,9 +6623,11 @@ function isPeriodicCollectPayload(data){
6474
6623
  }
6475
6624
  function shouldBlockEditorTracking(rawUrl){
6476
6625
  try{
6626
+ var rawString=String(rawUrl||"");
6477
6627
  var u=new URL(String(rawUrl||""),window.location.href);
6478
6628
  var host=(u.hostname||"").toLowerCase();
6479
6629
  var path=(u.pathname||"").toLowerCase();
6630
+ var full=(u.toString()||"").toLowerCase();
6480
6631
  var q=u.searchParams;
6481
6632
  var hasViewportPing=q.has("vp")&&(q.has("pp_miy")||q.has("pp_may"));
6482
6633
  var hasSessionIds=q.has("sid")||q.has("duid");
@@ -6486,11 +6637,12 @@ function shouldBlockEditorTracking(rawUrl){
6486
6637
  var isTaboolaUnip=host==="trc-events.taboola.com"&&/\\/log\\/\\d+\\/unip(?:\\/|$)/.test(path);
6487
6638
  var isCollectApiPath=path==="/api/collect"||path.indexOf("/api/collect/")===0;
6488
6639
  var isNbCollectorPath=path==="/nb-collector"||path.indexOf("/nb-collector/")===0;
6489
- return isCrossOriginCollector||(looksLikeHeartbeatPayload&&isSnowplowStylePath)||isCollectApiPath||isTaboolaUnip||isNbCollectorPath;
6640
+ var hasExtraMarker=hasMarker(host,EXTRA_TRACKING_MARKERS)||hasMarker(path,EXTRA_TRACKING_MARKERS)||hasMarker(full,EXTRA_TRACKING_MARKERS)||hasMarker(rawString,EXTRA_TRACKING_MARKERS);
6641
+ return isCrossOriginCollector||(looksLikeHeartbeatPayload&&isSnowplowStylePath)||isCollectApiPath||isTaboolaUnip||isNbCollectorPath||hasExtraMarker;
6490
6642
  }catch(_){
6491
6643
  try{
6492
6644
  var s=String(rawUrl||"").toLowerCase();
6493
- return s.indexOf("snowplow")!==-1||s.indexOf("com.snowplowanalytics")!==-1||s.indexOf("collector")!==-1||s.indexOf("/api/collect")!==-1||s.indexOf("trc-events.taboola.com")!==-1||s.indexOf("/nb-collector")!==-1||s.indexOf("northbeam")!==-1;
6645
+ return s.indexOf("snowplow")!==-1||s.indexOf("com.snowplowanalytics")!==-1||s.indexOf("collector")!==-1||s.indexOf("/api/collect")!==-1||s.indexOf("trc-events.taboola.com")!==-1||s.indexOf("/nb-collector")!==-1||s.indexOf("northbeam")!==-1||hasMarker(s,EXTRA_TRACKING_MARKERS);
6494
6646
  }catch(__){
6495
6647
  return false;
6496
6648
  }
package/dist/vite.js CHANGED
@@ -9,22 +9,114 @@ var DEFAULT_TRACKING_MARKERS = [
9
9
  "googletagmanager",
10
10
  "google-analytics",
11
11
  "googleads",
12
+ "googleadservices",
13
+ "googlesyndication",
12
14
  "doubleclick",
13
15
  "gtag(",
14
16
  "ga(",
15
17
  "fbq(",
16
- "clarity",
18
+ "connect.facebook.net",
19
+ "clarity.ms",
17
20
  "hotjar",
18
21
  "segment.com",
22
+ "segment.io",
23
+ "segmentapis",
19
24
  "mixpanel",
20
- "amplitude",
25
+ "amplitude.com",
26
+ "api.amplitude",
21
27
  "fullstory",
22
28
  "northbeam",
23
29
  "nb-collector",
30
+ "j.northbeam.io",
24
31
  "/api/collect",
25
32
  "/nb-collector",
26
33
  '"r":"periodic"',
27
- '"r": "periodic"'
34
+ '"r": "periodic"',
35
+ "shopify-pixel",
36
+ "config-security.com",
37
+ "heap.io",
38
+ "heapanalytics",
39
+ "posthog",
40
+ "matomo",
41
+ "piwik",
42
+ "plausible.io",
43
+ "umami",
44
+ "statcounter",
45
+ "kissmetrics",
46
+ "pendo.io",
47
+ "mouseflow",
48
+ "quantserve",
49
+ "quantcast",
50
+ "chartbeat",
51
+ "parse.ly",
52
+ "parsely",
53
+ "optimizely",
54
+ "logrocket",
55
+ "smartlook",
56
+ "inspectlet",
57
+ "crazyegg",
58
+ "lucky-orange",
59
+ "luckyorange",
60
+ "vwo.com",
61
+ "contentsquare",
62
+ "decibel",
63
+ "medallia",
64
+ "bat.bing.com",
65
+ "bing.com/action",
66
+ "analytics.tiktok.com",
67
+ "tiktok-pixel",
68
+ "snap.licdn.com",
69
+ "/li/track",
70
+ "ads-twitter.com",
71
+ "static.ads-twitter",
72
+ "pinimg.com",
73
+ "pinterest-tag",
74
+ "reddit.com/api/v2/conversions",
75
+ "redditstatic",
76
+ "criteo",
77
+ "adroll",
78
+ "outbrain",
79
+ "amazon-adsystem",
80
+ "adsystem.amazon",
81
+ "q.quora.com",
82
+ "quora-pixel",
83
+ "tealium",
84
+ "rudderstack",
85
+ "mparticle",
86
+ "freshpaint",
87
+ "jitsu",
88
+ "customer.io",
89
+ "hs-analytics",
90
+ "hsforms",
91
+ "hubspot",
92
+ "marketo",
93
+ "munchkin",
94
+ "pardot",
95
+ "intercom",
96
+ "drift",
97
+ "klaviyo",
98
+ "omnisend",
99
+ "attentive",
100
+ "sentry",
101
+ "bugsnag",
102
+ "datadog",
103
+ "dd-rum",
104
+ "browser-intake-datadoghq",
105
+ "newrelic",
106
+ "nr-data",
107
+ "rollbar",
108
+ "cloudflareinsights",
109
+ "vercel-insights",
110
+ "_vercel/insights",
111
+ "vercel-speed-insights",
112
+ "plausible",
113
+ "usermaven",
114
+ "freshmarketer",
115
+ "mc.yandex",
116
+ "yandex.ru/metrika",
117
+ "hm.baidu.com",
118
+ "getclicky",
119
+ "clicky.com"
28
120
  ];
29
121
  function normalizeTrackingMarkers(input) {
30
122
  if (!Array.isArray(input)) return [];
@@ -59,8 +151,17 @@ function parseTrackingMarkersParam(raw) {
59
151
  }
60
152
  function hasTrackingMarker(input, markers) {
61
153
  const text = String(input || "").toLowerCase();
154
+ const compactText = text.replace(/[^a-z0-9]/g, "");
62
155
  for (let i = 0; i < markers.length; i += 1) {
63
- if (text.includes(markers[i])) return true;
156
+ const marker = String(markers[i] || "").toLowerCase().trim();
157
+ if (!marker) continue;
158
+ if (text.includes(marker)) return true;
159
+ const markerNoProto = marker.replace(/^https?:\/\//, "");
160
+ const markerNoWww = markerNoProto.replace(/^www\./, "");
161
+ const compactMarker = markerNoWww.replace(/[^a-z0-9]/g, "");
162
+ if (markerNoProto && text.includes(markerNoProto)) return true;
163
+ if (markerNoWww && text.includes(markerNoWww)) return true;
164
+ if (compactMarker && compactText.includes(compactMarker)) return true;
64
165
  }
65
166
  return false;
66
167
  }
@@ -72,8 +173,30 @@ function patchKnownUnsafeEditorPatterns(scriptTag) {
72
173
  );
73
174
  return out;
74
175
  }
176
+ function escapeHtmlAttribute(value) {
177
+ return String(value || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
178
+ }
179
+ function getFirstMatchedTrackingMarker(input, markers) {
180
+ const text = String(input || "").toLowerCase();
181
+ for (let i = 0; i < markers.length; i += 1) {
182
+ if (text.includes(markers[i])) return markers[i];
183
+ }
184
+ return "";
185
+ }
186
+ function describeRemovedTrackingTag(tag, markers) {
187
+ const srcMatch = String(tag || "").match(/\bsrc\s*=\s*(["'])(.*?)\1/i);
188
+ const src = srcMatch?.[2]?.trim();
189
+ if (src) return `script:${src}`;
190
+ const idMatch = String(tag || "").match(/\bid\s*=\s*(["'])(.*?)\1/i);
191
+ const id = idMatch?.[2]?.trim();
192
+ if (id) return `tag#${id}`;
193
+ const marker = getFirstMatchedTrackingMarker(tag, markers);
194
+ if (marker) return `marker:${marker}`;
195
+ return "inline-script";
196
+ }
75
197
  function stripTrackingScriptsFromScrapedHtml(html, markers) {
76
198
  let removedCount = 0;
199
+ const removedScripts = [];
77
200
  let out = html;
78
201
  out = out.replace(/<script\b[\s\S]*?<\/script>/gi, (tag) => {
79
202
  var patchedTag = patchKnownUnsafeEditorPatterns(tag);
@@ -81,6 +204,9 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
81
204
  const src = srcMatch?.[2] || "";
82
205
  if (hasTrackingMarker(src, markers) || hasTrackingMarker(tag, markers)) {
83
206
  removedCount += 1;
207
+ if (removedScripts.length < 25) {
208
+ removedScripts.push(describeRemovedTrackingTag(tag, markers).slice(0, 240));
209
+ }
84
210
  return "";
85
211
  }
86
212
  return patchedTag;
@@ -88,13 +214,18 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
88
214
  out = out.replace(/<noscript\b[\s\S]*?<\/noscript>/gi, (tag) => {
89
215
  if (!hasTrackingMarker(tag, markers)) return tag;
90
216
  removedCount += 1;
217
+ if (removedScripts.length < 25) {
218
+ const marker = getFirstMatchedTrackingMarker(tag, markers);
219
+ removedScripts.push(`noscript:${(marker || "tracking").slice(0, 240)}`);
220
+ }
91
221
  return "";
92
222
  });
93
223
  if (removedCount > 0) {
224
+ const dataScriptsValue = removedScripts.length ? ` data-scripts="${escapeHtmlAttribute(removedScripts.join(" | "))}"` : "";
94
225
  out = out.replace(
95
226
  /<head([^>]*)>/i,
96
227
  `<head$1>
97
- <meta name="conversion-editor-tracking-scripts-stripped" content="${removedCount}">
228
+ <meta name="conversion-editor-tracking-scripts-stripped" content="${removedCount}"${dataScriptsValue}>
98
229
  `
99
230
  );
100
231
  }
@@ -1369,15 +1500,29 @@ function showEditorNotification(message, kind, durationMs) {
1369
1500
  }, Math.max(1200, durationMs || 2600));
1370
1501
  }
1371
1502
 
1503
+ function resolveSimulationId(source, keys) {
1504
+ var obj = source || {};
1505
+ var arr = Array.isArray(keys) ? keys : [];
1506
+ for (var i = 0; i < arr.length; i++) {
1507
+ var key = arr[i];
1508
+ var value = obj[key];
1509
+ if (value === undefined || value === null) continue;
1510
+ // Preserve numeric ids (including 0), but reject empty-string ids.
1511
+ if (typeof value === 'string' && value.trim() === '') continue;
1512
+ return value;
1513
+ }
1514
+ return null;
1515
+ }
1516
+
1372
1517
  function generatePreviewUrlString(args) {
1373
1518
  var baseUrl = (args && args.url) || '';
1374
1519
  var test = (args && args.test) || {};
1375
1520
  var variation = (args && args.variation) || {};
1376
1521
  if (!baseUrl) return '';
1377
- var testId = test.iid || '';
1378
- var variationId = variation.iid || '';
1379
- if (!testId || !variationId) return '';
1380
- var cId = String(testId || '') + '_' + String(variationId || '');
1522
+ var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
1523
+ var variationId = resolveSimulationId(variation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
1524
+ if (testId === null || variationId === null) return '';
1525
+ var cId = String(testId) + '_' + String(variationId);
1381
1526
  var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
1382
1527
  return (
1383
1528
  baseUrl +
@@ -1400,8 +1545,10 @@ function simulateExperiment() {
1400
1545
  test.pageUrl ||
1401
1546
  (test.metadata_1 && test.metadata_1.editor_url) ||
1402
1547
  '';
1403
- if (!test.iid || !activeVariation || !activeVariation.iid) {
1404
- showEditorNotification('Cannot simulate: missing test.iid or variation.iid.', 'error', 3200);
1548
+ var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
1549
+ var variationId = resolveSimulationId(activeVariation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
1550
+ if (testId === null || !activeVariation || variationId === null) {
1551
+ showEditorNotification('Cannot simulate: missing test/variation id.', 'error', 3200);
1405
1552
  return;
1406
1553
  }
1407
1554
  var url = generatePreviewUrlString({
@@ -6235,6 +6382,7 @@ function createVisualEditorMiddleware(options) {
6235
6382
  const trackingMarkersForRequest = mergeTrackingMarkers(
6236
6383
  extraTrackingMarkersForRequest
6237
6384
  );
6385
+ console.log("trackingMarkersForRequest", trackingMarkersForRequest);
6238
6386
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
6239
6387
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
6240
6388
  if (!targetUrl) {
@@ -6426,6 +6574,7 @@ var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
6426
6574
  var PROXY_PASSWORD=${JSON.stringify(password)};
6427
6575
  var PROXY_BASE_URL="";
6428
6576
  var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
6577
+ var EXTRA_TRACKING_MARKERS=${JSON.stringify(trackingMarkersForRequest)};
6429
6578
  var PARENT_URL_CHANNEL="vvveb-proxy-url";
6430
6579
  window.__CONVERSION_EDITOR_ACTIVE__=true;
6431
6580
  window.__EDITOR_MODE__=true;
@@ -6466,9 +6615,11 @@ function isPeriodicCollectPayload(data){
6466
6615
  }
6467
6616
  function shouldBlockEditorTracking(rawUrl){
6468
6617
  try{
6618
+ var rawString=String(rawUrl||"");
6469
6619
  var u=new URL(String(rawUrl||""),window.location.href);
6470
6620
  var host=(u.hostname||"").toLowerCase();
6471
6621
  var path=(u.pathname||"").toLowerCase();
6622
+ var full=(u.toString()||"").toLowerCase();
6472
6623
  var q=u.searchParams;
6473
6624
  var hasViewportPing=q.has("vp")&&(q.has("pp_miy")||q.has("pp_may"));
6474
6625
  var hasSessionIds=q.has("sid")||q.has("duid");
@@ -6478,11 +6629,12 @@ function shouldBlockEditorTracking(rawUrl){
6478
6629
  var isTaboolaUnip=host==="trc-events.taboola.com"&&/\\/log\\/\\d+\\/unip(?:\\/|$)/.test(path);
6479
6630
  var isCollectApiPath=path==="/api/collect"||path.indexOf("/api/collect/")===0;
6480
6631
  var isNbCollectorPath=path==="/nb-collector"||path.indexOf("/nb-collector/")===0;
6481
- return isCrossOriginCollector||(looksLikeHeartbeatPayload&&isSnowplowStylePath)||isCollectApiPath||isTaboolaUnip||isNbCollectorPath;
6632
+ var hasExtraMarker=hasMarker(host,EXTRA_TRACKING_MARKERS)||hasMarker(path,EXTRA_TRACKING_MARKERS)||hasMarker(full,EXTRA_TRACKING_MARKERS)||hasMarker(rawString,EXTRA_TRACKING_MARKERS);
6633
+ return isCrossOriginCollector||(looksLikeHeartbeatPayload&&isSnowplowStylePath)||isCollectApiPath||isTaboolaUnip||isNbCollectorPath||hasExtraMarker;
6482
6634
  }catch(_){
6483
6635
  try{
6484
6636
  var s=String(rawUrl||"").toLowerCase();
6485
- return s.indexOf("snowplow")!==-1||s.indexOf("com.snowplowanalytics")!==-1||s.indexOf("collector")!==-1||s.indexOf("/api/collect")!==-1||s.indexOf("trc-events.taboola.com")!==-1||s.indexOf("/nb-collector")!==-1||s.indexOf("northbeam")!==-1;
6637
+ return s.indexOf("snowplow")!==-1||s.indexOf("com.snowplowanalytics")!==-1||s.indexOf("collector")!==-1||s.indexOf("/api/collect")!==-1||s.indexOf("trc-events.taboola.com")!==-1||s.indexOf("/nb-collector")!==-1||s.indexOf("northbeam")!==-1||hasMarker(s,EXTRA_TRACKING_MARKERS);
6486
6638
  }catch(__){
6487
6639
  return false;
6488
6640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.4.9",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",