From 64c1fc57b4e4e561abc6287b6c6017d764150291 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 18 Feb 2026 14:40:44 +0000 Subject: [PATCH 1/2] Update src/app/layout.tsx --- src/app/layout.tsx | 115 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6e17555..12321ad 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -92,7 +92,9 @@ export default function RootLayout({ ' background-color: #4d96ff05 !important;' + '}' + 'img.webild-hover,' + - 'img.webild-selected {' + + 'img.webild-selected,' + + 'video.webild-hover,' + + 'video.webild-selected {' + ' outline-offset: 2px !important;' + '}' + '.webild-element-type-label {' + @@ -164,6 +166,10 @@ export default function RootLayout({ return 'Image'; } + if (tagName === 'video') { + return 'Video'; + } + const backgroundImage = computedStyle.backgroundImage; if (backgroundImage && backgroundImage !== 'none') { const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); @@ -246,6 +252,40 @@ export default function RootLayout({ return url; }; + const getMediaTypeFromUrl = (url) => { + const videoExts = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m4v', '.wmv']; + const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.avif']; + try { + const pathname = new URL(url).pathname.toLowerCase(); + if (videoExts.some(function(ext) { return pathname.endsWith(ext); })) return 'video'; + if (imageExts.some(function(ext) { return pathname.endsWith(ext); })) return 'image'; + } catch(e) {} + return 'unknown'; + }; + + const swapMediaElement = (oldEl, newTag, newSrc) => { + const newEl = document.createElement(newTag); + // Copy all attributes (preserves class, id, style, data-webild-id, etc.) + Array.from(oldEl.attributes).forEach(function(attr) { + if (attr.name !== 'src' && attr.name !== 'alt' && attr.name !== 'srcset' && attr.name !== 'autoplay' && attr.name !== 'loop' && attr.name !== 'muted' && attr.name !== 'playsinline') { + try { newEl.setAttribute(attr.name, attr.value); } catch(e) {} + } + }); + newEl.style.cssText = oldEl.style.cssText; + if (newTag === 'video') { + newEl.setAttribute('autoplay', ''); + newEl.setAttribute('loop', ''); + newEl.setAttribute('muted', ''); + newEl.setAttribute('playsinline', ''); + } + newEl.src = newSrc; + if (oldEl.parentNode) { + oldEl.parentNode.replaceChild(newEl, oldEl); + } + console.log('[Webild] swapped <' + oldEl.tagName.toLowerCase() + '> -> <' + newTag + '>', newSrc); + return newEl; + }; + const getElementInfo = (element, assignId = false) => { const rect = element.getBoundingClientRect(); const tagName = element.tagName.toLowerCase(); @@ -288,7 +328,19 @@ export default function RootLayout({ isBackground: false }; } - + + if (tagName === 'video') { + const rawSrc = element.src || element.currentSrc || (element.querySelector('source') && element.querySelector('source').src) || ''; + const resolvedSrc = extractOriginalUrl(rawSrc); + console.log('[Webild] getElementInfo video: rawSrc=', rawSrc, 'resolvedSrc=', resolvedSrc); + info.imageData = { + src: resolvedSrc, + alt: element.getAttribute('aria-label') || undefined, + isBackground: false, + isVideo: true + }; + } + const computedStyle = window.getComputedStyle(element); const backgroundImage = computedStyle.backgroundImage; if (backgroundImage && backgroundImage !== 'none') { @@ -336,7 +388,8 @@ export default function RootLayout({ const tagName = element.tagName?.toLowerCase(); if (invalidElements.includes(tagName)) return false; const isImage = tagName === 'img'; - if (isImage) return true; + const isVideo = tagName === 'video'; + if (isImage || isVideo) return true; const hasInnerHTML = element.innerHTML && element.innerHTML.trim().length > 0; const hasTextContent = element.textContent && element.textContent.trim().length > 0; const hasChildren = element.children && element.children.length > 0; @@ -376,7 +429,7 @@ export default function RootLayout({ node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0 ); - const hasImages = element.tagName === 'IMG' || computedStyle.backgroundImage !== 'none' || element.querySelector('img'); + const hasImages = element.tagName === 'IMG' || element.tagName === 'VIDEO' || computedStyle.backgroundImage !== 'none' || element.querySelector('img') || element.querySelector('video'); const isInteractive = ['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName); const hasFewChildren = element.children.length <= 3; const area = rect.width * rect.height; @@ -1057,11 +1110,22 @@ export default function RootLayout({ updateButtonText(element, change.oldValue); } } else if (change.type === 'replaceImage') { - const isBackground = element.tagName.toLowerCase() !== 'img'; + const revertTag = element.tagName.toLowerCase(); + const isBackground = revertTag !== 'img' && revertTag !== 'video'; if (isBackground) { element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : ''; } else { - element.src = change.oldValue; + const oldMediaType = getMediaTypeFromUrl(change.oldValue); + if (revertTag === 'video' && oldMediaType === 'image') { + swapMediaElement(element, 'img', change.oldValue); + } else if (revertTag === 'img' && oldMediaType === 'video') { + swapMediaElement(element, 'video', change.oldValue); + } else if (revertTag === 'video') { + element.src = change.oldValue; + element.load(); + } else { + element.src = change.oldValue; + } } } } catch (err) { @@ -1161,9 +1225,11 @@ export default function RootLayout({ if (!isActive) return; if (e.data.type === 'webild-replace-image') { - const { selector, newSrc, isBackground } = e.data.data; + const { selector, newSrc, isBackground, allowMediaTypeSwap } = e.data.data; let element = null; + console.log('[Webild] webild-replace-image received:', { selector, newSrc, isBackground, allowMediaTypeSwap }); + try { element = document.querySelector(selector); } catch { @@ -1175,6 +1241,7 @@ export default function RootLayout({ } if (!element) { + console.warn('[Webild] element not found for selector:', selector); window.parent.postMessage({ type: 'webild-image-replacement-error', data: { selector, message: 'Element not found', success: false } @@ -1182,20 +1249,51 @@ export default function RootLayout({ return; } + console.log('[Webild] element found:', element.tagName, 'isBackground:', isBackground); + try { let replaced = false; let oldValue = ''; if (isBackground) { oldValue = window.getComputedStyle(element).backgroundImage; + console.log('[Webild] replacing background-image, oldValue:', oldValue); element.style.backgroundImage = \`url('\${newSrc}')\`; replaced = true; } else if (element.tagName.toLowerCase() === 'img') { oldValue = element.src; - element.src = newSrc; + const newMediaType = getMediaTypeFromUrl(newSrc); + console.log('[Webild] replacing src, oldValue:', oldValue, '-> newSrc:', newSrc, 'newMediaType:', newMediaType, 'allowMediaTypeSwap:', allowMediaTypeSwap); + if (newMediaType === 'video' && allowMediaTypeSwap) { + const swapped = swapMediaElement(element, 'video', newSrc); + if (selectedElement === element) selectedElement = swapped; + element = swapped; + } else { + element.src = newSrc; + } + replaced = true; + } else if (element.tagName.toLowerCase() === 'video') { + oldValue = element.src || element.currentSrc || ''; + const newMediaType = getMediaTypeFromUrl(newSrc); + const sources = element.querySelectorAll('source'); + console.log('[Webild] replacing