diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5b299ba..a662200 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -164,1374 +164,6 @@ export default function RootLayout({ return 'Video'; } - const backgroundImage = computedStyle.backgroundImage; - if (backgroundImage && backgroundImage !== 'none') { - const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); - if (urlMatch && urlMatch[1] && !urlMatch[1].includes('gradient')) { - const area = element.offsetWidth * element.offsetHeight; - const hasReasonableSize = area > 1000; - const hasFewChildren = element.children.length <= 2; - - if (hasReasonableSize && hasFewChildren) { - return 'Image'; - } - } - } - - if (tagName === 'button') return 'Button'; - if (tagName === 'a' && element.getAttribute('href')) return 'Button'; - if (element.getAttribute('role') === 'button') return 'Button'; - - const buttonClasses = ['btn', 'button', 'cta', 'action-button']; - const hasButtonClass = buttonClasses.some(cls => - element.classList.contains(cls) || element.classList.contains(`btn-${cls}`) - ); - - if (hasButtonClass && element.textContent && element.textContent.trim().length > 0) { - return 'Button'; - } - - const textTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'label', 'li']; - if (textTags.includes(tagName)) { - return 'Text'; - } - - if (tagName === 'div') { - const hasDirectText = Array.from(element.childNodes).some(node => - node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0 - ); - - if (hasDirectText && !element.querySelector('div, section, article, main, header, footer')) { - return 'Text'; - } - - return 'Div'; - } - - if (tagName === 'article') { - return 'Article'; - } - - if (tagName === 'a' && !element.getAttribute('href')) { - return 'Text'; - } - - return 'Section'; - }; - - const extractOriginalUrl = (url) => { - if (!url) return url; - - if (url.includes('/_next/')) { - try { - const urlObj = new URL(url); - const originalPath = urlObj.searchParams.get('url'); - if (originalPath) { - return originalPath; - } - } catch (e) { - return url; - } - } - - if (url.includes('.webild.io/')) { - try { - const urlObj = new URL(url); - return urlObj.pathname; - } catch (e) { - return url; - } - } - - 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); - 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); - } - return newEl; - }; - - const getElementInfo = (element, assignId = false) => { - const rect = element.getBoundingClientRect(); - const tagName = element.tagName.toLowerCase(); - const selector = getUniqueSelector(element, assignId); - const sectionId = getSectionId(element); - - let className = undefined; - try { - if (element.className) { - if (typeof element.className === 'string') { - className = element.className; - } else if (element.className.baseVal !== undefined) { - className = element.className.baseVal; - } - } - } - catch (e) {} - - const info = { - tagName: tagName, - id: element.id || undefined, - className: className, - selector: selector, - elementType: null, - sectionId: sectionId, - boundingBox: { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height - } - }; - - if (tagName === 'img') { - const originalSrc = extractOriginalUrl(element.src); - info.imageData = { - src: originalSrc, - alt: element.alt || undefined, - naturalWidth: element.naturalWidth, - naturalHeight: element.naturalHeight, - isBackground: false - }; - } - - if (tagName === 'video') { - const rawSrc = element.src || element.currentSrc || (element.querySelector('source') && element.querySelector('source').src) || ''; - const resolvedSrc = extractOriginalUrl(rawSrc); - 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') { - const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); - if (urlMatch) { - const originalBgSrc = extractOriginalUrl(urlMatch[1]); - if (tagName !== 'img') { - info.imageData = { - src: originalBgSrc, - isBackground: true - }; - } else { - if (!info.imageData) info.imageData = {}; - info.imageData.backgroundImageSrc = originalBgSrc; - } - } - } - - const elementType = getElementType(element); - info.elementType = elementType; - - if (elementType === 'Button') { - const buttonText = element.textContent?.trim() || element.value || element.getAttribute('aria-label') || ''; - const buttonHref = element.getAttribute('href') || - element.getAttribute('data-href') || - element.getAttribute('onclick') || - element.dataset?.link || - undefined; - - info.buttonData = { - text: buttonText, - href: buttonHref - }; - } - - if (elementType === 'Text') { - info.textContent = element.textContent || ''; - } - - return info; - }; - - const isValidElement = (element) => { - if (!isActive) return false; - const tagName = element.tagName?.toLowerCase(); - if (invalidElements.includes(tagName)) return false; - const isImage = tagName === 'img'; - 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; - if (!hasInnerHTML && !hasTextContent && !hasChildren) { - return false; - } - const hasBackgroundImage = window.getComputedStyle(element).backgroundImage !== 'none'; - if (hasBackgroundImage && !hasChildren && !hasTextContent) { - return false; - } - - return true; - }; - - const getMostSpecificElement = (x, y) => { - const elements = document.elementsFromPoint(x, y); - const validElements = elements.filter(el => - isValidElement(el) && - !el.classList.contains('webild-hover-overlay') && - !el.classList.contains('webild-element-type-label') - ); - - if (validElements.length === 0) return null; - const scoredElements = validElements.map(element => { - let score = 0; - const rect = element.getBoundingClientRect(); - const tagName = element.tagName.toLowerCase(); - const computedStyle = window.getComputedStyle(element); - let depth = 0; - let current = element; - while (current && current !== document.body) { - depth++; - current = current.parentElement; - } - score += depth * 2; - const hasDirectText = Array.from(element.childNodes).some(node => - node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0 - ); - - 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; - const viewportArea = window.innerWidth * window.innerHeight; - const isSmallElement = area < viewportArea * 0.1; - if (hasDirectText) score += 20; - if (hasImages) score += 15; - if (isInteractive) score += 25; - if (hasFewChildren) score += 10; - if (isSmallElement) score += 5; - if (area > viewportArea * 0.3) score -= 30; - if (element.hasAttribute('data-section')) score -= 15; - if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) score += 20; - if (['p', 'span', 'label'].includes(tagName)) score += 15; - if (tagName === 'div' && !hasDirectText && element.children.length > 2) score -= 10; - - return { element, score }; - }); - scoredElements.sort((a, b) => b.score - a.score); - return scoredElements[0]?.element || validElements[0]; - }; - - const isTextElement = (element) => { - const elementType = getElementType(element); - return elementType === 'Text'; - }; - - const isButtonElement = (element) => { - const elementType = getElementType(element); - return elementType === 'Button'; - }; - - const updateButtonText = (element, newText) => { - const textNodes = []; - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - null - ); - - let node; - while (node = walker.nextNode()) { - if (node.textContent && node.textContent.trim()) { - textNodes.push(node); - } - } - - if (textNodes.length > 0) { - textNodes[0].textContent = newText; - for (let i = 1; i < textNodes.length; i++) { - textNodes[i].textContent = ''; - } - } else { - element.textContent = newText; - } - }; - - const makeEditable = (element, clickEvent) => { - if (!isTextElement(element)) return; - - originalContent = element.textContent; - element.contentEditable = 'true'; - - if (!element.dataset.webildOriginalWhiteSpace) { - const computedStyle = window.getComputedStyle(element); - element.dataset.webildOriginalWhiteSpace = computedStyle.whiteSpace; - element.dataset.webildOriginalWordWrap = computedStyle.wordWrap; - element.dataset.webildOriginalOverflowWrap = computedStyle.overflowWrap; - element.dataset.webildOriginalOverflow = computedStyle.overflow; - } - - element.style.whiteSpace = 'pre-wrap'; - element.style.wordWrap = 'break-word'; - element.style.overflowWrap = 'break-word'; - element.style.overflow = 'visible'; - - element.focus(); - isEditing = true; - - window.parent.postMessage({ - type: 'webild-text-editing-started', - data: { selector: getElementInfo(element).selector } - }, '*'); - - const handleBeforeInput = (e) => { - // Prevent deletion if it would leave the element empty - const currentText = element.textContent || ''; - const inputType = e.inputType; - - // Check if this is a delete operation that would leave the element empty - if ((inputType === 'deleteContentBackward' || inputType === 'deleteContentForward' || inputType === 'deleteByCut') && currentText.length <= 1) { - e.preventDefault(); - element.textContent = ' '; - // Move cursor to the beginning - const range = document.createRange(); - const sel = window.getSelection(); - range.setStart(element.firstChild || element, 0); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); - } - }; - - const handleInput = () => { - const elementInfo = getElementInfo(element); - let currentText = element.textContent; - - // Ensure there's always at least a space to keep the element editable - if (currentText === '' || currentText === null || currentText.length === 0) { - element.textContent = ' '; - currentText = ' '; - // Move cursor to the beginning - try { - const range = document.createRange(); - const sel = window.getSelection(); - range.setStart(element.firstChild || element, 0); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); - } catch (e) { - // Ignore cursor positioning errors - } - } - - window.parent.postMessage({ - type: 'webild-element-changed', - data: { - type: 'updateText', - selector: elementInfo.selector, - oldValue: originalContent, - newValue: currentText, - elementType: elementInfo.elementType, - sectionId: elementInfo.sectionId, - timestamp: Date.now() - } - }, '*'); - - if (currentText !== originalContent) { - window.parent.postMessage({ - type: 'webild-text-changed', - data: { - selector: elementInfo.selector, - hasChanges: true - } - }, '*'); - } - }; - - element.addEventListener('beforeinput', handleBeforeInput); - element.addEventListener('input', handleInput); - element.dataset.inputHandler = 'true'; - element.dataset.beforeInputHandler = 'true'; - - if (clickEvent && element.childNodes.length > 0) { - try { - let range = null; - - if (document.caretRangeFromPoint) { - range = document.caretRangeFromPoint(clickEvent.clientX, clickEvent.clientY); - } else if (document.caretPositionFromPoint) { - const position = document.caretPositionFromPoint(clickEvent.clientX, clickEvent.clientY); - if (position) { - range = document.createRange(); - range.setStart(position.offsetNode, position.offset); - range.collapse(true); - } - } - - if (range) { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - } - } catch (e) { - console.warn('[Webild] Could not set caret position:', e); - } - } - }; - - const makeUneditable = (element, save = false) => { - if (!element || element.contentEditable !== 'true') return; - - element.contentEditable = 'false'; - isEditing = false; - - if (element.dataset.webildOriginalWhiteSpace) { - element.style.whiteSpace = element.dataset.webildOriginalWhiteSpace === 'normal' ? '' : element.dataset.webildOriginalWhiteSpace; - delete element.dataset.webildOriginalWhiteSpace; - } - if (element.dataset.webildOriginalWordWrap) { - element.style.wordWrap = element.dataset.webildOriginalWordWrap === 'normal' ? '' : element.dataset.webildOriginalWordWrap; - delete element.dataset.webildOriginalWordWrap; - } - if (element.dataset.webildOriginalOverflowWrap) { - element.style.overflowWrap = element.dataset.webildOriginalOverflowWrap === 'normal' ? '' : element.dataset.webildOriginalOverflowWrap; - delete element.dataset.webildOriginalOverflowWrap; - } - if (element.dataset.webildOriginalOverflow) { - element.style.overflow = element.dataset.webildOriginalOverflow === 'visible' ? '' : element.dataset.webildOriginalOverflow; - delete element.dataset.webildOriginalOverflow; - } - - if (element.dataset.beforeInputHandler === 'true') { - element.removeEventListener('beforeinput', () => {}); - delete element.dataset.beforeInputHandler; - } - - if (element.dataset.inputHandler === 'true') { - element.removeEventListener('input', () => {}); - delete element.dataset.inputHandler; - } - - window.parent.postMessage({ - type: 'webild-text-editing-ended', - data: { selector: getElementInfo(element).selector } - }, '*'); - - if (save && originalContent !== element.textContent) { - const elementInfo = getElementInfo(element); - let finalText = element.textContent; - - // Trim the final text and convert space-only to empty string for saving - if (finalText === ' ' || finalText.trim() === '') { - finalText = ''; - // Update the actual element text to empty for display - element.textContent = ''; - } - - const change = { - type: 'updateText', - selector: elementInfo.selector, - oldValue: originalContent, - newValue: finalText, - elementType: elementInfo.elementType, - sectionId: elementInfo.sectionId, - timestamp: Date.now() - }; - - saveChangeToStorage(change); - - window.parent.postMessage({ - type: 'webild-element-changed', - data: change - }, '*'); - } else if (!save && originalContent !== null) { - element.textContent = originalContent; - } - - originalContent = null; - }; - - const createHoverOverlay = (element) => { - const rect = element.getBoundingClientRect(); - const overlay = document.createElement('div'); - overlay.className = 'webild-hover-overlay'; - overlay.style.cssText = ` - position: fixed !important; - top: ${rect.top - 2}px !important; - left: ${rect.left - 2}px !important; - width: ${rect.width + 4}px !important; - height: ${rect.height + 4}px !important; - background-color: rgba(90, 113, 230, 0.15) !important; - pointer-events: none !important; - z-index: 999998 !important; - transition: all 0.15s ease !important; - `; - document.body.appendChild(overlay); - return overlay; - }; - - const removeHoverOverlay = () => { - if (hoverOverlay) { - hoverOverlay.remove(); - hoverOverlay = null; - } - }; - - const showElementTypeLabel = (element, elementType) => { - if (!elementType) return; - - removeElementTypeLabel(); - - const rect = element.getBoundingClientRect(); - elementTypeLabel = document.createElement('div'); - elementTypeLabel.className = 'webild-element-type-label'; - const ariaLabel = element.getAttribute('aria-label'); - let labelText; - - if (elementType === 'Div') { - labelText = 'Div'; - } else if (elementType === 'Article') { - labelText = 'Article'; - } else if (elementType === 'Section') { - labelText = ariaLabel || 'Section'; - } else { - labelText = elementType; - } - - elementTypeLabel.textContent = labelText; - document.body.appendChild(elementTypeLabel); - const labelRect = elementTypeLabel.getBoundingClientRect(); - let labelTop = rect.top - labelRect.height - 2; - let labelLeft = rect.left - 3; - let isLabelOnTop = true; - if (labelTop < 0) { - labelTop = rect.bottom + 1; - isLabelOnTop = false; - } - if (labelTop + labelRect.height > window.innerHeight) { - labelTop = rect.bottom - labelRect.height; - isLabelOnTop = false; - if (labelTop < 0) { - labelTop = rect.top; - isLabelOnTop = true; - } - } - if (labelLeft + labelRect.width > window.innerWidth) { - labelLeft = window.innerWidth - labelRect.width; - } - if (labelLeft < 0) { - labelLeft = 0; - } - if (isLabelOnTop) { - elementTypeLabel.classList.add('label-top'); - } else { - elementTypeLabel.classList.add('label-bottom'); - } - - elementTypeLabel.style.cssText = ` - left: ${labelLeft}px !important; - top: ${labelTop}px !important; - transform: none !important; - `; - }; - - const removeElementTypeLabel = () => { - if (elementTypeLabel) { - elementTypeLabel.remove(); - elementTypeLabel = null; - } - }; - - const handleMouseOver = (e) => { - if (!isActive) return; - - lastMouseX = e.clientX; - lastMouseY = e.clientY; - - const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; - - if (!isValidElement(target) || target === hoveredElement || target === selectedElement) { - return; - } - - if (hoveredElement && hoveredElement !== selectedElement) { - hoveredElement.classList.remove(hoverClass); - if (hoveredElement.dataset.webildOriginalPosition) { - hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; - delete hoveredElement.dataset.webildOriginalPosition; - } - removeHoverOverlay(); - removeElementTypeLabel(); - } - - hoveredElement = target; - - const computedStyle = window.getComputedStyle(target); - const currentPosition = computedStyle.position; - - if (currentPosition === 'static' || currentPosition === '') { - hoveredElement.dataset.webildOriginalPosition = currentPosition || 'none'; - hoveredElement.style.position = 'relative'; - } - - hoveredElement.classList.add(hoverClass); - - if ((!selectedElement || selectedElement !== target) && !isScrolling) { - hoverOverlay = createHoverOverlay(target); - } - - const elementType = getElementType(target); - showElementTypeLabel(target, elementType); - - window.parent.postMessage({ - type: 'webild-element-hover', - data: getElementInfo(target, false) - }, '*'); - }; - - const handleMouseOut = (e) => { - if (!isActive) return; - - if (hoveredElement && hoveredElement !== selectedElement) { - hoveredElement.classList.remove(hoverClass); - - if (hoveredElement.dataset.webildOriginalPosition) { - hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; - delete hoveredElement.dataset.webildOriginalPosition; - } - - removeHoverOverlay(); - removeElementTypeLabel(); - - hoveredElement = null; - - window.parent.postMessage({ - type: 'webild-element-hover', - data: null - }, '*'); - } - }; - - const handleClick = (e) => { - if (!isActive) return; - - if (isEditing) { - e.stopPropagation(); - return; - } - - e.preventDefault(); - e.stopPropagation(); - - const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; - if (!isValidElement(target)) return; - - if (selectedElement && selectedElement !== target) { - makeUneditable(selectedElement, false); - selectedElement.classList.remove(selectedClass); - selectedElement.classList.remove(hoverClass); - - if (selectedElement.dataset.webildOriginalPosition) { - selectedElement.style.position = selectedElement.dataset.webildOriginalPosition === 'none' ? '' : selectedElement.dataset.webildOriginalPosition; - delete selectedElement.dataset.webildOriginalPosition; - } - - removeHoverOverlay(); - removeElementTypeLabel(); - } - - if (selectedElement === target) { - if (target.dataset.webildOriginalPosition) { - target.style.position = target.dataset.webildOriginalPosition === 'none' ? '' : target.dataset.webildOriginalPosition; - delete target.dataset.webildOriginalPosition; - } - - removeHoverOverlay(); - removeElementTypeLabel(); - - selectedElement = null; - window.parent.postMessage({ - type: 'webild-element-selected', - data: null - }, '*'); - return; - } - - selectedElement = target; - selectedElement.classList.add(selectedClass); - - removeHoverOverlay(); - removeElementTypeLabel(); - - if (hoveredElement === target) { - hoveredElement.classList.remove(hoverClass); - hoveredElement = null; - } - - const elementInfo = getElementInfo(target, true); - selectedElement.dataset.webildSelector = elementInfo.selector; - showElementTypeLabel(target, elementInfo.elementType); - - window.parent.postMessage({ - type: 'webild-element-selected', - data: elementInfo - }, '*'); - - if (isTextElement(target)) { - setTimeout(() => makeEditable(target, e), 50); - } - }; - - const handleKeyDown = (e) => { - if (!isActive) return; - if (!isEditing || !selectedElement) return; - - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - makeUneditable(selectedElement, true); - } else if (e.key === 'Escape') { - e.preventDefault(); - makeUneditable(selectedElement, false); - } - }; - - const handleBlur = (e) => { - if (!isActive) return; - if (isEditing && selectedElement && e.target === selectedElement) { - makeUneditable(selectedElement, true); - } - }; - - let lastMouseX = 0; - let lastMouseY = 0; - - const handleScroll = () => { - if (!isActive) return; - - if (isEditing) return; - - if (selectedElement) { - makeUneditable(selectedElement, false); - selectedElement.classList.remove(selectedClass); - if (selectedElement.dataset.webildOriginalPosition) { - selectedElement.style.position = selectedElement.dataset.webildOriginalPosition === 'none' ? '' : selectedElement.dataset.webildOriginalPosition; - delete selectedElement.dataset.webildOriginalPosition; - } - selectedElement = null; - - window.parent.postMessage({ - type: 'webild-element-selected', - data: null - }, '*'); - } - - if (hoveredElement) { - hoveredElement.classList.remove(hoverClass); - if (hoveredElement.dataset.webildOriginalPosition) { - hoveredElement.style.position = hoveredElement.dataset.webildOriginalPosition === 'none' ? '' : hoveredElement.dataset.webildOriginalPosition; - delete hoveredElement.dataset.webildOriginalPosition; - } - hoveredElement = null; - - window.parent.postMessage({ - type: 'webild-element-hover', - data: null - }, '*'); - } - - removeHoverOverlay(); - removeElementTypeLabel(); - - isScrolling = true; - - if (scrollTimeout) { - clearTimeout(scrollTimeout); - } - - scrollTimeout = setTimeout(() => { - isScrolling = false; - - if (lastMouseX > 0 && lastMouseY > 0) { - const target = getMostSpecificElement(lastMouseX, lastMouseY); - if (target && isValidElement(target) && target !== selectedElement) { - hoveredElement = target; - - const computedStyle = window.getComputedStyle(target); - const currentPosition = computedStyle.position; - - if (currentPosition === 'static' || currentPosition === '') { - hoveredElement.dataset.webildOriginalPosition = currentPosition || 'none'; - hoveredElement.style.position = 'relative'; - } - - hoveredElement.classList.add(hoverClass); - hoverOverlay = createHoverOverlay(target); - - const elementType = getElementType(target); - showElementTypeLabel(target, elementType); - - window.parent.postMessage({ - type: 'webild-element-hover', - data: getElementInfo(target, false) - }, '*'); - } - } - }, 150); - - window.parent.postMessage({ - type: 'webild-iframe-scroll' - }, '*'); - }; - - const getStorageKey = () => { - const url = new URL(window.location.href); - const pathParts = url.pathname.split('/').filter(Boolean); - return `webild-changes-${pathParts.join('-')}`; - }; - - const saveChangeToStorage = (change) => { - try { - const storageKey = getStorageKey(); - const existingChanges = JSON.parse(localStorage.getItem(storageKey) || '[]'); - - const filteredChanges = existingChanges.filter(c => { - return !(c.oldValue === change.oldValue && c.sectionId === change.sectionId); - }); - filteredChanges.push(change); - - localStorage.setItem(storageKey, JSON.stringify(filteredChanges)); - - window.parent.postMessage({ - type: 'webild-change-saved-locally', - data: { change, allChanges: filteredChanges } - }, '*'); - } catch (error) { - console.error('Failed to save change to localStorage:', error); - } - }; - - const clearLocalChanges = () => { - try { - const storageKey = getStorageKey(); - localStorage.removeItem(storageKey); - window.parent.postMessage({ - type: 'webild-local-changes-cleared', - data: {} - }, '*'); - } catch (error) { - console.error('Failed to clear local changes:', error); - } - }; - - const handleMessage = (e) => { - if (!e.data || !e.data.type) return; - - if (e.data.type === 'webild-activate-editor') { - if (!isActive) { - isActive = true; - window.parent.postMessage({ type: 'webild-editor-activated' }, '*'); - } - return; - } - - if (e.data.type === 'webild-deactivate-editor') { - if (isActive) { - isActive = false; - - if (selectedElement) { - makeUneditable(selectedElement, false); - selectedElement.classList.remove(selectedClass); - selectedElement = null; - } - if (hoveredElement) { - hoveredElement.classList.remove(hoverClass); - hoveredElement = null; - } - - removeHoverOverlay(); - removeElementTypeLabel(); - window.parent.postMessage({ type: 'webild-editor-deactivated' }, '*'); - } - return; - } - - if (e.data.type === 'webild-clear-local-changes') { - clearLocalChanges(); - return; - } - - if (e.data.type === 'webild-cancel-changes') { - try { - const storageKey = getStorageKey(); - const savedChanges = localStorage.getItem(storageKey); - if (savedChanges) { - const changes = JSON.parse(savedChanges); - changes.forEach(change => { - try { - const element = document.querySelector(change.selector); - if (!element) return; - - if (change.type === 'updateText') { - if (isTextElement(element)) { - element.textContent = change.oldValue; - } - } else if (change.type === 'updateButton') { - if (isButtonElement(element)) { - updateButtonText(element, change.oldValue); - } - } else if (change.type === 'replaceImage') { - const revertTag = element.tagName.toLowerCase(); - const isBackground = revertTag !== 'img' && revertTag !== 'video'; - if (isBackground) { - element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : ''; - } else { - 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) { - console.warn('[Webild] Failed to revert change:', err); - } - }); - } - clearLocalChanges(); - } catch (error) { - console.error('[Webild] Failed to cancel changes:', error); - } - return; - } - - if (e.data.type === 'webild-update-text') { - const { selector, newValue, oldValue, sectionId } = e.data.data; - try { - let element = null; - - if (selectedElement && isTextElement(selectedElement)) { - element = selectedElement; - } - else if (selector) { - try { - element = document.querySelector(selector); - } catch (err) { - console.warn('[Webild] Invalid selector:', selector); - } - } - - if (!element && sectionId) { - const sectionElement = document.querySelector('[data-section="' + sectionId + '"]'); - if (sectionElement) { - const textElements = sectionElement.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, button, div'); - for (let i = 0; i < textElements.length; i++) { - const el = textElements[i]; - if (isTextElement(el) && el.textContent.trim() === (oldValue || '').trim()) { - element = el; - const newSelector = getUniqueSelector(element, true); - if (newSelector) { - element.dataset.webildSelector = newSelector; - } - break; - } - } - } - } - - if (element && isTextElement(element)) { - element.textContent = newValue; - const finalSelector = element.dataset.webildSelector || getUniqueSelector(element, true); - - window.parent.postMessage({ - type: 'webild-text-update-success', - data: { - selector: finalSelector, - newValue: newValue - } - }, '*'); - } else { - window.parent.postMessage({ - type: 'webild-text-update-failed', - data: { selector, newValue } - }, '*'); - } - } catch (error) { - window.parent.postMessage({ - type: 'webild-text-update-failed', - data: { selector, newValue, error: error.message } - }, '*'); - } - return; - } - - if (e.data.type === 'webild-update-button') { - const { selector, text, href } = e.data.data; - try { - const element = document.querySelector(selector); - if (element && isButtonElement(element)) { - if (text !== undefined) { - updateButtonText(element, text); - } - if (href !== undefined) { - if (element.tagName.toLowerCase() === 'a') { - element.href = href; - } else { - element.setAttribute('data-href', href); - } - } - } - } catch (error) { - console.error('[Webild] Invalid selector for button update:', selector, error); - } - return; - } - - if (!isActive) return; - - if (e.data.type === 'webild-replace-image') { - const { selector, newSrc, isBackground, allowMediaTypeSwap } = e.data.data; - let element = null; - - try { - element = document.querySelector(selector); - } catch { - window.parent.postMessage({ - type: 'webild-image-replacement-error', - data: { selector, message: 'Invalid selector: ' + error.message, success: false } - }, '*'); - return; - } - - if (!element) { - window.parent.postMessage({ - type: 'webild-image-replacement-error', - data: { selector, message: 'Element not found', success: false } - }, '*'); - return; - } - - try { - let replaced = false; - let oldValue = ''; - - if (isBackground) { - oldValue = window.getComputedStyle(element).backgroundImage; - element.style.backgroundImage = `url('${newSrc}')`; - replaced = true; - } else if (element.tagName.toLowerCase() === 'img') { - oldValue = element.src; - const newMediaType = getMediaTypeFromUrl(newSrc); - 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'); - if (newMediaType === 'image' && allowMediaTypeSwap) { - const swapped = swapMediaElement(element, 'img', newSrc); - if (selectedElement === element) selectedElement = swapped; - element = swapped; - } else { - if (sources.length > 0) { - sources.forEach(function(source) { source.src = newSrc; }); - element.load(); - } else { - element.src = newSrc; - element.load(); - } - } - replaced = true; - } else { - const hasBackgroundImage = window.getComputedStyle(element).backgroundImage !== 'none'; - if (hasBackgroundImage) { - oldValue = window.getComputedStyle(element).backgroundImage; - element.style.backgroundImage = `url('${newSrc}')`; - replaced = true; - } - } - - if (replaced) { - const elementInfo = getElementInfo(element); - - let cleanOldValue = oldValue; - if (oldValue.includes('url(')) { - const urlMatch = oldValue.match(/url(['"]?([^'")]+)['"]?)/); - if (urlMatch) { - cleanOldValue = urlMatch[1]; - } - } - - cleanOldValue = extractOriginalUrl(cleanOldValue); - - const change = { - type: 'replaceImage', - selector: selector, - oldValue: cleanOldValue, - newValue: newSrc, - elementType: elementInfo.elementType, - sectionId: elementInfo.sectionId, - timestamp: Date.now() - }; - - saveChangeToStorage(change); - - window.parent.postMessage({ - type: 'webild-element-changed', - data: change - }, '*'); - - window.parent.postMessage({ - type: 'webild-image-replaced', - data: { selector, newSrc, success: true } - }, '*'); - } else { - window.parent.postMessage({ - type: 'webild-image-replacement-error', - data: { selector, message: 'Could not determine how to replace image', success: false } - }, '*'); - } - } catch (error) { - window.parent.postMessage({ - type: 'webild-image-replacement-error', - data: { selector, message: error.message || 'Failed to replace image', success: false } - }, '*'); - } - } - }; - - document.addEventListener('mouseover', handleMouseOver, true); - document.addEventListener('mouseout', handleMouseOut, true); - document.addEventListener('click', handleClick, true); - document.addEventListener('keydown', handleKeyDown, true); - document.addEventListener('blur', handleBlur, true); - window.addEventListener('scroll', handleScroll, true); - window.addEventListener('message', handleMessage, true); - - let lastPathname = window.location.pathname; - - const notifyPageChange = () => { - window.parent.postMessage({ - type: 'webild-page-changed', - data: { pathname: window.location.pathname } - }, '*'); - }; - - window.addEventListener('popstate', () => { - if (lastPathname !== window.location.pathname) { - lastPathname = window.location.pathname; - notifyPageChange(); - } - }, true); - - const urlCheckInterval = setInterval(() => { - if (lastPathname !== window.location.pathname) { - lastPathname = window.location.pathname; - notifyPageChange(); - } - }, 500); - - notifyPageChange(); - - window.webildCleanup = () => { - isActive = false; - - if (selectedElement) { - makeUneditable(selectedElement, false); - } - - removeHoverOverlay(); - removeElementTypeLabel(); - - if (urlCheckInterval) { - clearInterval(urlCheckInterval); - } - - document.removeEventListener('mouseover', handleMouseOver, true); - document.removeEventListener('mouseout', handleMouseOut, true); - document.removeEventListener('click', handleClick, true); - document.removeEventListener('keydown', handleKeyDown, true); - document.removeEventListener('blur', handleBlur, true); - window.removeEventListener('scroll', handleScroll, true); - window.removeEventListener('message', handleMessage, true); - - document.querySelectorAll('.' + hoverClass).forEach(el => { - el.classList.remove(hoverClass); - }); - document.querySelectorAll('.' + selectedClass).forEach(el => { - el.classList.remove(selectedClass); - }); - - const styleEl = document.getElementById('webild-inspector-styles'); - if (styleEl) styleEl.remove(); - - hoveredElement = null; - selectedElement = null; - }; - - window.parent.postMessage({ type: 'webild-editor-ready' }, '*'); -})(); -` - }} - /> - -