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