1/** 2 * Clipboard handling for AI assistant share buttons. 3 * 4 * Copilot and Gemini accept no prompt in a URL, so their buttons carry the 5 * prompt in a "prompt" query parameter. Clicking one copies that prompt to the 6 * clipboard and opens a blank chat, with the parameter stripped, for the 7 * visitor to paste into. 8 * 9 * @package SocialMediaFeather 10 */ 11 12( function () { 13 'use strict'; 14 15 var SELECTOR = '.synved-social-provider-copilot, .synved-social-provider-gemini'; 16 var MESSAGE = 'Prompt copied â paste it into the chat'; 17 var DELAY = 600; 18 19 /** 20 * Copy text to the clipboard. 21 * 22 * Fire and forget, so the caller stays inside the click gesture and the new 23 * tab is not treated as an unrequested popup. 24 * 25 * @param {string} text Text to copy. 26 */ 27 function copyToClipboard( text ) { 28 if ( ! text ) { 29 return; 30 } 31 32 if ( navigator.clipboard && navigator.clipboard.writeText ) { 33 navigator.clipboard.writeText( text ); 34 return; 35 } 36 37 var field = document.createElement( 'textarea' ); 38 field.value = text; 39 field.setAttribute( 'readonly', '' ); 40 field.style.position = 'fixed'; 41 field.style.left = '-9999px'; 42 document.body.appendChild( field ); 43 field.select(); 44 45 try { 46 document.execCommand( 'copy' ); 47 } catch ( error ) { 48 } 49 50 field.parentNode.removeChild( field ); 51 } 52 53 /** 54 * Pull the prompt out of a button href. 55 * 56 * @param {string} href Button href. 57 * 58 * @return {string} The decoded prompt, or an empty string. 59 */ 60 function readPrompt( href ) { 61 var query = href.split( '?' )[1] || ''; 62 var pairs = query.split( '&' ); 63 var count = pairs.length; 64 65 for ( var i = 0; i < count; i++ ) { 66 if ( 0 === pairs[ i ].indexOf( 'prompt=' ) ) { 67 try { 68 return decodeURIComponent( pairs[ i ].slice( 7 ).replace( /\+/g, ' ' ) ); 69 } catch ( error ) { 70 return ''; 71 } 72 } 73 } 74 75 return ''; 76 } 77 78 /** 79 * Show a transient message above a button. 80 * 81 * Appended to the body so no ancestor overflow can clip it. 82 * 83 * @param {Element} button The button clicked. 84 * @param {string} message Message to show. 85 */ 86 function showNotice( button, message ) { 87 var notice = document.createElement( 'div' ); 88 notice.className = 'synved-social-notify'; 89 notice.innerText = message; 90 notice.style.cssText = 'background: rgba(0, 0, 0, .85);' + 91 'border-radius: 4px;' + 92 'color: #fff;' + 93 'font-family: "Helvetica Neue", Verdana, Helvetica, Arial, sans-serif;' + 94 'font-size: 12px;' + 95 'left: 0;' + 96 'line-height: 1.4;' + 97 'opacity: 0;' + 98 'padding: 8px 10px;' + 99 'pointer-events: none;' + 100 'position: fixed;' + 101 'top: 0;' + 102 'transition: opacity .2s ease-in;' + 103 'white-space: nowrap;' + 104 'z-index: 99999;'; 105 document.body.appendChild( notice ); 106 107 // Centre it above the button, kept inside the viewport. 108 var rect = button.getBoundingClientRect(); 109 var left = rect.left + ( rect.width - notice.offsetWidth ) / 2; 110 left = Math.min( Math.max( 8, left ), window.innerWidth - notice.offsetWidth - 8 ); 111 var top = rect.top - notice.offsetHeight - 8; 112 113 if ( 8 > top ) { 114 top = rect.bottom + 8; 115 } 116 117 notice.style.left = Math.floor( left ) + 'px'; 118 notice.style.top = Math.floor( top ) + 'px'; 119 120 setTimeout( 121 function () { 122 notice.style.opacity = '1'; 123 }, 124 10 125 ); 126 setTimeout( 127 function () { 128 notice.style.opacity = '0'; 129 setTimeout( 130 function () { 131 if ( notice.parentNode ) { 132 notice.parentNode.removeChild( notice ); 133 } 134 }, 135 300 136 ); 137 }, 138 2500 139 ); 140 } 141 142 /** 143 * Open an AI assistant in a new tab. 144 * 145 * @param {string} url Chat URL. 146 */ 147 function openTab( url ) { 148 var tab = window.open( url, '_blank' ); 149 150 if ( tab ) { 151 tab.opener = null; 152 } 153 } 154 155 document.addEventListener( 156 'click', 157 function ( event ) { 158 var button = event.target.closest ? event.target.closest( SELECTOR ) : null; 159 160 if ( ! button ) { 161 return; 162 } 163 164 var href = button.getAttribute( 'href' ) || ''; 165 166 event.preventDefault(); 167 copyToClipboard( readPrompt( href ) ); 168 showNotice( button, MESSAGE ); 169 170 // Let the notice land before the new tab takes focus. The delay
170stays 171 // well inside the user-activation window, so the tab is not blocked. 172 setTimeout( 173 function () { 174 openTab( href.split( '?' )[0] ); 175 }, 176 DELAY 177 ); 178 } 179 ); 180} )();
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.