Files
2c3b9f6b-097d-4d86-b836-adb…/src/app/layout.tsx
2026-02-09 22:48:28 +00:00

1264 lines
40 KiB
TypeScript

import type { Metadata } from "next";
import { Montserrat } from "next/font/google";
import "./globals.css";
import { ServiceWrapper } from "@/components/ServiceWrapper";
import Tag from "@/tag/Tag";
const montserrat = Montserrat({
variable: "--font-montserrat", subsets: ["latin"],
});
export const metadata: Metadata = {
title: "NewsHub - Premium Newsletter for Curated Insights", description: "Stay informed with NewsHub's weekly curated newsletter. Get exclusive insights, industry trends, and thoughtful analysis delivered to your inbox. Subscribe free today.", keywords: ["newsletter", "curated content", "insights", "industry trends", "email subscription"],
robots: {
index: true,
follow: true,
},
openGraph: {
title: "NewsHub - Curated Stories & Insights", description: "Discover premium insights and industry trends curated just for you. Subscribe to NewsHub's weekly newsletter.", type: "website", siteName: "NewsHub", images: [
{
url: "https://img.b2bpic.net/free-vector/computer-with-social-profile-social-community_24877-53905.jpg", alt: "NewsHub Newsletter Platform"
},
],
},
twitter: {
card: "summary_large_image", title: "NewsHub - Curated Insights Newsletter", description: "Join thousands discovering quality content. Subscribe to NewsHub's premium newsletter.", images: ["https://img.b2bpic.net/free-vector/computer-with-social-profile-social-community_24877-53905.jpg"],
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<ServiceWrapper>
<body
className={`${montserrat.variable} antialiased`}
>
<Tag />
{children}
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
if (window.self === window.top) return;
if (window.__webildEditorInitialized) return;
window.__webildEditorInitialized = true;
let isActive = false;
let hoveredElement = null;
let selectedElement = null;
let originalContent = null;
let isEditing = false;
let elementTypeLabel = null;
let hoverOverlay = null;
let scrollTimeout = null;
let isScrolling = false;
const invalidElements = ['html', 'body', 'script', 'style', 'meta', 'link', 'head', 'noscript', 'title'];
const hoverClass = 'webild-hover';
const selectedClass = 'webild-selected';
const style = document.createElement('style');
style.id = 'webild-inspector-styles';
style.textContent = '' +
'.webild-hover {' +
' outline: 2px dashed #4d96ff80 !important;' +
' border-radius: 0 !important;' +
' outline-offset: 2px !important;' +
' cursor: pointer !important;' +
' transition: outline 0.15s ease !important;' +
' background-color: #4d96ff05 !important;' +
'}' +
'.webild-selected {' +
' outline: 2px solid #4d96ff !important;' +
' outline-offset: 2px !important;' +
' transition: outline 0.15s ease !important;' +
' background-color: #4d96ff05 !important;' +
' border-radius: 0 !important;' +
'}' +
'[contenteditable="true"].webild-selected {' +
' outline: 2px solid #4d96ff !important;' +
' background-color: #4d96ff05 !important;' +
'}' +
'img.webild-hover,' +
'img.webild-selected {' +
' outline-offset: 2px !important;' +
'}' +
'.webild-element-type-label {' +
' position: fixed !important;' +
' z-index: 999999 !important;' +
' background: #4d96ff !important;' +
' color: white !important;' +
' padding: 4px 8px !important;' +
' font-size: 11px !important;' +
' font-weight: 600 !important;' +
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;' +
' pointer-events: none !important;' +
' white-space: nowrap !important;' +
' box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;' +
' letter-spacing: 0.3px !important;' +
' border: 1px solid #4d96ff20 !important;' +
'}' +
'.webild-element-type-label.label-top {' +
' border-radius: 6px 6px 0 0 !important;' +
'}' +
'.webild-element-type-label.label-bottom {' +
' border-radius: 0 0 6px 6px !important;' +
'}' +
'.webild-hover-overlay {' +
' position: fixed !important;' +
' background-color: #4d96ff15 !important;' +
' pointer-events: none !important;' +
' z-index: 999998 !important;' +
' transition: all 0.15s ease !important;' +
'}';
document.head.appendChild(style);
const getUniqueSelector = (element, assignId = false) => {
if (element.dataset && element.dataset.webildSelector) {
return element.dataset.webildSelector;
}
const existingId = element.getAttribute('data-webild-id');
if (existingId) {
return '[data-webild-id="' + existingId + '"]';
}
if (assignId) {
const uniqueId = 'webild-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
element.setAttribute('data-webild-id', uniqueId);
return '[data-webild-id="' + uniqueId + '"]';
}
return null;
};
const getSectionId = (element) => {
let current = element;
while (current && current !== document.body) {
const sectionId = current.getAttribute('data-section');
if (sectionId) {
return sectionId;
}
current = current.parentElement;
}
return 'hero';
};
const getElementType = (element) => {
const tagName = element.tagName.toLowerCase();
const computedStyle = window.getComputedStyle(element);
if (tagName === 'img') {
return 'Image';
}
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('.webildsbx.cc/')) {
try {
const urlObj = new URL(url);
return urlObj.pathname;
} catch (e) {
return url;
}
}
return url;
};
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
};
}
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';
if (isImage) 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' || computedStyle.backgroundImage !== 'none' || element.querySelector('img');
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';
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.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 (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 isBackground = element.tagName.toLowerCase() !== 'img';
if (isBackground) {
element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : '';
} 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 } = 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;
element.src = newSrc;
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);
window.webildCleanup = () => {
isActive = false;
if (selectedElement) {
makeUneditable(selectedElement, false);
}
removeHoverOverlay();
removeElementTypeLabel();
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' }, '*');
})();
`
}}
/>
</body>
</ServiceWrapper>
</html>
);
}