PageSourceSearch

https://www.grapplearts.com/wp-content/plugins/social-media-feathe…/script/ai-clipboard.js?ver=1789438440

js grapplearts.com collected 2026-09-24 17:20:51 UTC 4,327 bytes, 180 lines download raw bytes

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.