@accelerated-agency/visual-editor 0.6.0 → 0.6.2

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.
Files changed (3) hide show
  1. package/dist/vite.cjs +338 -29
  2. package/dist/vite.js +338 -29
  3. package/package.json +1 -1
package/dist/vite.cjs CHANGED
@@ -2194,10 +2194,13 @@ function simulateExperiment() {
2194
2194
  variations[0];
2195
2195
  }
2196
2196
  var targetUrl =
2197
+ (test.metadata_1 && test.metadata_1.editor_url) ||
2197
2198
  (Array.isArray(test.urltargeting) && test.urltargeting[0]) ||
2198
2199
  test.pageUrl ||
2199
- (test.metadata_1 && test.metadata_1.editor_url) ||
2200
2200
  '';
2201
+ if (targetUrl && !/^https?:///i.test(String(targetUrl).trim())) {
2202
+ targetUrl = 'https://' + String(targetUrl).trim();
2203
+ }
2201
2204
  var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
2202
2205
  var variationId = resolveSimulationId(activeVariation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
2203
2206
  if (testId === null || !activeVariation || variationId === null) {
@@ -3150,6 +3153,7 @@ var PROP_META = {
3150
3153
  'pp-value': {label:'Value', cssProp:null},
3151
3154
  'pp-text': {label:'Inner text', cssProp:null},
3152
3155
  'pp-html': {label:'Inner HTML', cssProp:null},
3156
+ 'pp-select-options': {label:'Options', cssProp:null},
3153
3157
  'pp-mob-css': {label:'Mobile CSS', cssProp:null},
3154
3158
  'pp-tab-css': {label:'Tablet CSS', cssProp:null},
3155
3159
  };
@@ -3166,6 +3170,7 @@ function getOriginalValue(inputId, el) {
3166
3170
  switch (inputId) {
3167
3171
  case 'pp-text': return el.innerText || '';
3168
3172
  case 'pp-html': return el.innerHTML || '';
3173
+ case 'pp-select-options': return getSelectOptionsText(el);
3169
3174
  case 'pp-cls': return el.className || '';
3170
3175
  case 'pp-id': return el.id || '';
3171
3176
  case 'pp-href': return el.getAttribute('href') || '';
@@ -3315,6 +3320,7 @@ function revertChangeOnDom(change) {
3315
3320
  switch (change.inputId) {
3316
3321
  case 'pp-text': el.innerText = orig; break;
3317
3322
  case 'pp-html': el.innerHTML = orig; break;
3323
+ case 'pp-select-options': applySelectOptionsFromText(el, orig); break;
3318
3324
  case 'pp-cls': el.className = orig; break;
3319
3325
  case 'pp-id': el.id = orig; break;
3320
3326
  case 'pp-css': orig ? el.setAttribute('style', orig) : el.removeAttribute('style'); break;
@@ -4973,6 +4979,8 @@ function stateChangeToChainSet(c) {
4973
4979
  return { selector: c.selector, type: 'content', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4974
4980
  case 'pp-html':
4975
4981
  return { selector: c.selector, type: 'content', html: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4982
+ case 'pp-select-options':
4983
+ return { selector: c.selector, type: 'content', html: selectOptionsTextToHtml(c.value), vveTs: c.vveTs || nextHistoryTimestamp() };
4976
4984
  case 'pp-cls':
4977
4985
  return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4978
4986
  case 'pp-id':
@@ -5586,6 +5594,10 @@ function setTreeHoverHighlight(el) {
5586
5594
  }
5587
5595
 
5588
5596
  function isTreeHoverOnlyClassMutation(mutation) {
5597
+ return isEditorChromeOnlyClassMutation(mutation);
5598
+ }
5599
+
5600
+ function isEditorChromeOnlyClassMutation(mutation) {
5589
5601
  if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
5590
5602
  var oldClass = String(mutation.oldValue || '');
5591
5603
  var target = mutation.target;
@@ -5593,7 +5605,12 @@ function isTreeHoverOnlyClassMutation(mutation) {
5593
5605
  try {
5594
5606
  nextClass = target && typeof target.className === 'string' ? target.className : '';
5595
5607
  } catch(_) {}
5596
- return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
5608
+ var combined = oldClass + ' ' + nextClass;
5609
+ return (
5610
+ combined.indexOf('vve-tree-hover') >= 0 ||
5611
+ combined.indexOf('vve-selected') >= 0 ||
5612
+ combined.indexOf('vve-dragging') >= 0
5613
+ );
5597
5614
  }
5598
5615
 
5599
5616
  function setDragHandleActive(on) {
@@ -5643,7 +5660,13 @@ function positionSelectionToolbar() {
5643
5660
  if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
5644
5661
  if (selectedEl !== liveSelected) {
5645
5662
  selectedEl = liveSelected;
5646
- renderRightPanel(liveSelected);
5663
+ if (!document.activeElement || (
5664
+ document.activeElement.id !== 'pp-html' &&
5665
+ document.activeElement.id !== 'pp-text' &&
5666
+ document.activeElement.id !== 'pp-select-options'
5667
+ )) {
5668
+ renderRightPanel(liveSelected);
5669
+ }
5647
5670
  syncDomTreeSelection();
5648
5671
  }
5649
5672
  var elR = getIframeElementVisualRect(selectedEl);
@@ -6177,19 +6200,149 @@ function renderElementsTree(filterRaw) {
6177
6200
  return;
6178
6201
  }
6179
6202
 
6180
- function nodeIcon(tag) {
6181
- tag = (tag || '').toLowerCase();
6182
- if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
6183
- if (tag === 'a') return 'bi bi-link-45deg';
6184
- if (tag === 'img') return 'bi bi-image';
6185
- if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
6186
- if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
6187
- if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
6188
- if (tag === 'li') return 'bi bi-dot';
6189
- if (tag === 'svg') return 'bi bi-bezier2';
6190
- if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
6191
- return 'bi bi-square';
6192
- }
6203
+ function nodeIcon(tag) {
6204
+ tag = (tag || '').toLowerCase();
6205
+
6206
+ // Headings
6207
+ if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
6208
+
6209
+ // Document & metadata
6210
+ if (tag === 'html') return 'bi bi-filetype-html';
6211
+ if (tag === 'head') return 'bi bi-file-earmark-code';
6212
+ if (tag === 'body') return 'bi bi-file-earmark-richtext';
6213
+ if (tag === 'title') return 'bi bi-card-heading';
6214
+ if (tag === 'meta') return 'bi bi-info-circle';
6215
+ if (tag === 'link') return 'bi bi-link';
6216
+ if (tag === 'style') return 'bi bi-filetype-css';
6217
+ if (tag === 'script') return 'bi bi-filetype-js';
6218
+ if (tag === 'noscript') return 'bi bi-slash-circle';
6219
+ if (tag === 'base') return 'bi bi-house-gear';
6220
+
6221
+ // Sectioning & layout
6222
+ if (tag === 'section' || tag === 'main' || tag === 'article' ||
6223
+ tag === 'header' || tag === 'footer' || tag === 'nav' || tag === 'aside')
6224
+ return 'bi bi-layout-three-columns';
6225
+ if (tag === 'div') return 'bi bi-square';
6226
+ if (tag === 'address') return 'bi bi-geo-alt';
6227
+
6228
+ // Text content
6229
+ if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
6230
+ if (tag === 'blockquote' || tag === 'q') return 'bi bi-blockquote-left';
6231
+ if (tag === 'pre') return 'bi bi-code-square';
6232
+ if (tag === 'code') return 'bi bi-code-slash';
6233
+ if (tag === 'kbd') return 'bi bi-keyboard';
6234
+ if (tag === 'samp') return 'bi bi-terminal';
6235
+ if (tag === 'var') return 'bi bi-braces';
6236
+ if (tag === 'cite') return 'bi bi-quote';
6237
+ if (tag === 'abbr') return 'bi bi-fonts';
6238
+ if (tag === 'time') return 'bi bi-clock';
6239
+ if (tag === 'mark') return 'bi bi-highlighter';
6240
+ if (tag === 'small') return 'bi bi-type';
6241
+ if (tag === 'sub') return 'bi bi-subscript';
6242
+ if (tag === 'sup') return 'bi bi-superscript';
6243
+ if (tag === 'br') return 'bi bi-arrow-return-left';
6244
+ if (tag === 'wbr') return 'bi bi-distribute-horizontal';
6245
+
6246
+ // Inline formatting
6247
+ if (tag === 'strong' || tag === 'b') return 'bi bi-type-bold';
6248
+ if (tag === 'em' || tag === 'i') return 'bi bi-type-italic';
6249
+ if (tag === 'u' || tag === 'ins') return 'bi bi-type-underline';
6250
+ if (tag === 's' || tag === 'del' || tag === 'strike') return 'bi bi-type-strikethrough';
6251
+
6252
+ // Lists
6253
+ if (tag === 'ul') return 'bi bi-list-ul';
6254
+ if (tag === 'ol') return 'bi bi-list-ol';
6255
+ if (tag === 'li') return 'bi bi-dot';
6256
+ if (tag === 'dl') return 'bi bi-card-list';
6257
+ if (tag === 'dt') return 'bi bi-tag';
6258
+ if (tag === 'dd') return 'bi bi-text-indent-left';
6259
+
6260
+ // Links & navigation
6261
+ if (tag === 'a') return 'bi bi-link-45deg';
6262
+
6263
+ // Media
6264
+ if (tag === 'img') return 'bi bi-image';
6265
+ if (tag === 'picture') return 'bi bi-images';
6266
+ if (tag === 'figure') return 'bi bi-card-image';
6267
+ if (tag === 'figcaption') return 'bi bi-card-text';
6268
+ if (tag === 'video') return 'bi bi-camera-video';
6269
+ if (tag === 'audio') return 'bi bi-music-note-beamed';
6270
+ if (tag === 'source') return 'bi bi-cloud-arrow-down';
6271
+ if (tag === 'track') return 'bi bi-badge-cc';
6272
+ if (tag === 'iframe') return 'bi bi-window';
6273
+ if (tag === 'embed' || tag === 'object') return 'bi bi-box-arrow-in-down';
6274
+ if (tag === 'param') return 'bi bi-sliders';
6275
+ if (tag === 'canvas') return 'bi bi-easel';
6276
+ if (tag === 'map') return 'bi bi-map';
6277
+ if (tag === 'area') return 'bi bi-bounding-box';
6278
+
6279
+ // Vector / math
6280
+ if (tag === 'svg') return 'bi bi-bezier2';
6281
+ if (tag === 'path') return 'bi bi-bezier';
6282
+ if (tag === 'circle') return 'bi bi-circle';
6283
+ if (tag === 'rect') return 'bi bi-square';
6284
+ if (tag === 'line') return 'bi bi-slash-lg';
6285
+ if (tag === 'polygon') return 'bi bi-pentagon';
6286
+ if (tag === 'polyline') return 'bi bi-share';
6287
+ if (tag === 'ellipse') return 'bi bi-circle-half';
6288
+ if (tag === 'g') return 'bi bi-collection';
6289
+ if (tag === 'use') return 'bi bi-arrow-repeat';
6290
+ if (tag === 'defs') return 'bi bi-bookmark';
6291
+ if (tag === 'symbol') return 'bi bi-star';
6292
+ if (tag === 'text') return 'bi bi-fonts';
6293
+ if (tag === 'math') return 'bi bi-calculator';
6294
+
6295
+ // Forms
6296
+ if (tag === 'form') return 'bi bi-file-earmark-check';
6297
+ if (tag === 'fieldset') return 'bi bi-bounding-box-circles';
6298
+ if (tag === 'legend') return 'bi bi-tag-fill';
6299
+ if (tag === 'label') return 'bi bi-tag';
6300
+ if (tag === 'input') return 'bi bi-input-cursor-text';
6301
+ if (tag === 'button') return 'bi bi-ui-radios';
6302
+ if (tag === 'select') return 'bi bi-menu-button';
6303
+ if (tag === 'option') return 'bi bi-check2-square';
6304
+ if (tag === 'optgroup') return 'bi bi-list-nested';
6305
+ if (tag === 'textarea') return 'bi bi-textarea-resize';
6306
+ if (tag === 'datalist') return 'bi bi-list-columns';
6307
+ if (tag === 'output') return 'bi bi-box-arrow-right';
6308
+ if (tag === 'progress') return 'bi bi-bar-chart-line';
6309
+ if (tag === 'meter') return 'bi bi-speedometer2';
6310
+
6311
+ // Tables
6312
+ if (tag === 'table') return 'bi bi-table';
6313
+ if (tag === 'caption') return 'bi bi-card-heading';
6314
+ if (tag === 'thead') return 'bi bi-layout-text-window';
6315
+ if (tag === 'tbody') return 'bi bi-layout-text-sidebar';
6316
+ if (tag === 'tfoot') return 'bi bi-layout-text-window-reverse';
6317
+ if (tag === 'tr') return 'bi bi-grip-horizontal';
6318
+ if (tag === 'th') return 'bi bi-grid-3x3-gap-fill';
6319
+ if (tag === 'td') return 'bi bi-grid-3x3-gap';
6320
+ if (tag === 'col') return 'bi bi-layout-three-columns';
6321
+ if (tag === 'colgroup') return 'bi bi-columns-gap';
6322
+
6323
+ // Interactive / disclosure
6324
+ if (tag === 'details') return 'bi bi-caret-down-square';
6325
+ if (tag === 'summary') return 'bi bi-card-text';
6326
+ if (tag === 'dialog') return 'bi bi-chat-square-text';
6327
+ if (tag === 'menu') return 'bi bi-list';
6328
+
6329
+ // Web components / templating
6330
+ if (tag === 'template') return 'bi bi-file-earmark-code';
6331
+ if (tag === 'slot') return 'bi bi-box-seam';
6332
+
6333
+ // Ruby annotation
6334
+ if (tag === 'ruby' || tag === 'rt' || tag === 'rp' || tag === 'rb')
6335
+ return 'bi bi-translate';
6336
+ if (tag === 'bdi' || tag === 'bdo') return 'bi bi-arrow-left-right';
6337
+
6338
+ // Misc
6339
+ if (tag === 'hr') return 'bi bi-hr';
6340
+ if (tag === '#text' || tag === 'text-node') return 'bi bi-cursor-text';
6341
+ if (tag === '#comment') return 'bi bi-chat-left-text';
6342
+
6343
+ return 'bi bi-square';
6344
+ }
6345
+
6193
6346
 
6194
6347
  var nodes = collectEditorInsertedElements(doc);
6195
6348
 
@@ -6456,11 +6609,64 @@ function isFormControlElement(el) {
6456
6609
  function shouldShowInnerContentFields(el) {
6457
6610
  if (!el || el.nodeType !== 1) return false;
6458
6611
  var tag = (el.tagName || '').toLowerCase();
6459
- if (tag === 'input' || tag === 'textarea' || tag === 'video') return false;
6612
+ if (tag === 'input' || tag === 'textarea' || tag === 'select' || tag === 'video') return false;
6460
6613
  if (isEmbeddedVideoIframe(el)) return false;
6461
6614
  return true;
6462
6615
  }
6463
6616
 
6617
+ function getSelectOptionsText(el) {
6618
+ if (!el || el.nodeType !== 1) return '';
6619
+ try {
6620
+ var opts = el.querySelectorAll('option');
6621
+ var lines = [];
6622
+ for (var i = 0; i < opts.length; i++) {
6623
+ lines.push(opts[i].textContent || '');
6624
+ }
6625
+ return lines.join('\\n');
6626
+ } catch(_) {
6627
+ return '';
6628
+ }
6629
+ }
6630
+
6631
+ function selectOptionsTextToHtml(text) {
6632
+ var lines = String(text == null ? '' : text).split('\\n');
6633
+ var parts = [];
6634
+ for (var i = 0; i < lines.length; i++) {
6635
+ var label = lines[i];
6636
+ if (!label && i === lines.length - 1 && lines.length > 1) continue;
6637
+ parts.push('<option>' + esc(label) + '</option>');
6638
+ }
6639
+ if (!parts.length) parts.push('<option>Option 1</option>');
6640
+ return parts.join('');
6641
+ }
6642
+
6643
+ function applySelectOptionsFromText(el, text) {
6644
+ if (!el || el.nodeType !== 1) return;
6645
+ beginSuppressIframeMutationDirty();
6646
+ try {
6647
+ var prevValue = '';
6648
+ try { prevValue = el.value || ''; } catch(_) {}
6649
+ var doc = el.ownerDocument;
6650
+ while (el.firstChild) el.removeChild(el.firstChild);
6651
+ var lines = String(text == null ? '' : text).split('\\n');
6652
+ if (!lines.length || (lines.length === 1 && !String(lines[0] || '').trim())) {
6653
+ lines = ['Option 1', 'Option 2'];
6654
+ }
6655
+ for (var i = 0; i < lines.length; i++) {
6656
+ var label = lines[i];
6657
+ if (!label && i === lines.length - 1) continue;
6658
+ var opt = doc.createElement('option');
6659
+ opt.textContent = label;
6660
+ el.appendChild(opt);
6661
+ }
6662
+ if (prevValue) {
6663
+ try { el.value = prevValue; } catch(_) {}
6664
+ }
6665
+ } finally {
6666
+ endSuppressIframeMutationDirty();
6667
+ }
6668
+ }
6669
+
6464
6670
  function elementHasHtmlChildren(el) {
6465
6671
  if (!el || el.nodeType !== 1) return false;
6466
6672
  try {
@@ -7119,6 +7325,12 @@ function renderRightPanel(el, options) {
7119
7325
  subLbl('Placeholder') +
7120
7326
  '<input class="pr-inp" id="pp-ph" type="text" value="'+esc(el.getAttribute('placeholder')||'')+'" style="width:100%;margin-bottom:8px">';
7121
7327
  }
7328
+ if (tag === 'select') {
7329
+ contentHtml +=
7330
+ subLbl('Options') +
7331
+ '<div style="font-size:11px;color:var(--text-3);margin:-4px 0 8px">One option label per line</div>' +
7332
+ '<textarea class="pr-inp" id="pp-select-options" style="width:100%;min-height:80px;font-family:var(--font-mono);font-size:11px">'+esc(getSelectOptionsText(el))+'</textarea>';
7333
+ }
7122
7334
  if (shouldShowInnerContentFields(el)) {
7123
7335
  if (elementHasHtmlChildren(el)) {
7124
7336
  contentHtml +=
@@ -7226,18 +7438,49 @@ function renderRightPanel(el, options) {
7226
7438
  function wireContentFieldSync(el, sel) {
7227
7439
  var textInp = document.getElementById('pp-text');
7228
7440
  var htmlInp = document.getElementById('pp-html');
7441
+ var selectOptsInp = document.getElementById('pp-select-options');
7229
7442
  var syncing = false;
7230
- function applyContentChange(changedId) {
7443
+ var htmlApplyTimer = null;
7444
+ var selectApplyTimer = null;
7445
+
7446
+ function applyHtmlToElement(htmlValue) {
7447
+ beginSuppressIframeMutationDirty();
7448
+ try {
7449
+ el.innerHTML = htmlValue;
7450
+ } finally {
7451
+ endSuppressIframeMutationDirty();
7452
+ }
7453
+ }
7454
+
7455
+ function applyContentChange(changedId, immediate) {
7231
7456
  if (syncing) return;
7457
+ if (changedId === 'pp-html' && !immediate) {
7458
+ if (htmlApplyTimer) clearTimeout(htmlApplyTimer);
7459
+ htmlApplyTimer = setTimeout(function() { applyContentChange('pp-html', true); }, 250);
7460
+ return;
7461
+ }
7462
+ if (changedId === 'pp-select-options' && !immediate) {
7463
+ if (selectApplyTimer) clearTimeout(selectApplyTimer);
7464
+ selectApplyTimer = setTimeout(function() { applyContentChange('pp-select-options', true); }, 250);
7465
+ return;
7466
+ }
7232
7467
  syncing = true;
7233
7468
  try {
7234
7469
  var orig = getOriginalValue(changedId, el);
7235
7470
  if (changedId === 'pp-text') {
7236
- el.innerText = textInp.value;
7471
+ beginSuppressIframeMutationDirty();
7472
+ try {
7473
+ el.innerText = textInp.value;
7474
+ } finally {
7475
+ endSuppressIframeMutationDirty();
7476
+ }
7237
7477
  if (htmlInp) htmlInp.value = el.innerHTML;
7238
7478
  logChange(sel, 'pp-text', textInp.value, el, orig);
7479
+ } else if (changedId === 'pp-select-options') {
7480
+ applySelectOptionsFromText(el, selectOptsInp.value);
7481
+ logChange(sel, 'pp-select-options', selectOptsInp.value, el, orig);
7239
7482
  } else {
7240
- el.innerHTML = htmlInp.value;
7483
+ applyHtmlToElement(htmlInp.value);
7241
7484
  if (textInp) textInp.value = el.innerText;
7242
7485
  logChange(sel, 'pp-html', htmlInp.value, el, orig);
7243
7486
  }
@@ -7250,8 +7493,12 @@ function wireContentFieldSync(el, sel) {
7250
7493
  textInp.addEventListener('change', function() { applyContentChange('pp-text'); });
7251
7494
  }
7252
7495
  if (htmlInp) {
7253
- htmlInp.addEventListener('input', function() { applyContentChange('pp-html'); });
7254
- htmlInp.addEventListener('change', function() { applyContentChange('pp-html'); });
7496
+ htmlInp.addEventListener('input', function() { applyContentChange('pp-html', false); });
7497
+ htmlInp.addEventListener('change', function() { applyContentChange('pp-html', true); });
7498
+ }
7499
+ if (selectOptsInp) {
7500
+ selectOptsInp.addEventListener('input', function() { applyContentChange('pp-select-options', false); });
7501
+ selectOptsInp.addEventListener('change', function() { applyContentChange('pp-select-options', true); });
7255
7502
  }
7256
7503
  }
7257
7504
 
@@ -7742,14 +7989,21 @@ function attachChangeObserver() {
7742
7989
  changeObserverDoc = null;
7743
7990
  }
7744
7991
  changeObserver = new MutationObserver(function(mutations) {
7992
+ if (suppressIframeMutationDirty > 0) return;
7745
7993
  var hasMeaningfulMutation = false;
7746
7994
  for (var mi = 0; mi < mutations.length; mi++) {
7747
- if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
7995
+ if (!isEditorChromeOnlyClassMutation(mutations[mi])) {
7748
7996
  hasMeaningfulMutation = true;
7749
7997
  break;
7750
7998
  }
7751
7999
  }
7752
8000
  if (!hasMeaningfulMutation) return;
8001
+ var activeId = '';
8002
+ try { activeId = document.activeElement && document.activeElement.id ? String(document.activeElement.id) : ''; } catch(_) {}
8003
+ if (activeId === 'pp-html' || activeId === 'pp-text' || activeId === 'pp-select-options') {
8004
+ scheduleDomTreeRefresh();
8005
+ return;
8006
+ }
7753
8007
  // Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
7754
8008
  // Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
7755
8009
  // Keep selection sticky by re-resolving from fingerprint.
@@ -8245,7 +8499,9 @@ window.addEventListener('load', function() {
8245
8499
  return;
8246
8500
  }
8247
8501
  hideIframeLoadError();
8248
- emitEditorUrlChanged(iframe.src || docUrl);
8502
+ var iframeLiveUrl = '';
8503
+ try { iframeLiveUrl = String(iframe.contentWindow.location.href || ''); } catch(_) {}
8504
+ emitEditorUrlChanged(iframeLiveUrl || docUrl || iframe.src || '');
8249
8505
  // Stale events: src may already be the proxy URL while the document is still
8250
8506
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
8251
8507
  if (docUrl === 'about:blank') {
@@ -8630,6 +8886,11 @@ function createVisualEditorMiddleware(options) {
8630
8886
  }
8631
8887
  if (chunks.length > 0) requestBody = Buffer.concat(chunks);
8632
8888
  }
8889
+ const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
8890
+ const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
8891
+ const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
8892
+ const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
8893
+ const passthroughUpstreamRedirects = (method === "GET" || method === "HEAD") && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
8633
8894
  const upstreamTimeoutMs = 12e4;
8634
8895
  const ac = new AbortController();
8635
8896
  const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
@@ -8639,7 +8900,7 @@ function createVisualEditorMiddleware(options) {
8639
8900
  method,
8640
8901
  headers: fetchHeaders,
8641
8902
  body: requestBody ? Buffer.from(requestBody) : null,
8642
- redirect: "follow",
8903
+ redirect: passthroughUpstreamRedirects ? "manual" : "follow",
8643
8904
  signal: ac.signal
8644
8905
  });
8645
8906
  } catch (fetchErr) {
@@ -8663,12 +8924,43 @@ function createVisualEditorMiddleware(options) {
8663
8924
  return;
8664
8925
  }
8665
8926
  clearTimeout(timeoutId);
8927
+ if (passthroughUpstreamRedirects && upstream.status >= 300 && upstream.status < 400) {
8928
+ const locationHeader = upstream.headers.get("location") || upstream.headers.get("Location");
8929
+ if (locationHeader) {
8930
+ try {
8931
+ const redirectTarget = new URL(locationHeader, targetUrl);
8932
+ if (redirectTarget.origin === origin) {
8933
+ const proxyRedirect = new URL(proxyRootForRequest, "http://localhost");
8934
+ proxyRedirect.searchParams.set("password", password);
8935
+ proxyRedirect.searchParams.set("url", redirectTarget.toString());
8936
+ url.searchParams.forEach((value, key) => {
8937
+ if (key === "url" || key === "password") return;
8938
+ proxyRedirect.searchParams.set(key, value);
8939
+ });
8940
+ res.statusCode = upstream.status === 303 ? 303 : 302;
8941
+ res.setHeader("Location", `${proxyRedirect.pathname}${proxyRedirect.search}`);
8942
+ res.setHeader("Cache-Control", "no-store");
8943
+ res.setHeader("Access-Control-Allow-Origin", "*");
8944
+ setFrameHeaders(req, res);
8945
+ res.end();
8946
+ return;
8947
+ }
8948
+ res.statusCode = 502;
8949
+ res.setHeader("Content-Type", "application/json");
8950
+ res.end(
8951
+ JSON.stringify({
8952
+ error: "Cross-origin redirect blocked in editor preview",
8953
+ location: redirectTarget.toString(),
8954
+ from: targetUrl
8955
+ })
8956
+ );
8957
+ return;
8958
+ } catch (_) {
8959
+ }
8960
+ }
8961
+ }
8666
8962
  const responseContentType = upstream.headers.get("content-type") || "";
8667
8963
  const isHtmlResponse = responseContentType.includes("text/html");
8668
- const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
8669
- const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
8670
- const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
8671
- const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
8672
8964
  const shouldInjectHtmlBridge = isHtmlResponse && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
8673
8965
  if (!isHtmlResponse || !shouldInjectHtmlBridge) {
8674
8966
  const binary = Buffer.from(await upstream.arrayBuffer());
@@ -8722,6 +9014,18 @@ ${iframeAlwaysShowCssGuardScript}
8722
9014
  /<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
8723
9015
  ""
8724
9016
  );
9017
+ html = html.replace(
9018
+ /(<meta[^>]+http-equiv=["']?refresh["']?[^>]*content=["'][^"']*url=)([^"';]+)(["'][^>]*>)/gi,
9019
+ (match, prefix, urlPart, suffix) => {
9020
+ try {
9021
+ const abs = new URL(String(urlPart).trim(), origin).toString();
9022
+ if (new URL(abs).origin !== origin) return match;
9023
+ return `${prefix}${proxyBase}${encodeURIComponent(abs)}${suffix}`;
9024
+ } catch {
9025
+ return match;
9026
+ }
9027
+ }
9028
+ );
8725
9029
  const runtimePreflightScript = `<script>(function(){try{
8726
9030
  var TARGET_ORIGIN=${JSON.stringify(origin)};
8727
9031
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
@@ -8928,6 +9232,8 @@ try{if(window.history&&typeof window.history.pushState==="function"){var nativeP
8928
9232
  try{if(window.history&&typeof window.history.replaceState==="function"){var nativeReplaceState=window.history.replaceState;window.history.replaceState=function(){var ret=nativeReplaceState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
8929
9233
  try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
8930
9234
  try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
9235
+ try{window.addEventListener("pageshow",notifyEditorUrlChanged,true);}catch(_){}
9236
+ try{window.addEventListener("load",function(){setTimeout(notifyEditorUrlChanged,0);},true);}catch(_){}
8931
9237
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
8932
9238
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
8933
9239
  function toProxy(raw){
@@ -8956,6 +9262,9 @@ function toProxy(raw){
8956
9262
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
8957
9263
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
8958
9264
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
9265
+ function interceptMetaRefresh(){try{var metas=document.querySelectorAll('meta[http-equiv="refresh" i],meta[http-equiv="Refresh"]');for(var i=0;i<metas.length;i++){var content=metas[i].getAttribute("content")||"";var m=content.match(/url=(.+)$/i);if(!m)continue;var raw=m[1].trim().replace(/^['"]|['"]$/g,"");if(safeNavigate(raw,"replace"))metas[i].parentNode&&metas[i].parentNode.removeChild(metas[i]);}}catch(_){}}
9266
+ try{interceptMetaRefresh();}catch(_){}
9267
+ try{document.addEventListener("DOMContentLoaded",interceptMetaRefresh,true);}catch(_){}
8959
9268
  try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
8960
9269
  try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
8961
9270
  try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
package/dist/vite.js CHANGED
@@ -2186,10 +2186,13 @@ function simulateExperiment() {
2186
2186
  variations[0];
2187
2187
  }
2188
2188
  var targetUrl =
2189
+ (test.metadata_1 && test.metadata_1.editor_url) ||
2189
2190
  (Array.isArray(test.urltargeting) && test.urltargeting[0]) ||
2190
2191
  test.pageUrl ||
2191
- (test.metadata_1 && test.metadata_1.editor_url) ||
2192
2192
  '';
2193
+ if (targetUrl && !/^https?:///i.test(String(targetUrl).trim())) {
2194
+ targetUrl = 'https://' + String(targetUrl).trim();
2195
+ }
2193
2196
  var testId = resolveSimulationId(test, ['iid', 'experimentIid', 'experimentId', '_id', 'id']);
2194
2197
  var variationId = resolveSimulationId(activeVariation, ['iid', 'platformIid', 'variationIid', '_id', 'id']);
2195
2198
  if (testId === null || !activeVariation || variationId === null) {
@@ -3142,6 +3145,7 @@ var PROP_META = {
3142
3145
  'pp-value': {label:'Value', cssProp:null},
3143
3146
  'pp-text': {label:'Inner text', cssProp:null},
3144
3147
  'pp-html': {label:'Inner HTML', cssProp:null},
3148
+ 'pp-select-options': {label:'Options', cssProp:null},
3145
3149
  'pp-mob-css': {label:'Mobile CSS', cssProp:null},
3146
3150
  'pp-tab-css': {label:'Tablet CSS', cssProp:null},
3147
3151
  };
@@ -3158,6 +3162,7 @@ function getOriginalValue(inputId, el) {
3158
3162
  switch (inputId) {
3159
3163
  case 'pp-text': return el.innerText || '';
3160
3164
  case 'pp-html': return el.innerHTML || '';
3165
+ case 'pp-select-options': return getSelectOptionsText(el);
3161
3166
  case 'pp-cls': return el.className || '';
3162
3167
  case 'pp-id': return el.id || '';
3163
3168
  case 'pp-href': return el.getAttribute('href') || '';
@@ -3307,6 +3312,7 @@ function revertChangeOnDom(change) {
3307
3312
  switch (change.inputId) {
3308
3313
  case 'pp-text': el.innerText = orig; break;
3309
3314
  case 'pp-html': el.innerHTML = orig; break;
3315
+ case 'pp-select-options': applySelectOptionsFromText(el, orig); break;
3310
3316
  case 'pp-cls': el.className = orig; break;
3311
3317
  case 'pp-id': el.id = orig; break;
3312
3318
  case 'pp-css': orig ? el.setAttribute('style', orig) : el.removeAttribute('style'); break;
@@ -4965,6 +4971,8 @@ function stateChangeToChainSet(c) {
4965
4971
  return { selector: c.selector, type: 'content', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4966
4972
  case 'pp-html':
4967
4973
  return { selector: c.selector, type: 'content', html: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4974
+ case 'pp-select-options':
4975
+ return { selector: c.selector, type: 'content', html: selectOptionsTextToHtml(c.value), vveTs: c.vveTs || nextHistoryTimestamp() };
4968
4976
  case 'pp-cls':
4969
4977
  return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
4970
4978
  case 'pp-id':
@@ -5578,6 +5586,10 @@ function setTreeHoverHighlight(el) {
5578
5586
  }
5579
5587
 
5580
5588
  function isTreeHoverOnlyClassMutation(mutation) {
5589
+ return isEditorChromeOnlyClassMutation(mutation);
5590
+ }
5591
+
5592
+ function isEditorChromeOnlyClassMutation(mutation) {
5581
5593
  if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
5582
5594
  var oldClass = String(mutation.oldValue || '');
5583
5595
  var target = mutation.target;
@@ -5585,7 +5597,12 @@ function isTreeHoverOnlyClassMutation(mutation) {
5585
5597
  try {
5586
5598
  nextClass = target && typeof target.className === 'string' ? target.className : '';
5587
5599
  } catch(_) {}
5588
- return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
5600
+ var combined = oldClass + ' ' + nextClass;
5601
+ return (
5602
+ combined.indexOf('vve-tree-hover') >= 0 ||
5603
+ combined.indexOf('vve-selected') >= 0 ||
5604
+ combined.indexOf('vve-dragging') >= 0
5605
+ );
5589
5606
  }
5590
5607
 
5591
5608
  function setDragHandleActive(on) {
@@ -5635,7 +5652,13 @@ function positionSelectionToolbar() {
5635
5652
  if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
5636
5653
  if (selectedEl !== liveSelected) {
5637
5654
  selectedEl = liveSelected;
5638
- renderRightPanel(liveSelected);
5655
+ if (!document.activeElement || (
5656
+ document.activeElement.id !== 'pp-html' &&
5657
+ document.activeElement.id !== 'pp-text' &&
5658
+ document.activeElement.id !== 'pp-select-options'
5659
+ )) {
5660
+ renderRightPanel(liveSelected);
5661
+ }
5639
5662
  syncDomTreeSelection();
5640
5663
  }
5641
5664
  var elR = getIframeElementVisualRect(selectedEl);
@@ -6169,19 +6192,149 @@ function renderElementsTree(filterRaw) {
6169
6192
  return;
6170
6193
  }
6171
6194
 
6172
- function nodeIcon(tag) {
6173
- tag = (tag || '').toLowerCase();
6174
- if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
6175
- if (tag === 'a') return 'bi bi-link-45deg';
6176
- if (tag === 'img') return 'bi bi-image';
6177
- if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
6178
- if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
6179
- if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
6180
- if (tag === 'li') return 'bi bi-dot';
6181
- if (tag === 'svg') return 'bi bi-bezier2';
6182
- if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
6183
- return 'bi bi-square';
6184
- }
6195
+ function nodeIcon(tag) {
6196
+ tag = (tag || '').toLowerCase();
6197
+
6198
+ // Headings
6199
+ if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
6200
+
6201
+ // Document & metadata
6202
+ if (tag === 'html') return 'bi bi-filetype-html';
6203
+ if (tag === 'head') return 'bi bi-file-earmark-code';
6204
+ if (tag === 'body') return 'bi bi-file-earmark-richtext';
6205
+ if (tag === 'title') return 'bi bi-card-heading';
6206
+ if (tag === 'meta') return 'bi bi-info-circle';
6207
+ if (tag === 'link') return 'bi bi-link';
6208
+ if (tag === 'style') return 'bi bi-filetype-css';
6209
+ if (tag === 'script') return 'bi bi-filetype-js';
6210
+ if (tag === 'noscript') return 'bi bi-slash-circle';
6211
+ if (tag === 'base') return 'bi bi-house-gear';
6212
+
6213
+ // Sectioning & layout
6214
+ if (tag === 'section' || tag === 'main' || tag === 'article' ||
6215
+ tag === 'header' || tag === 'footer' || tag === 'nav' || tag === 'aside')
6216
+ return 'bi bi-layout-three-columns';
6217
+ if (tag === 'div') return 'bi bi-square';
6218
+ if (tag === 'address') return 'bi bi-geo-alt';
6219
+
6220
+ // Text content
6221
+ if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
6222
+ if (tag === 'blockquote' || tag === 'q') return 'bi bi-blockquote-left';
6223
+ if (tag === 'pre') return 'bi bi-code-square';
6224
+ if (tag === 'code') return 'bi bi-code-slash';
6225
+ if (tag === 'kbd') return 'bi bi-keyboard';
6226
+ if (tag === 'samp') return 'bi bi-terminal';
6227
+ if (tag === 'var') return 'bi bi-braces';
6228
+ if (tag === 'cite') return 'bi bi-quote';
6229
+ if (tag === 'abbr') return 'bi bi-fonts';
6230
+ if (tag === 'time') return 'bi bi-clock';
6231
+ if (tag === 'mark') return 'bi bi-highlighter';
6232
+ if (tag === 'small') return 'bi bi-type';
6233
+ if (tag === 'sub') return 'bi bi-subscript';
6234
+ if (tag === 'sup') return 'bi bi-superscript';
6235
+ if (tag === 'br') return 'bi bi-arrow-return-left';
6236
+ if (tag === 'wbr') return 'bi bi-distribute-horizontal';
6237
+
6238
+ // Inline formatting
6239
+ if (tag === 'strong' || tag === 'b') return 'bi bi-type-bold';
6240
+ if (tag === 'em' || tag === 'i') return 'bi bi-type-italic';
6241
+ if (tag === 'u' || tag === 'ins') return 'bi bi-type-underline';
6242
+ if (tag === 's' || tag === 'del' || tag === 'strike') return 'bi bi-type-strikethrough';
6243
+
6244
+ // Lists
6245
+ if (tag === 'ul') return 'bi bi-list-ul';
6246
+ if (tag === 'ol') return 'bi bi-list-ol';
6247
+ if (tag === 'li') return 'bi bi-dot';
6248
+ if (tag === 'dl') return 'bi bi-card-list';
6249
+ if (tag === 'dt') return 'bi bi-tag';
6250
+ if (tag === 'dd') return 'bi bi-text-indent-left';
6251
+
6252
+ // Links & navigation
6253
+ if (tag === 'a') return 'bi bi-link-45deg';
6254
+
6255
+ // Media
6256
+ if (tag === 'img') return 'bi bi-image';
6257
+ if (tag === 'picture') return 'bi bi-images';
6258
+ if (tag === 'figure') return 'bi bi-card-image';
6259
+ if (tag === 'figcaption') return 'bi bi-card-text';
6260
+ if (tag === 'video') return 'bi bi-camera-video';
6261
+ if (tag === 'audio') return 'bi bi-music-note-beamed';
6262
+ if (tag === 'source') return 'bi bi-cloud-arrow-down';
6263
+ if (tag === 'track') return 'bi bi-badge-cc';
6264
+ if (tag === 'iframe') return 'bi bi-window';
6265
+ if (tag === 'embed' || tag === 'object') return 'bi bi-box-arrow-in-down';
6266
+ if (tag === 'param') return 'bi bi-sliders';
6267
+ if (tag === 'canvas') return 'bi bi-easel';
6268
+ if (tag === 'map') return 'bi bi-map';
6269
+ if (tag === 'area') return 'bi bi-bounding-box';
6270
+
6271
+ // Vector / math
6272
+ if (tag === 'svg') return 'bi bi-bezier2';
6273
+ if (tag === 'path') return 'bi bi-bezier';
6274
+ if (tag === 'circle') return 'bi bi-circle';
6275
+ if (tag === 'rect') return 'bi bi-square';
6276
+ if (tag === 'line') return 'bi bi-slash-lg';
6277
+ if (tag === 'polygon') return 'bi bi-pentagon';
6278
+ if (tag === 'polyline') return 'bi bi-share';
6279
+ if (tag === 'ellipse') return 'bi bi-circle-half';
6280
+ if (tag === 'g') return 'bi bi-collection';
6281
+ if (tag === 'use') return 'bi bi-arrow-repeat';
6282
+ if (tag === 'defs') return 'bi bi-bookmark';
6283
+ if (tag === 'symbol') return 'bi bi-star';
6284
+ if (tag === 'text') return 'bi bi-fonts';
6285
+ if (tag === 'math') return 'bi bi-calculator';
6286
+
6287
+ // Forms
6288
+ if (tag === 'form') return 'bi bi-file-earmark-check';
6289
+ if (tag === 'fieldset') return 'bi bi-bounding-box-circles';
6290
+ if (tag === 'legend') return 'bi bi-tag-fill';
6291
+ if (tag === 'label') return 'bi bi-tag';
6292
+ if (tag === 'input') return 'bi bi-input-cursor-text';
6293
+ if (tag === 'button') return 'bi bi-ui-radios';
6294
+ if (tag === 'select') return 'bi bi-menu-button';
6295
+ if (tag === 'option') return 'bi bi-check2-square';
6296
+ if (tag === 'optgroup') return 'bi bi-list-nested';
6297
+ if (tag === 'textarea') return 'bi bi-textarea-resize';
6298
+ if (tag === 'datalist') return 'bi bi-list-columns';
6299
+ if (tag === 'output') return 'bi bi-box-arrow-right';
6300
+ if (tag === 'progress') return 'bi bi-bar-chart-line';
6301
+ if (tag === 'meter') return 'bi bi-speedometer2';
6302
+
6303
+ // Tables
6304
+ if (tag === 'table') return 'bi bi-table';
6305
+ if (tag === 'caption') return 'bi bi-card-heading';
6306
+ if (tag === 'thead') return 'bi bi-layout-text-window';
6307
+ if (tag === 'tbody') return 'bi bi-layout-text-sidebar';
6308
+ if (tag === 'tfoot') return 'bi bi-layout-text-window-reverse';
6309
+ if (tag === 'tr') return 'bi bi-grip-horizontal';
6310
+ if (tag === 'th') return 'bi bi-grid-3x3-gap-fill';
6311
+ if (tag === 'td') return 'bi bi-grid-3x3-gap';
6312
+ if (tag === 'col') return 'bi bi-layout-three-columns';
6313
+ if (tag === 'colgroup') return 'bi bi-columns-gap';
6314
+
6315
+ // Interactive / disclosure
6316
+ if (tag === 'details') return 'bi bi-caret-down-square';
6317
+ if (tag === 'summary') return 'bi bi-card-text';
6318
+ if (tag === 'dialog') return 'bi bi-chat-square-text';
6319
+ if (tag === 'menu') return 'bi bi-list';
6320
+
6321
+ // Web components / templating
6322
+ if (tag === 'template') return 'bi bi-file-earmark-code';
6323
+ if (tag === 'slot') return 'bi bi-box-seam';
6324
+
6325
+ // Ruby annotation
6326
+ if (tag === 'ruby' || tag === 'rt' || tag === 'rp' || tag === 'rb')
6327
+ return 'bi bi-translate';
6328
+ if (tag === 'bdi' || tag === 'bdo') return 'bi bi-arrow-left-right';
6329
+
6330
+ // Misc
6331
+ if (tag === 'hr') return 'bi bi-hr';
6332
+ if (tag === '#text' || tag === 'text-node') return 'bi bi-cursor-text';
6333
+ if (tag === '#comment') return 'bi bi-chat-left-text';
6334
+
6335
+ return 'bi bi-square';
6336
+ }
6337
+
6185
6338
 
6186
6339
  var nodes = collectEditorInsertedElements(doc);
6187
6340
 
@@ -6448,11 +6601,64 @@ function isFormControlElement(el) {
6448
6601
  function shouldShowInnerContentFields(el) {
6449
6602
  if (!el || el.nodeType !== 1) return false;
6450
6603
  var tag = (el.tagName || '').toLowerCase();
6451
- if (tag === 'input' || tag === 'textarea' || tag === 'video') return false;
6604
+ if (tag === 'input' || tag === 'textarea' || tag === 'select' || tag === 'video') return false;
6452
6605
  if (isEmbeddedVideoIframe(el)) return false;
6453
6606
  return true;
6454
6607
  }
6455
6608
 
6609
+ function getSelectOptionsText(el) {
6610
+ if (!el || el.nodeType !== 1) return '';
6611
+ try {
6612
+ var opts = el.querySelectorAll('option');
6613
+ var lines = [];
6614
+ for (var i = 0; i < opts.length; i++) {
6615
+ lines.push(opts[i].textContent || '');
6616
+ }
6617
+ return lines.join('\\n');
6618
+ } catch(_) {
6619
+ return '';
6620
+ }
6621
+ }
6622
+
6623
+ function selectOptionsTextToHtml(text) {
6624
+ var lines = String(text == null ? '' : text).split('\\n');
6625
+ var parts = [];
6626
+ for (var i = 0; i < lines.length; i++) {
6627
+ var label = lines[i];
6628
+ if (!label && i === lines.length - 1 && lines.length > 1) continue;
6629
+ parts.push('<option>' + esc(label) + '</option>');
6630
+ }
6631
+ if (!parts.length) parts.push('<option>Option 1</option>');
6632
+ return parts.join('');
6633
+ }
6634
+
6635
+ function applySelectOptionsFromText(el, text) {
6636
+ if (!el || el.nodeType !== 1) return;
6637
+ beginSuppressIframeMutationDirty();
6638
+ try {
6639
+ var prevValue = '';
6640
+ try { prevValue = el.value || ''; } catch(_) {}
6641
+ var doc = el.ownerDocument;
6642
+ while (el.firstChild) el.removeChild(el.firstChild);
6643
+ var lines = String(text == null ? '' : text).split('\\n');
6644
+ if (!lines.length || (lines.length === 1 && !String(lines[0] || '').trim())) {
6645
+ lines = ['Option 1', 'Option 2'];
6646
+ }
6647
+ for (var i = 0; i < lines.length; i++) {
6648
+ var label = lines[i];
6649
+ if (!label && i === lines.length - 1) continue;
6650
+ var opt = doc.createElement('option');
6651
+ opt.textContent = label;
6652
+ el.appendChild(opt);
6653
+ }
6654
+ if (prevValue) {
6655
+ try { el.value = prevValue; } catch(_) {}
6656
+ }
6657
+ } finally {
6658
+ endSuppressIframeMutationDirty();
6659
+ }
6660
+ }
6661
+
6456
6662
  function elementHasHtmlChildren(el) {
6457
6663
  if (!el || el.nodeType !== 1) return false;
6458
6664
  try {
@@ -7111,6 +7317,12 @@ function renderRightPanel(el, options) {
7111
7317
  subLbl('Placeholder') +
7112
7318
  '<input class="pr-inp" id="pp-ph" type="text" value="'+esc(el.getAttribute('placeholder')||'')+'" style="width:100%;margin-bottom:8px">';
7113
7319
  }
7320
+ if (tag === 'select') {
7321
+ contentHtml +=
7322
+ subLbl('Options') +
7323
+ '<div style="font-size:11px;color:var(--text-3);margin:-4px 0 8px">One option label per line</div>' +
7324
+ '<textarea class="pr-inp" id="pp-select-options" style="width:100%;min-height:80px;font-family:var(--font-mono);font-size:11px">'+esc(getSelectOptionsText(el))+'</textarea>';
7325
+ }
7114
7326
  if (shouldShowInnerContentFields(el)) {
7115
7327
  if (elementHasHtmlChildren(el)) {
7116
7328
  contentHtml +=
@@ -7218,18 +7430,49 @@ function renderRightPanel(el, options) {
7218
7430
  function wireContentFieldSync(el, sel) {
7219
7431
  var textInp = document.getElementById('pp-text');
7220
7432
  var htmlInp = document.getElementById('pp-html');
7433
+ var selectOptsInp = document.getElementById('pp-select-options');
7221
7434
  var syncing = false;
7222
- function applyContentChange(changedId) {
7435
+ var htmlApplyTimer = null;
7436
+ var selectApplyTimer = null;
7437
+
7438
+ function applyHtmlToElement(htmlValue) {
7439
+ beginSuppressIframeMutationDirty();
7440
+ try {
7441
+ el.innerHTML = htmlValue;
7442
+ } finally {
7443
+ endSuppressIframeMutationDirty();
7444
+ }
7445
+ }
7446
+
7447
+ function applyContentChange(changedId, immediate) {
7223
7448
  if (syncing) return;
7449
+ if (changedId === 'pp-html' && !immediate) {
7450
+ if (htmlApplyTimer) clearTimeout(htmlApplyTimer);
7451
+ htmlApplyTimer = setTimeout(function() { applyContentChange('pp-html', true); }, 250);
7452
+ return;
7453
+ }
7454
+ if (changedId === 'pp-select-options' && !immediate) {
7455
+ if (selectApplyTimer) clearTimeout(selectApplyTimer);
7456
+ selectApplyTimer = setTimeout(function() { applyContentChange('pp-select-options', true); }, 250);
7457
+ return;
7458
+ }
7224
7459
  syncing = true;
7225
7460
  try {
7226
7461
  var orig = getOriginalValue(changedId, el);
7227
7462
  if (changedId === 'pp-text') {
7228
- el.innerText = textInp.value;
7463
+ beginSuppressIframeMutationDirty();
7464
+ try {
7465
+ el.innerText = textInp.value;
7466
+ } finally {
7467
+ endSuppressIframeMutationDirty();
7468
+ }
7229
7469
  if (htmlInp) htmlInp.value = el.innerHTML;
7230
7470
  logChange(sel, 'pp-text', textInp.value, el, orig);
7471
+ } else if (changedId === 'pp-select-options') {
7472
+ applySelectOptionsFromText(el, selectOptsInp.value);
7473
+ logChange(sel, 'pp-select-options', selectOptsInp.value, el, orig);
7231
7474
  } else {
7232
- el.innerHTML = htmlInp.value;
7475
+ applyHtmlToElement(htmlInp.value);
7233
7476
  if (textInp) textInp.value = el.innerText;
7234
7477
  logChange(sel, 'pp-html', htmlInp.value, el, orig);
7235
7478
  }
@@ -7242,8 +7485,12 @@ function wireContentFieldSync(el, sel) {
7242
7485
  textInp.addEventListener('change', function() { applyContentChange('pp-text'); });
7243
7486
  }
7244
7487
  if (htmlInp) {
7245
- htmlInp.addEventListener('input', function() { applyContentChange('pp-html'); });
7246
- htmlInp.addEventListener('change', function() { applyContentChange('pp-html'); });
7488
+ htmlInp.addEventListener('input', function() { applyContentChange('pp-html', false); });
7489
+ htmlInp.addEventListener('change', function() { applyContentChange('pp-html', true); });
7490
+ }
7491
+ if (selectOptsInp) {
7492
+ selectOptsInp.addEventListener('input', function() { applyContentChange('pp-select-options', false); });
7493
+ selectOptsInp.addEventListener('change', function() { applyContentChange('pp-select-options', true); });
7247
7494
  }
7248
7495
  }
7249
7496
 
@@ -7734,14 +7981,21 @@ function attachChangeObserver() {
7734
7981
  changeObserverDoc = null;
7735
7982
  }
7736
7983
  changeObserver = new MutationObserver(function(mutations) {
7984
+ if (suppressIframeMutationDirty > 0) return;
7737
7985
  var hasMeaningfulMutation = false;
7738
7986
  for (var mi = 0; mi < mutations.length; mi++) {
7739
- if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
7987
+ if (!isEditorChromeOnlyClassMutation(mutations[mi])) {
7740
7988
  hasMeaningfulMutation = true;
7741
7989
  break;
7742
7990
  }
7743
7991
  }
7744
7992
  if (!hasMeaningfulMutation) return;
7993
+ var activeId = '';
7994
+ try { activeId = document.activeElement && document.activeElement.id ? String(document.activeElement.id) : ''; } catch(_) {}
7995
+ if (activeId === 'pp-html' || activeId === 'pp-text' || activeId === 'pp-select-options') {
7996
+ scheduleDomTreeRefresh();
7997
+ return;
7998
+ }
7745
7999
  // Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
7746
8000
  // Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
7747
8001
  // Keep selection sticky by re-resolving from fingerprint.
@@ -8237,7 +8491,9 @@ window.addEventListener('load', function() {
8237
8491
  return;
8238
8492
  }
8239
8493
  hideIframeLoadError();
8240
- emitEditorUrlChanged(iframe.src || docUrl);
8494
+ var iframeLiveUrl = '';
8495
+ try { iframeLiveUrl = String(iframe.contentWindow.location.href || ''); } catch(_) {}
8496
+ emitEditorUrlChanged(iframeLiveUrl || docUrl || iframe.src || '');
8241
8497
  // Stale events: src may already be the proxy URL while the document is still
8242
8498
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
8243
8499
  if (docUrl === 'about:blank') {
@@ -8622,6 +8878,11 @@ function createVisualEditorMiddleware(options) {
8622
8878
  }
8623
8879
  if (chunks.length > 0) requestBody = Buffer.concat(chunks);
8624
8880
  }
8881
+ const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
8882
+ const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
8883
+ const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
8884
+ const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
8885
+ const passthroughUpstreamRedirects = (method === "GET" || method === "HEAD") && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
8625
8886
  const upstreamTimeoutMs = 12e4;
8626
8887
  const ac = new AbortController();
8627
8888
  const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
@@ -8631,7 +8892,7 @@ function createVisualEditorMiddleware(options) {
8631
8892
  method,
8632
8893
  headers: fetchHeaders,
8633
8894
  body: requestBody ? Buffer.from(requestBody) : null,
8634
- redirect: "follow",
8895
+ redirect: passthroughUpstreamRedirects ? "manual" : "follow",
8635
8896
  signal: ac.signal
8636
8897
  });
8637
8898
  } catch (fetchErr) {
@@ -8655,12 +8916,43 @@ function createVisualEditorMiddleware(options) {
8655
8916
  return;
8656
8917
  }
8657
8918
  clearTimeout(timeoutId);
8919
+ if (passthroughUpstreamRedirects && upstream.status >= 300 && upstream.status < 400) {
8920
+ const locationHeader = upstream.headers.get("location") || upstream.headers.get("Location");
8921
+ if (locationHeader) {
8922
+ try {
8923
+ const redirectTarget = new URL(locationHeader, targetUrl);
8924
+ if (redirectTarget.origin === origin) {
8925
+ const proxyRedirect = new URL(proxyRootForRequest, "http://localhost");
8926
+ proxyRedirect.searchParams.set("password", password);
8927
+ proxyRedirect.searchParams.set("url", redirectTarget.toString());
8928
+ url.searchParams.forEach((value, key) => {
8929
+ if (key === "url" || key === "password") return;
8930
+ proxyRedirect.searchParams.set(key, value);
8931
+ });
8932
+ res.statusCode = upstream.status === 303 ? 303 : 302;
8933
+ res.setHeader("Location", `${proxyRedirect.pathname}${proxyRedirect.search}`);
8934
+ res.setHeader("Cache-Control", "no-store");
8935
+ res.setHeader("Access-Control-Allow-Origin", "*");
8936
+ setFrameHeaders(req, res);
8937
+ res.end();
8938
+ return;
8939
+ }
8940
+ res.statusCode = 502;
8941
+ res.setHeader("Content-Type", "application/json");
8942
+ res.end(
8943
+ JSON.stringify({
8944
+ error: "Cross-origin redirect blocked in editor preview",
8945
+ location: redirectTarget.toString(),
8946
+ from: targetUrl
8947
+ })
8948
+ );
8949
+ return;
8950
+ } catch (_) {
8951
+ }
8952
+ }
8953
+ }
8658
8954
  const responseContentType = upstream.headers.get("content-type") || "";
8659
8955
  const isHtmlResponse = responseContentType.includes("text/html");
8660
- const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
8661
- const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
8662
- const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
8663
- const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
8664
8956
  const shouldInjectHtmlBridge = isHtmlResponse && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
8665
8957
  if (!isHtmlResponse || !shouldInjectHtmlBridge) {
8666
8958
  const binary = Buffer.from(await upstream.arrayBuffer());
@@ -8714,6 +9006,18 @@ ${iframeAlwaysShowCssGuardScript}
8714
9006
  /<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
8715
9007
  ""
8716
9008
  );
9009
+ html = html.replace(
9010
+ /(<meta[^>]+http-equiv=["']?refresh["']?[^>]*content=["'][^"']*url=)([^"';]+)(["'][^>]*>)/gi,
9011
+ (match, prefix, urlPart, suffix) => {
9012
+ try {
9013
+ const abs = new URL(String(urlPart).trim(), origin).toString();
9014
+ if (new URL(abs).origin !== origin) return match;
9015
+ return `${prefix}${proxyBase}${encodeURIComponent(abs)}${suffix}`;
9016
+ } catch {
9017
+ return match;
9018
+ }
9019
+ }
9020
+ );
8717
9021
  const runtimePreflightScript = `<script>(function(){try{
8718
9022
  var TARGET_ORIGIN=${JSON.stringify(origin)};
8719
9023
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
@@ -8920,6 +9224,8 @@ try{if(window.history&&typeof window.history.pushState==="function"){var nativeP
8920
9224
  try{if(window.history&&typeof window.history.replaceState==="function"){var nativeReplaceState=window.history.replaceState;window.history.replaceState=function(){var ret=nativeReplaceState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
8921
9225
  try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
8922
9226
  try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
9227
+ try{window.addEventListener("pageshow",notifyEditorUrlChanged,true);}catch(_){}
9228
+ try{window.addEventListener("load",function(){setTimeout(notifyEditorUrlChanged,0);},true);}catch(_){}
8923
9229
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
8924
9230
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
8925
9231
  function toProxy(raw){
@@ -8948,6 +9254,9 @@ function toProxy(raw){
8948
9254
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
8949
9255
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
8950
9256
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
9257
+ function interceptMetaRefresh(){try{var metas=document.querySelectorAll('meta[http-equiv="refresh" i],meta[http-equiv="Refresh"]');for(var i=0;i<metas.length;i++){var content=metas[i].getAttribute("content")||"";var m=content.match(/url=(.+)$/i);if(!m)continue;var raw=m[1].trim().replace(/^['"]|['"]$/g,"");if(safeNavigate(raw,"replace"))metas[i].parentNode&&metas[i].parentNode.removeChild(metas[i]);}}catch(_){}}
9258
+ try{interceptMetaRefresh();}catch(_){}
9259
+ try{document.addEventListener("DOMContentLoaded",interceptMetaRefresh,true);}catch(_){}
8951
9260
  try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
8952
9261
  try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
8953
9262
  try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",