1/** 2 * AI Share & Summarize Plugin - JavaScript 3 * Advanced functionality for tooltips, hover effects, responsive behavior, click tracking 4 * and the visitor-facing AI summary generation button (v2.0.0). 5 * Version: 2.0.1 6 */ 7 8/** 9 * Optional dark-background detection. 10 * 11 * Active only when the admin selects the "auto" dark-mode adaptation mode. 12 * Walks up from each share-buttons container looking for the first opaque 13 * background, computes its WCAG relative luminance, and toggles the 14 * .is-on-dark class accordingly. Runs once on DOMContentLoaded and again 15 * when the OS-level prefers-color-scheme flips (themes that wire dark mode 16 * to that media query change their background at that moment, so we need 17 * to re-measure). No MutationObserver is installed to keep runtime cost 18 * negligible. 19 */ 20(function() { 21 if ( ! window.ayudawpAissL10n || 'auto' !== window.ayudawpAissL10n.darkAdaptMode ) { 22 return; 23 } 24 25 function detect() { 26 var nodes = document.querySelectorAll( 27 '.ayudawp-share-buttons:not(.dark):not(.custom), .ayudawp-aiss-summary' 28 ); 29 for ( var i = 0; i < nodes.length; i++ ) { 30 var node = nodes[ i ].parentElement; 31 var bg = ''; 32 while ( node && node !== document.documentElement ) { 33 var computed = window.getComputedStyle( node ).backgroundColor; 34 if ( computed && 'rgba(0, 0, 0, 0)' !== computed && 'transparent' !== computed ) { 35 bg = computed; 36 break; 37 } 38 node = node.parentElement; 39 } 40 if ( ! bg ) { 41 continue; 42 } 43 var match = bg.match( /\d+(\.\d+)?/g ); 44 if ( ! match || match.length < 3 ) { 45 continue; 46 } 47 var r = parseFloat( match[ 0 ] ) / 255; 48 var g = parseFloat( match[ 1 ] ) / 255; 49 var b = parseFloat( match[ 2 ] ) / 255; 50 var lum = 0.2126 * r + 0.7152 * g + 0.0722 * b; 51 nodes[ i ].classList.toggle( 'is-on-dark', lum < 0.5 ); 52 } 53 } 54 55 if ( 'loading' === document.readyState ) { 56 document.addEventListener( 'DOMContentLoaded', detect ); 57 } else { 58 detect(); 59 } 60 61 if ( window.matchMedia ) { 62 var mq = window.matchMedia( '(prefers-color-scheme: dark)' ); 63 if ( mq.addEventListener ) { 64 mq.addEventListener( 'change', detect ); 65 } else if ( mq.addListener ) { 66 mq.addListener( detect ); 67 } 68 } 69})(); 70 71jQuery(document).ready(function($) { 72 73 /**
74 * Platforms that require copy-to-clipboard behavior 75 * These platforms don't support direct URL parameters for prompts 76 */ 77 var copyPromptPlatforms = ['gemini', 'deepseek', 'copilot', 'qwen', 'meta_ai']; 78 79 /** 80 * Platform base URLs for copy-to-clipboard platforms 81 */ 82 var platformBaseUrls = { 83 'gemini': 'https://gemini.google.com/app', 84 'deepseek': 'https://chat.deepseek.com/', 85 'copilot': 'https://copilot.microsoft.com/', 86 'qwen': 'https://chat.qwen.ai/', 87 'meta_ai': 'https://www.meta.ai/' 88 }; 89 90 /** 91 * Universal tooltip system for all buttons 92 */ 93 function attachUniversalTooltips() { 94 $('.ayudawp-tooltip').remove(); 95 96 $('.ayudawp-share-btn').each(function() { 97 var $button = $(this); 98 var platform = detectPlatform($button); 99 var tooltipText = ''; 100 101 if (copyPromptPlatforms.indexOf(platform) !== -1) { 102 var isIconsOnly = $button.closest('.ayudawp-share-buttons').hasClass('icons-only'); 103 var platformName = ''; 104 105 if (platform === 'gemini') { 106 platformName = 'Gemini'; 107 } else if (platform === 'deepseek') { 108 platformName = 'DeepSeek'; 109 } else if (platform === 'copilot') { 110 platformName = 'Copilot'; 111 } else if (platform === 'qwen') { 112 platformName = 'Qwen'; 113 } else if (platform === 'meta_ai' || platform === 'meta-ai') { 114 platformName = 'Meta AI'; 115 } 116 117 if (isIconsOnly) { 118 tooltipText = (window.ayudawpAissL10n && window.ayudawpAissL10n[platform + 'TooltipShort']) || 119 (window.ayudawpAissL10n && window.ayudawpAissL10n.copyPromptShort) || 120 'Copy prompt & open'; 121 } else { 122 tooltipText = (window.ayudawpAissL10n && window.ayudawpAissL10n[platform + 'TooltipLong']) || 123 (window.ayudawpAissL10n && window.ayudawpAissL10n.copyPromptLong) || 124 ('Copy prompt and open ' + platformName); 125 } 126 } else if (platform && window.ayudawpAissL10n && window.ayudawpAissL10n.platformNames) { 127 tooltipText = window.ayudawpAissL10n.platformNames[platform] || platform; 128 } 129 130 if (tooltipText) { 131 var $tooltip = $('<div class="ayudawp-tooltip">' + tooltipText + '</div>'); 132 $('body').append($tooltip); 133 134 $button.on('mouseenter', function() { 135 var offset = $button.offset(); 136 var buttonWidth = $button.outerWidth(); 137 var tooltipWidth = $tooltip.outerWidth(); 138 var tooltipHeight = $tooltip.outerHeight(); 139 140 $tooltip.css({ 141 top: offset.top - tooltipHeight - 8, 142 left: offset.left + (buttonWidth / 2) - (tooltipWidth / 2) 143 }).addClass('active'); 144 }); 145 146 $button.on('mouseleave', function() { 147 $tooltip.removeClass('active'); 148 }); 149 150 $button.data('tooltip', $tooltip); 151 } 152 }); 153 } 154 155 /** 156 * Detect platform from button data attribute or class 157 * 158 * @param {jQuery} $button Button element 159 * @return {string} Platform identifier 160 */ 161 function detectPlatform($button) { 162 var platform = $button.attr('data-platform'); 163 164 if (!platform) { 165 var classes = $button.attr('class').split(' '); 166 var platforms = ['twitter', 'linkedin', 'facebook', 'telegram', 'whatsapp', 'email', 'raindrop', 167 'reddit', 'bluesky', 'line', 'mastodon', 'threads', 'pinterest', 168 'claude', 'chatgpt', 'google-ai', 'gemini', 169 'grok', 'perplexity', 'deepseek', 'mistral', 'copilot', 'qwen', 'meta-ai']; 170 for (var i = 0; i < platforms.length; i++) { 171 if (classes.indexOf(platforms[i]) !== -1) { 172 platform = platforms[i]; 173 break; 174 } 175 } 176 } 177 178 return platform || ''; 179 } 180 181 /** 182 * Enhanced click handling with proper platform detection and analytics tracking 183 */ 184 function attachClickHandlers() { 185 $(document).off('click.ayudawp').on('click.ayudawp', '.ayudawp-share-btn', function(e) { 186 e.preventDefault(); 187 e.stopPropagation(); 188 189 var $button = $(this); 190 var url = $button.attr('data-url') || $button.attr('href'); 191 var platform = detectPlatform($button); 192 193 if (!url) { 194 return false;
195 } 196 197 // Track the click (fire and forget). 198 trackClick(platform, $button); 199 200 // Special handling for platforms that require copy-to-clipboard. 201 if (copyPromptPlatforms.indexOf(platform) !== -1) { 202 handleCopyPromptClick(url, platform); 203 return false; 204 } 205 206 // Social media popups. 207 if (['twitter', 'linkedin', 'facebook', 'telegram', 'reddit', 'bluesky'].indexOf(platform) !== -1) { 208 var width = 600, height = 400; 209 if (platform === 'reddit') { 210 width = 900; 211 height = 600; 212 } 213 var left = (screen.width / 2) - (width / 2); 214 var top = (screen.height / 2) - (height / 2); 215 216 window.open(url, 'share', 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',scrollbars=yes,resizable=yes'); 217 return false; 218 } 219 220 // Default behavior for all other buttons. 221 window.open(url, '_blank', 'noopener,noreferrer'); 222 return false; 223 }); 224 } 225 226 /** 227 * Track button click via REST API (fire and forget) 228 * No nonce needed: public non-destructive endpoint, compatible with page caching. 229 * 230 * @param {string} platform Platform identifier 231 * @param {jQuery} $button The clicked button element 232 */ 233 function trackClick(platform, $button) { 234 if (!window.ayudawpAissL10n || !window.ayudawpAissL10n.trackUrl) { 235 return; 236 } 237 238 // Get post ID from the closest container or page body. 239 var postId = $button.closest('[data-post-id]').attr('data-post-id') || 240 $('article[id^="post-"]').first().attr('id'); 241 242 if (postId && typeof postId === 'string') { 243 postId = postId.replace('post-', ''); 244 } 245 246 // Normalize platform names (google-ai -> google_ai). 247 var normalizedPlatform = platform ? platform.replace(/-/g, '_') : ''; 248 249 if (!normalizedPlatform) { 250 return; 251 } 252 253 // Fire and forget - don't block user interaction. 254 try { 255 var xhr = new XMLHttpRequest(); 256 xhr.open('POST', window.ayudawpAissL10n.trackUrl, true); 257 xhr.setRequestHeader('Content-Type', 'application/json'); 258 xhr.send(JSON.stringify({ 259 platform: normalizedPlatform, 260 post_id: parseInt(postId, 10) || 0 261 })); 262 } catch (err) { 263 // Silently fail - tracking should never break user experience. 264 } 265 } 266 267 /**
268 * Handle click for platforms that require copy-to-clipboard behavior 269 * Works for Gemini, DeepSeek, and Copilot 270 * 271 * @param {string} url - The URL containing the prompt parameter 272 * @param {string} platform - The platform identifier 273 */ 274 function handleCopyPromptClick(url, platform) { 275 try { 276 var urlParts = url.split('?'); 277 var queryString = urlParts.length > 1 ? urlParts[1] : ''; 278 var urlParams = new URLSearchParams(queryString); 279 var prompt = urlParams.get('prompt') || urlParams.get('q') || ''; 280 var baseUrl = platformBaseUrls[platform] || urlParts[0]; 281 282 if (prompt) { 283 copyToClipboard(decodeURIComponent(prompt)).then(function() { 284 showNotification(window.ayudawpAissL10n.promptCopied || 'Prompt copied!', 'success'); 285 setTimeout(function() { 286 window.open(baseUrl, '_blank', 'noopener,noreferrer'); 287 }, 500); 288 }).catch(function() { 289 window.open(baseUrl, '_blank', 'noopener,noreferrer'); 290 }); 291 } else { 292 window.open(baseUrl, '_blank', 'noopener,noreferrer'); 293 } 294 } catch (error) { 295 var fallbackUrl = platformBaseUrls[platform] || url.split('?')[0]; 296 window.open(fallbackUrl, '_blank', 'noopener,noreferrer'); 297 } 298 } 299 300 /** 301 * Copy to clipboard function 302 */ 303 function copyToClipboard(text) { 304 if (navigator.clipboard && window.isSecureContext) { 305 return navigator.clipboard.writeText(text); 306 } else { 307 var textArea = document.createElement('textarea'); 308 textArea.value = text; 309 textArea.style.position = 'fixed'; 310 textArea.style.left = '-999999px'; 311 textArea.style.top = '-999999px'; 312 document.body.appendChild(textArea); 313 textArea.focus(); 314 textArea.select(); 315 316 return new Promise(function(resolve, reject) { 317 try { 318 document.execCommand('copy') ? resolve() : reject(); 319 } catch (err) { 320 reject(err); 321 } finally { 322 textArea.remove(); 323 } 324 }); 325 } 326 } 327 328 /** 329 * Show notification 330 */ 331 function showNotification(message, type) { 332 if (document.querySelector('.ayudawp-notification')) return; 333 334 var notification = document.createElement('div'); 335 notification.className = 'ayudawp-notification ayudawp-notification-' + type; 336 notification.textContent = message; 337 notification.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#333;color:white;padding:16px 24px;border-radius:8px;font-size:14px;font-weight:500;z-index:9999;box-shadow:0 8px 32px rgba(0,0,0,0.3);opacity:0;transition:all 0.4s ease;border:1px solid #555;'; 338 339 document.body.appendChild(notification); 340 341 setTimeout(function() { 342 notification.style.opacity = '1'; 343 }, 50); 344 345 setTimeout(function() { 346 notification.style.opacity = '0'; 347 notification.style.transform = 'translate(-50%,-50%) scale(0.9)'; 348 setTimeout(function() { 349 if (notification.parentNode) { 350 notification.parentNode.removeChild(notification); 351 } 352 }, 400); 353 }, 2500); 354 } 355 356 /** 357 * Improve accessibility with keyboard navigation 358 */ 359 function enhanceKeyboardNavigation() { 360 $(document).on('keydown', '.ayudawp-share-btn', function(e) { 361 if (e.keyCode === 13 || e.keyCode === 32) { 362 e.preventDefault(); 363 $(this).click(); 364 } 365 }); 366 } 367 368 /** 369 * Enhanced responsive behavior for icons-only mode 370 */ 371 function handleResponsiveIconsOnly() { 372 var $iconsOnlyContainers = $('.ayudawp-share-buttons.icons-only .ayudawp-buttons-container'); 373 374 $iconsOnlyContainers.each(function() { 375 var $container = $(this); 376 var $buttons = $container.find('.ayudawp-share-btn'); 377 var containerWidth = $container.width(); 378 var buttonWidth = 44; 379 var gap = 10; 380 var buttonsPerRow = Math.floor((containerWidth + gap) / (buttonWidth + gap)); 381 382 if ($(window).width() <= 480 && buttonsPerRow < $buttons.length) { 383 $container.css({ 384 'display': 'grid', 385 'grid-template-columns': 'repeat(auto-fit, 44px)', 386 'gap': '8px', 387 'max-width': '300px', 388 'margin': '0 auto' 389 }); 390 } else { 391 $container.css({ 392 'display': 'flex', 393 'grid-template-columns': '', 394 'max-width': '', 395 'margin': '' 396 }); 397 } 398 }); 399 } 400 401 /** 402 * Smooth hover animation for all buttons 403 */
404 function attachHoverEffects() { 405 $(document).on('mouseenter', '.ayudawp-share-btn', function() { 406 var $this = $(this); 407 var platform = $this.attr('data-platform'); 408 if (copyPromptPlatforms.indexOf(platform) === -1) { 409 $this.css('transform', 'translateY(-2px)'); 410 } 411 }); 412 413 $(document).on('mouseleave', '.ayudawp-share-btn', function() { 414 var $this = $(this); 415 $this.css('transform', 'translateY(0)'); 416 }); 417 } 418 419 /** 420 * Initialize all features 421 */ 422 function initializeAllFeatures() { 423 attachUniversalTooltips(); 424 attachClickHandlers(); 425 enhanceKeyboardNavigation(); 426 handleResponsiveIconsOnly(); 427 attachHoverEffects(); 428 } 429 430 // Initialize on document ready. 431 initializeAllFeatures(); 432 433 // Handle window resize for responsive behavior. 434 $(window).on('resize', function() { 435 handleResponsiveIconsOnly(); 436 }); 437 438 // Re-initialize when new content is added dynamically. 439 $(document).on('DOMNodeInserted', function(e) { 440 if ($(e.target).hasClass('ayudawp-share-buttons') || $(e.target).find('.ayudawp-share-buttons').length > 0) { 441 setTimeout(function() { 442 attachUniversalTooltips(); 443 handleResponsiveIconsOnly(); 444 }, 100); 445 } 446 }); 447 448}); 449 450/** 451 * AI Summary frontend generation button (v2.0.0) 452 * 453 * Vanilla JS â runs outside jQuery so the handler survives aggressive 454 * defer/async optimizations from caching/perf plugins (e.g. wpo-tweaks, 455 * WP Rocket, LiteSpeed). Event delegation on document so it picks up 456 * placeholders rendered by shortcodes, page builders or AJAX-loaded 457 * content. Uses fetch() with credentials so it works for logged-in and 458 * anonymous visitors alike. 459 * 460 * @since 2.0.0 461 */ 462(function () { 463 'use strict'; 464 465 function ayudawpAissInitSummaryButton() { 466 if (document.body && document.body.__aissSummaryBtnBound) { 467 return; 468 } 469 if (document.body) { 470 document.body.__aissSummaryBtnBound = true; 471 } 472 473 document.addEventListener('click', function (e) { 474 var btn = e.target && e.target.closest ? e.target.closest('.ayudawp-aiss-summary-generate-btn') : null; 475 if (!btn) { 476 return; 477 } 478 e.preventDefault(); 479 480 var placeholder = btn.closest('.ayudawp-aiss-summary--placeholder'); 481 if (!placeholder) { 482 return; 483 } 484 var postId = parseInt(placeholder.getAttribute('data-post-id'), 10); 485 if (!postId || btn.disabled) { 486 return; 487 } 488 489 var L = window.ayudawpAissL10n || {}; 490 var labelEl = btn.querySelector('.ayudawp-aiss-summary-label'); 491 var originalLabel = labelEl ? labelEl.textContent : ''; 492 493 btn.disabled = true; 494 if (labelEl) { 495 labelEl.textContent = L.summaryGenerating || 'Generatingâ¦'; 496 } 497 498 var endpoint = L.publicGenerateUrl || (window.location.origin + '/wp-json/aiss/v1/summary/generate'); 499 500 fetch(endpoint, { 501 method: 'POST', 502 credentials: 'same-origin', 503 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, 504 body: JSON.stringify({ post_id: postId }) 505 }).then(function (response) { 506 return response.json().then(function (data) { 507 return { ok: response.ok, data: data }; 508 }); 509 }).then(function (result) { 510 if (result.ok && result.data && result.data.html) { 511 var wrapper = document.createElement('div'); 512 wrapper.innerHTML = result.data.html.trim(); 513 // The server response includes an inline <style> for the 514 // critical CSS plus the actual <aside> summary block. 515 // Picking firstChild would replace the placeholder with 516 // the invisible <style>, making the box disappear. Pick 517 // the real summary node instead (the .ayudawp-aiss-summary 518 // that is NOT the placeholder we're replacing). 519 var node = wrapper.querySelector('.ayudawp-aiss-summary:not(.ayudawp-aiss-summary--placeholder)'); 520 if (node && placeholder.parentNode) { 521 placeholder.parentNode.replaceChild(node, placeholder); 522 // A11y: open the freshly generated summary and move focus 523 // to its toggle so screen readers announce the new content 524 // that replaced the button the visitor just activated. 525 var details = node.querySelector('details.ayudawp-aiss-summary-details'); 526 if (details) { details.open = true; } 527 var toggle = node.querySelector('summary.ayudawp-aiss-summary-toggle') || node.querySelector('summary'); 528 if (toggle && typeof toggle.focus === 'function') { 529 try { toggle.focus(); } catch (e) {} 530 } 531 } 532 return; 533 } 534 var msg = (result.data && result.data.message) ? result.data.message : (L.summaryGenerateError || 'Generation failed. Try again.'); 535 if (labelEl) { labelEl.textContent = msg; } 536 window.setTimeout(function () { 537 btn.disabled = false;
538 if (labelEl) { labelEl.textContent = originalLabel; } 539 }, 3000); 540 }).catch(function () { 541 if (labelEl) { labelEl.textContent = L.summaryGenerateError || 'Generation failed. Try again.'; } 542 window.setTimeout(function () { 543 btn.disabled = false; 544 if (labelEl) { labelEl.textContent = originalLabel; } 545 }, 3000); 546 }); 547 }, false); 548 } 549 550 // Bind whenever the DOM is ready â works whether this script runs 551 // before or after DOMContentLoaded (covers defer/async loading). 552 if (document.readyState === 'loading') { 553 document.addEventListener('DOMContentLoaded', ayudawpAissInitSummaryButton); 554 } else { 555 ayudawpAissInitSummaryButton(); 556 } 557})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.