Linux heracles.o2switch.net 4.18.0-553.62.1.lve.el8.x86_64 #1 SMP Mon Jul 21 17:50:35 UTC 2025 x86_64
/
home
/
ophq1335
/
www
/
wp-content
/
plugins
/
wp-seopress
/
assets
/
js
/
//home/ophq1335/www/wp-content/plugins/wp-seopress/assets/js/seopress-metabox-promo.js
/** * SEOPress Metabox Promotion Banner * * Injects a promotional banner into the universal metabox sidebar. * Watches for the metabox dialog to open and injects the banner. * * @package SEOPress * @since 9.6.0 */ (function() { 'use strict'; // Check dependencies if (!window.wp || !window.wp.element) { return; } const { createElement, useState, render } = wp.element; // React 18+ uses createRoot, fallback to render for older versions const createRoot = wp.element.createRoot || null; /** * Metabox Promo Banner Component */ function MetaboxPromoBanner({ promotion, onDismiss }) { const [isVisible, setIsVisible] = useState(true); if (!isVisible || !promotion) { return null; } const handleDismiss = (e) => { e.preventDefault(); e.stopPropagation(); setIsVisible(false); if (onDismiss) { onDismiss(promotion.id, promotion.dismiss_duration || 30); } }; const handleCtaClick = () => { // Track click if (window.seopressPromotions && window.seopressPromotions.stats_endpoint) { if (navigator.sendBeacon) { const data = new FormData(); data.append('ad_id', promotion.id); data.append('action', 'click'); navigator.sendBeacon(window.seopressPromotions.stats_endpoint, data); } } }; const bgColor = promotion.styling?.background_color || '#4E21E7'; const textColor = promotion.styling?.text_color || '#FFFFFF'; const buttonStyle = promotion.styling?.button_style || 'primary'; // Button styles based on API setting const getButtonStyles = () => { const baseStyles = { display: 'inline-block', padding: '10px 16px', borderRadius: '6px', textDecoration: 'none', fontSize: '12px', fontWeight: '600', textAlign: 'center', marginTop: '4px', width: '100%', boxSizing: 'border-box', cursor: 'pointer', border: 'none' }; switch (buttonStyle) { case 'secondary': // Outline style return { ...baseStyles, backgroundColor: 'transparent', color: textColor, border: `2px solid ${textColor}` }; case 'link': // Text link style return { ...baseStyles, backgroundColor: 'transparent', color: textColor, padding: '10px 0', textDecoration: 'underline' }; case 'primary': default: // Filled style (default) return { ...baseStyles, backgroundColor: textColor, color: bgColor }; } }; // Compact vertical layout for narrow sidebar return createElement( 'div', { className: 'seopress-metabox-promo-banner', style: { backgroundColor: bgColor, color: textColor, padding: '16px', borderRadius: '8px', position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '8px' } }, // Dismiss button (top right) promotion.dismissible !== false && createElement( 'button', { onClick: handleDismiss, style: { position: 'absolute', top: '8px', right: '8px', background: 'none', border: 'none', color: textColor, cursor: 'pointer', padding: '4px', opacity: 0.7, display: 'flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1 }, 'aria-label': 'Dismiss' }, createElement('span', { className: 'dashicons dashicons-no-alt', style: { fontSize: '16px', width: '16px', height: '16px' } }) ), // Icon promotion.icon && createElement( 'div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px', backgroundColor: 'rgba(255,255,255,0.15)', borderRadius: '6px' } }, createElement('span', { className: `dashicons dashicons-${promotion.icon}`, style: { color: textColor, fontSize: '18px', width: '18px', height: '18px' } }) ), // Title promotion.title && createElement( 'div', { style: { fontWeight: '600', fontSize: '13px', lineHeight: '1.4', paddingRight: '24px', marginTop: '4px' } }, promotion.title ), // Body promotion.body && createElement( 'div', { style: { fontSize: '12px', opacity: 0.9, lineHeight: '1.5' } }, promotion.body ), // CTA button promotion.cta_url && createElement( 'a', { href: promotion.cta_url, target: '_blank', rel: 'noopener noreferrer', onClick: handleCtaClick, style: getButtonStyles() }, promotion.cta_text || 'Learn More' ) ); } /** * Handle dismiss action */ function handleDismiss(promoId, duration) { if (window.seopressPromotions && window.seopressPromotions.dismiss_nonce) { const formData = new FormData(); formData.append('action', 'seopress_dismiss_promotion'); formData.append('promo_id', promoId); formData.append('duration', duration); formData.append('_ajax_nonce', window.seopressPromotions.dismiss_nonce); fetch(window.ajaxurl || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }); // Track dismiss if (window.seopressPromotions.stats_endpoint) { if (navigator.sendBeacon) { const data = new FormData(); data.append('ad_id', promoId); data.append('action', 'dismiss'); navigator.sendBeacon(window.seopressPromotions.stats_endpoint, data); } } } } /** * Inject the banner into the metabox */ function injectBanner(container) { // Check if already injected anywhere in the document if (document.querySelector('.seopress-metabox-promo-root')) { return; } const promotion = window.seopressMetaboxPromo?.promotion; if (!promotion) { return; } // Create mount point const mountPoint = document.createElement('div'); mountPoint.className = 'seopress-metabox-promo-root'; mountPoint.style.cssText = 'padding: 16px;'; // Find the main flex container (the one with height: 100%) const mainFlex = container.querySelector('.flex[style*="height: 100%"], .flex[style*="height:100%"]'); let sidebar = null; if (mainFlex) { // The sidebar is the first child of this flex sidebar = mainFlex.firstElementChild; // Verify it looks like a sidebar (narrow width) if (sidebar) { const rect = sidebar.getBoundingClientRect(); if (rect.width > 300) { // Too wide, might not be the sidebar sidebar = null; } } } // Alternative: find by looking for styled-component with narrow width if (!sidebar) { const styledDivs = container.querySelectorAll('[class*="sc-"]'); for (const div of styledDivs) { const rect = div.getBoundingClientRect(); const text = div.textContent || ''; // Sidebar should be narrow and contain the tabs if (rect.width > 100 && rect.width < 280 && rect.height > 200 && text.includes('OVERVIEW')) { sidebar = div; break; } } } if (sidebar) { // Fix sidebar height to show full banner (remove 85% height restriction) sidebar.style.height = 'auto'; sidebar.style.paddingBottom = '35px'; mountPoint.style.cssText = 'padding: 12px; margin-top: auto;'; sidebar.appendChild(mountPoint); } else { // Fallback: insert in the content area const scrollArea = container.querySelector('[class*="scroll"]'); if (scrollArea) { mountPoint.style.cssText = 'padding: 16px;'; scrollArea.insertBefore(mountPoint, scrollArea.firstChild); } else { container.appendChild(mountPoint); } } // Render the banner (React 18+ uses createRoot, fallback to render for older versions) const bannerElement = createElement(MetaboxPromoBanner, { promotion: promotion, onDismiss: handleDismiss }); if (createRoot) { const root = createRoot(mountPoint); root.render(bannerElement); } else { render(bannerElement, mountPoint); } } /** * Check if metabox is visible */ function isMetaboxVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); // Check basic visibility if (style.display === 'none' || style.visibility === 'hidden') { return false; } // For fixed-positioned elements, offsetParent is always null // So we need a different check - verify it has dimensions if (style.position === 'fixed') { return element.offsetWidth > 0 && element.offsetHeight > 0; } return element.offsetParent !== null; } /** * Find and inject into metabox */ function tryInject() { // Look for the universal metabox dialog const metabox = document.querySelector('.sp-seo-metabox'); if (metabox && isMetaboxVisible(metabox)) { injectBanner(metabox); return true; } // Also try classic editor metabox const classicMetabox = document.querySelector('#seopress_cpt .inside'); if (classicMetabox && isMetaboxVisible(classicMetabox)) { injectBanner(classicMetabox); return true; } return false; } /** * Initialize with MutationObserver */ function init() { // Check if promotion data exists if (!window.seopressMetaboxPromo || !window.seopressMetaboxPromo.promotion) { return; } // Try to inject immediately tryInject(); // Set up MutationObserver to watch for metabox visibility changes const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // Check for added nodes if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList?.contains('sp-seo-metabox') || node.querySelector?.('.sp-seo-metabox')) { setTimeout(tryInject, 100); } } } } // Check for style/attribute changes (dialog opening) if (mutation.type === 'attributes') { if (mutation.target.classList?.contains('sp-seo-metabox')) { setTimeout(tryInject, 100); } } } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); // Also poll periodically as a fallback let attempts = 0; const maxAttempts = 60; // 30 seconds const pollInterval = setInterval(() => { attempts++; if (tryInject() || attempts >= maxAttempts) { clearInterval(pollInterval); } }, 500); // Also try on Gutenberg editor ready if (wp.domReady) { wp.domReady(() => { setTimeout(tryInject, 500); setTimeout(tryInject, 1500); }); } // Timeout to disconnect observer after 60 seconds setTimeout(() => { observer.disconnect(); }, 60000); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();