PageSourceSearch

https://cdn1.corresponsables.com/wp-content/plugins/ai-share-summa…/assets/js/ai-share-summarize.js?ver=2.5.0

js corresponsables.com collected 2026-10-01 09:17:38 UTC 25,387 bytes, 689 lines download raw bytes

1/**
2 * Share Buttons & AI-powered Summaries 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.5.0
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
71/**
72 * Share buttons runtime (rewritten without jQuery in v2.5.0)
73 *
74 * Everything below used to run inside jQuery(document).ready. The plugin no
75 * longer declares a jQuery dependency, so block themes that do not load it on
76 * the front end stop pulling in ~85 KB to show share buttons. Three things
77 * went away rather than being ported, because they were doing work the
78 * stylesheet already does or the browser does better:
79 *
80 *   - the hover handler that wrote `transform: translateY(-2px)` inline, which
81 *     the CSS :hover rule had been applying since 1.6.0;
82 *   - the resize handler that rewrote the icons-only grid with inline styles,
83 *     now a media query;
84 *   - the DOMNodeInserted listener, a Mutation Event removed from Chrome in
85 *     version 127, replaced by a MutationObserver that only watches for share
86 *     blocks arriving.
87 *
88 * @since 2.5.0
89 */
90(function () {
91    'use strict';
92
93    /**
94     * AI platforms with no way to pre-fill the prompt from the URL, so the
95     * prompt is copied to the clipboard and the site opened empty.
96     *
97     * Checked again on 12 Sep 2026 against each platform, with Perplexity's
98     * working ?q= as the positive control: Meta AI does pre-fill from
99     * ?prompt= and left this list in 2.5.0; Gemini, DeepSeek, Copilot and
100     * Qwen still do not.
101     */
102    var copyPromptPlatforms = ['gemini', 'deepseek', 'copilot', 'qwen'];
103
104    /**
105     * Landing pages for the copy-prompt platforms.
106     */
107    var platformBaseUrls = {
108        'gemini':   'https://gemini.google.com/app',
109        'deepseek': 'https://chat.deepseek.com/',
110        'copilot':  'https://copilot.microsoft.com/',
111        'qwen':     'https://chat.qwen.ai/'
112    };
113
114    /** Platforms that open in a sized popup window instead of a tab. */
115    var popupPlatforms = ['twitter', 'linkedin', 'facebook', 'telegram', 'reddit', 'bluesky'];
116
117    /** Buttons that act on the current page instead of opening a destination. */
118    var actionPlatforms = ['copy_link', 'web_share'];
119
120    var L = window.ayudawpAissL10n || {};
121    var tooltipEl = null;
122
123    function l10n(key, fallback) {
124        return (L && L[key]) ? L[key] : fallback;
125    }
126
127    /**
128     * Platform of a button, from its data attribute or, failing that, its class.
129     *
130     * @param {Element} btn Button element.
131     * @return {string} Platform key, '' when unknown.
132     */
133    function detectPlatform(btn) {
134        var platform = btn.getAttribute('data-platform');
135        if (platform) {
136            return platform;
137        }
138        var known = ['twitter', 'linkedin', 'facebook', 'telegram', 'whatsapp', 'email', 'raindrop',
139            'reddit', 'bluesky', 'line', 'mastodon', 'threads', 'pinterest',
140            'claude', 'chatgpt', 'google-ai', 'gemini', 'grok', 'perplexity',
141            'deepseek', 'mistral', 'copilot', 'qwen', 'meta-ai', 'copy-link', 'web-share'];
142        for (var i = 0; i < known.length; i++) {
143            if (btn.classList.contains(known[i])) {
144                return known[i].replace(/-/g, '_');
145            }
146        }
147        return '';
148    }
149
150    /**
151     * Tooltip text for a button, or '' when it needs none.
152     *
153     * @param {Element} btn      Button element.
154     * @param {string}  platform Platform key.
155     * @return {string} Tooltip text.
156     */
157    function tooltipFor(btn, platform) {
158        if (copyPromptPlatforms.indexOf(platform) !== -1) {
159            var block = btn.closest('.ayudawp-share-buttons');
160            var short = block && block.classList.contains('icons-only');
161            return short
162                ? l10n(platform + 'TooltipShort', l10n('copyPromptShort', 'Copy prompt & open'))
163                : l10n(platform + 'TooltipLong', l10n('copyPromptLong', 'Copy prompt and open'));
164        }
165        var names = L.platformNames || {};
166        return names[platform] || '';
167    }
168
169    /**
170     * Single shared tooltip element.
171     *
172     * One node reused by every button, instead of one node plus two listeners
173     * per button as before 2.5.0 — a 24-button block was adding 24 divs to
174     * <body> on every page load.
175     *
176     * @return {Element} The tooltip element.
177     */
178    function getTooltip() {
179        if (!tooltipEl) {
180            tooltipEl = document.createElement('div');
181            tooltipEl.className = 'ayudawp-tooltip';
182            tooltipEl.setAttribute('aria-hidden', 'true');
183            document.body.appendChild(tooltipEl);
184        }
185        return tooltipEl;
186    }
187
188    function showTooltip(btn) {
189        var platform = detectPlatform(btn);
190        var text     = tooltipFor(btn, platform);
191        if (!text) {
192            return;
193        }
194        var tip = getTooltip();
195        tip.textContent = text;
196        tip.classList.add('active');
197
198        var rect = btn.getBoundingClientRect();
199        var top  = rect.top + window.pageYOffset - tip.offsetHeight - 8;
200        var left = rect.left + window.pageXOffset + (rect.width / 2) - (tip.offsetWidth / 2);
201        tip.style.top  = Math.max(0, top) + 'px';
202        tip.style.left = Math.max(0, left) + 'px';
203    }
204
205    function hideTooltip() {
206        if (tooltipEl) {
207            tooltipEl.classList.remove('active');
208        }
209    }
210
211    /**
212     * Report a click to the analytics endpoint, fire and forget.
213     *
214     * sendBeacon since 2.5.0: the browser queues the request and delivers it
215     * even if the page is being replaced by the destination, which is exactly
216     * what happens when the click opens another site.
217     *
218     * @param {string}  platform Platform key.
219     * @param {Element} btn      Clicked button.
220     */
221    function trackClick(platform, btn) {
222        if (!L.trackUrl || !platform) {
223            return;
224        }
225
226        var container = btn.closest('[data-post-id]');
227        var postId    = container ? container.getAttribute('data-post-id') : '';
228
229        if (!postId) {
230            var article = document.querySelector('article[id^="post-"]');
231            if (article) {
232                postId = article.id.replace('post-', '');
233            }
234        }
235
236        var payload = JSON.stringify({
237            platform: platform.replace(/-/g, '_'),
238            post_id: parseInt(postId, 10) || 0
239        });
240
241        try {
242            if (navigator.sendBeacon) {
243                navigator.sendBeacon(L.trackUrl, new Blob([payload], { type: 'application/json' }));
244                return;
245            }
246            if (window.fetch) {
247                window.fetch(L.trackUrl, {
248                    method: 'POST',
249                    headers: { 'Content-Type': 'application/json' },
250                    body: payload,
251                    keepalive: true,
252                    credentials: 'same-origin'
253                }).catch(function () {});
254                return;
255            }
256            var xhr = new XMLHttpRequest();
257            xhr.open('POST', L.trackUrl, true);
258            xhr.setRequestHeader('Content-Type', 'application/json');
259            xhr.send(payload);
260        } catch (err) {
261            // Tracking must never break sharing.
262        }
263    }
264
265    /**
266     * Copy text to the clipboard, resolving when it is done.
267     *
268     * @param {string} text Text to copy.
269     * @return {Promise} Resolves on success.
270     */
271    function copyToClipboard(text) {
272        if (navigator.clipboard && window.isSecureContext) {
273            return navigator.clipboard.writeText(text);
274        }
275
276        return new Promise(function (resolve, reject) {
277            var area = document.createElement('textarea');
278            area.value = text;
279            area.setAttribute('readonly', 'readonly');
280            area.style.position = 'fixed';
281            area.style.left = '-999999px';
282            area.style.top = '-999999px';
283            document.body.appendChild(area);
284            area.focus();
285            area.select();
286            try {
287                if (document.execCommand('copy')) {
288                    resolve();
289                } else {
290                    reject();
291                }
292            } catch (err) {
293                reject(err);
294            } finally {
295                area.remove();
296            }
297        });
298    }
299
300    /**
301     * Transient on-screen message.
302     *
303     * role="status" so screen readers announce it: before 2.5.0 the copy
304     * confirmation was visual only.
305     *
306     * @param {string} message Text to show.
307     */
308    function showNotification(message) {
309        var existing = document.querySelector('.ayudawp-notification');
310        if (existing) {
311            existing.remove();
312        }
313
314        var note = document.createElement('div');
315        note.className = 'ayudawp-notification';
316        note.setAttribute('role', 'status');
317        note.textContent = message;
318        document.body.appendChild(note);
319
320        window.setTimeout(function () {
321            note.classList.add('is-visible');
322        }, 20);
323
324        window.setTimeout(function () {
325            note.classList.remove('is-visible');
326            window.setTimeout(function () {
327                if (note.parentNode) {
328                    note.parentNode.removeChild(note);
329                }
330            }, 400);
331        }, 2500);
332    }
333
334    /**
335     * Copy the prompt and open the platform empty.
336     *
337     * @param {string} url      Button URL carrying the prompt.
338     * @param {string} platform Platform key.
339     */
340    function handleCopyPromptClick(url, platform) {
341        var base = platformBaseUrls[platform] || url.split('?')[0];
342        var prompt = '';
343
344        try {
345            var query = url.split('?')[1] || '';
346            var params = new URLSearchParams(query);
347            prompt = params.get('prompt') || params.get('q') || '';
348        } catch (err) {
349            prompt = '';
350        }
351
352        if (!prompt) {
353            window.open(base, '_blank', 'noopener,noreferrer');
354            return;
355        }
356
357        copyToClipboard(decodeURIComponent(prompt)).then(function () {
358            showNotification(l10n('promptCopied', 'Prompt copied!'));
359            window.setTimeout(function () {
360                window.open(base, '_blank', 'noopener,noreferrer');
361            }, 500);
362        }).catch(function () {
363            window.open(base, '_blank', 'noopener,noreferrer');
364        });
365    }
366
367    /**
368     * Copy the post link (v2.5.0).
369     *
370     * @param {string} url Link to copy.
371     */
372    function handleCopyLink(url) {
373        copyToClipboard(url).then(function () {
374            showNotification(l10n('linkCopied', 'Link copied to clipboard!'));
375        }).catch(function () {
376            showNotification(l10n('copyFailed', 'Could not copy.'));
377        });
378    }
379
380    /**
381     * Hand the post to the operating system share sheet (v2.5.0).
382     *
383     * @param {Element} btn Button element.
384     * @param {string}  url Link to share.
385     */
386    function handleWebShare(btn, url) {
387        // No API, or the browser refuses the call (it is only available in a
388        // secure context, so plain http never has it): fall back to copying
389        // the link rather than doing nothing, which is what a visitor who
390        // manages to click it would otherwise get.
391        if (!navigator.share) {
392            handleCopyLink(url);
393            return;
394        }
395
396        var data = { url: url };
397        var title = btn.getAttribute('data-title');
398        if (title) {
399            data.title = title;
400        }
401
402        try {
403            var shared = navigator.share(data);
404            if (shared && shared.catch) {
405                shared.catch(function (err) {
406                    // AbortError is the visitor closing the sheet: not a failure.
407                    if (err && 'AbortError' === err.name) {
408                        return;
409                    }
410                    handleCopyLink(url);
411                });
412            }
413        } catch (err) {
414            handleCopyLink(url);
415        }
416    }
417
418    /**
419     * Expand or collapse the mobile compact block (v2.5.0).
420     *
421     * @param {Element} toggle The toggle button.
422     */
423    function handleCompactToggle(toggle) {
424        var block = toggle.closest('.ayudawp-share-buttons');
425        if (!block) {
426            return;
427        }
428
429        var expanded = block.classList.toggle('is-expanded');
430        toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
431
432        var label = toggle.querySelector('.ayudawp-button-text');
433        if (!label) {
434            return;
435        }
436        if (expanded) {
437            if (!toggle.hasAttribute('data-collapsed-label')) {
438                toggle.setAttribute('data-collapsed-label', label.textContent);
439            }
440            label.textContent = block.classList.contains('compact-single')
441                ? l10n('compactClose', 'Close')
442                : l10n('compactLess', 'Less');
443        } else {
444            label.textContent = toggle.getAttribute('data-collapsed-label') || label.textContent;
445        }
446    }
447
448    /**
449     * Single delegated click handler for every share button on the page.
450     *
451     * @param {Event} e Click event.
452     */
453    function onClick(e) {
454        var target = e.target;
455        if (!target || !target.closest) {
456            return;
457        }
458
459        var toggle = target.closest('.ayudawp-aiss-more-btn');
460        if (toggle) {
461            e.preventDefault();
462            handleCompactToggle(toggle);
463            return;
464        }
465
466        var btn = target.closest('.ayudawp-share-btn');
467        if (!btn) {
468            return;
469        }
470
471        var url = btn.getAttribute('data-url') || btn.getAttribute('href');
472        if (!url) {
473            return;
474        }
475
476        var platform = detectPlatform(btn);
477
478        e.preventDefault();
479        trackClick(platform, btn);
480
481        if (actionPlatforms.indexOf(platform) !== -1) {
482            if ('copy_link' === platform) {
483                handleCopyLink(url);
484            } else {
485                handleWebShare(btn, url);
486            }
487            return;
488        }
489
490        if (copyPromptPlatforms.indexOf(platform) !== -1) {
491            handleCopyPromptClick(url, platform);
492            return;
493        }
494
495        if (popupPlatforms.indexOf(platform) !== -1) {
496            var width  = 'reddit' === platform ? 900 : 600;
497            var height = 'reddit' === platform ? 600 : 400;
498            var left   = (window.screen.width / 2) - (width / 2);
499            var top    = (window.screen.height / 2) - (height / 2);
500            window.open(url, 'share', 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',scrollbars=yes,resizable=yes');
501            return;
502        }
503
504        window.open(url, '_blank', 'noopener,noreferrer');
505    }
506
507    /**
508     * Reveal the native share buttons where the browser supports the API.
509     *
510     * Rendered hidden by PHP so the markup is the same for every visitor (and
511     * therefore cacheable), and only shown here when navigator.share exists.
512     */
513    function revealNativeShare() {
514        if (!navigator.share) {
515            return;
516        }
517        var buttons = document.querySelectorAll('.ayudawp-aiss-needs-share-api[hidden]');
518        for (var i = 0; i < buttons.length; i++) {
519            buttons[i].removeAttribute('hidden');
520        }
521    }
522
523    function init() {
524        document.addEventListener('click', onClick, false);
525
526        // Tooltips follow both pointer and keyboard focus.
527        document.addEventListener('mouseover', function (e) {
528            var btn = e.target && e.target.closest ? e.target.closest('.ayudawp-share-btn') : null;
529            if (btn && !btn.classList.contains('ayudawp-aiss-more-btn')) {
530                showTooltip(btn);
531            }
532        }, false);
533        document.addEventListener('mouseout', function (e) {
534            if (e.target && e.target.closest && e.target.closest('.ayudawp-share-btn')) {
535                hideTooltip();
536            }
537        }, false);
538        document.addEventListener('focusin', function (e) {
539            var btn = e.target && e.target.closest ? e.target.closest('.ayudawp-share-btn') : null;
540            if (btn && !btn.classList.contains('ayudawp-aiss-more-btn')) {
541                showTooltip(btn);
542            }
543        }, false);
544        document.addEventListener('focusout', hideTooltip, false);
545        window.addEventListener('scroll', hideTooltip, { passive: true });
546
547        revealNativeShare();
548
549        // Share blocks injected later (AJAX pagination, page builders, infinite
550        // scroll) only need the native-share buttons revealing: clicks and
551        // tooltips are delegated on document and work without re-binding.
552        if (window.MutationObserver && document.body) {
553            var observer = new MutationObserver(function (mutations) {
554                for (var i = 0; i < mutations.length; i++) {
555                    for (var j = 0; j < mutations[i].addedNodes.length; j++) {
556                        var node = mutations[i].addedNodes[j];
557                        if (node.nodeType !== 1) {
558                            continue;
559                        }
560                        if (node.classList && node.classList.contains('ayudawp-share-buttons')) {
561                            revealNativeShare();
562                            return;
563                        }
564                        if (node.querySelector && node.querySelector('.ayudawp-share-buttons')) {
565                            revealNativeShare();
566                            return;
567                        }
568                    }
569                }
570            });
571            observer.observe(document.body, { childList: true, subtree: true });
572        }
573    }
574
575    if ('loading' === document.readyState) {
576        document.addEventListener('DOMContentLoaded', init);
577    } else {
578        init();
579    }
580})();
581
582/**
583 * AI Summary frontend generation button (v2.0.0)
584 *
585 * Vanilla JS — runs outside jQuery so the handler survives aggressive
586 * defer/async optimizations from caching/perf plugins (e.g. wpo-tweaks,
587 * WP Rocket, LiteSpeed). Event delegation on document so it picks up
588 * placeholders rendered by shortcodes, page builders or AJAX-loaded
589 * content. Uses fetch() with credentials so it works for logged-in and
590 * anonymous visitors alike.
591 *
592 * @since 2.0.0
593 */
594(function () {
595    'use strict';
596
597    function ayudawpAissInitSummaryButton() {
598        if (document.body && document.body.__aissSummaryBtnBound) {
599            return;
600        }
601        if (document.body) {
602            document.body.__aissSummaryBtnBound = true;
603        }
604
605        document.addEventListener('click', function (e) {
606            var btn = e.target && e.target.closest ? e.target.closest('.ayudawp-aiss-summary-generate-btn') : null;
607            if (!btn) {
608                return;
609            }
610            e.preventDefault();
611
612            var placeholder = btn.closest('.ayudawp-aiss-summary--placeholder');
613            if (!placeholder) {
614                return;
615            }
616            var postId = parseInt(placeholder.getAttribute('data-post-id'), 10);
617            if (!postId || btn.disabled) {
618                return;
619            }
620
621            var L = window.ayudawpAissL10n || {};
622            var labelEl = btn.querySelector('.ayudawp-aiss-summary-label');
623            var originalLabel = labelEl ? labelEl.textContent : '';
624
625            btn.disabled = true;
626            if (labelEl) {
627                labelEl.textContent = L.summaryGenerating || 'Generating…';
628            }
629
630            var endpoint = L.publicGenerateUrl || (window.location.origin + '/wp-json/aiss/v1/summary/generate');
631
632            fetch(endpoint, {
633                method:      'POST',
634                credentials: 'same-origin',
635                headers:     { 'Content-Type': 'application/json', 'Accept': 'application/json' },
636                body:        JSON.stringify({ post_id: postId })
637            }).then(function (response) {
638                return response.json().then(function (data) {
639                    return { ok: response.ok, data: data };
640                });
641            }).then(function (result) {
642                if (result.ok && result.data && result.data.html) {
643                    var wrapper = document.createElement('div');
644                    wrapper.innerHTML = result.data.html.trim();
645                    // The server response includes an inline <style> for the
646                    // critical CSS plus the actual <aside> summary block.
647                    // Picking firstChild would replace the placeholder with
648                    // the invisible <style>, making the box disappear. Pick
649                    // the real summary node instead (the .ayudawp-aiss-summary
650                    // that is NOT the placeholder we're replacing).
651                    var node = wrapper.querySelector('.ayudawp-aiss-summary:not(.ayudawp-aiss-summary--placeholder)');
652                    if (node && placeholder.parentNode) {
653                        placeholder.parentNode.replaceChild(node, placeholder);
654                        // A11y: open the freshly generated summary and move focus
655                        // to its toggle so screen readers announce the new content
656                        // that replaced the button the visitor just activated.
657                        var details = node.querySelector('details.ayudawp-aiss-summary-details');
658                        if (details) { details.open = true; }
659                        var toggle = node.querySelector('summary.ayudawp-aiss-summary-toggle') || node.querySelector('summary');
660                        if (toggle && typeof toggle.focus === 'function') {
661                            try { toggle.focus(); } catch (e) {}
662                        }
663                    }
664                    return;
665                }
666                var msg = (result.data && result.data.message) ? result.data.message : (L.summaryGenerateError || 'Generation failed. Try again.');
667                if (labelEl) { labelEl.textContent = msg; }
668                window.setTimeout(function () {
669                    btn.disabled = false;
670                    if (labelEl) { labelEl.textContent = originalLabel; }
671                }, 3000);
672            }).catch(function () {
673                if (labelEl) { labelEl.textContent = L.summaryGenerateError || 'Generation failed. Try again.'; }
674                window.setTimeout(function () {
675                    btn.disabled = false;
676                    if (labelEl) { labelEl.textContent = originalLabel; }
677                }, 3000);
678            });
679        }, false);
680    }
681
682    // Bind whenever the DOM is ready — works whether this script runs
683    // before or after DOMContentLoaded (covers defer/async loading).
684    if (document.readyState === 'loading') {
685        document.addEventListener('DOMContentLoaded', ayudawpAissInitSummaryButton);
686    } else {
687        ayudawpAissInitSummaryButton();
688    }
689})();

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.