PageSourceSearch

https://www.anthemawards.com/wp-content/mu-plugins/rm-theme-base/a…/rm-theme-behaviour.js?ver=2026.09.13.04

js anthemawards.com collected 2026-09-24 17:14:04 UTC 5,893 bytes, 191 lines download raw bytes

1/*
2 * Delegated replacements for inline on* attributes.
3 *
4 * Two behaviours, both of which existed as inline handlers repeated across the themes:
5 *
6 *   [data-rm-copy]     click copies that value to the clipboard.
7 *   [data-rm-fallback] an image that fails to load swaps to that URL, once.
8 *
9 * copyToClipboard() was defined ten times across six themes — byte-identical every time, an
10 * IE-era implementation using window.clipboardData with a document.execCommand fallback — and
11 * called from eight inline onclick attributes. One copy lives here now, and the call sites are
12 * data attributes, which is what guardrail 11 asks for: nothing executable in markup, so a
13 * Content-Security-Policy without 'unsafe-inline' becomes possible.
14 *
15 * The visible behaviour is deliberately unchanged: the same #url-copied element is faded in and
16 * out, through jQuery when it is present so the animation matches what the themes did, and with a
17 * plain show/hide when it is not. This file has no jQuery dependency of its own.
18 *
19 * Listeners are delegated from the document, so markup injected later — winner cards, search
20 * results, anything built in the browser — is covered without re-binding.
21 *
22 * Build: 2026.08.28.03
23 */
24( function ( document, window ) {
25	'use strict';
26
27	var FEEDBACK_ID = 'url-copied';
28	var FEEDBACK_MS = 2500;
29
30	/**
31	 * Show the "copied" confirmation, matching what the inline handlers did.
32	 *
33	 * @return {void}
34	 */
35	function confirmCopy( selector ) {
36		var node = selector
37			? document.querySelector( selector )
38			: document.getElementById( FEEDBACK_ID );
39
40		if ( ! node ) {
41			return;
42		}
43
44		if ( window.jQuery ) {
45			window.jQuery( node ).stop( true, true ).fadeIn( 'slow' ).delay( FEEDBACK_MS ).fadeOut( 'slow' );
46
47			return;
48		}
49
50		node.style.display = 'block';
51		window.setTimeout( function () {
52			node.style.display = 'none';
53		}, FEEDBACK_MS );
54	}
55
56	/**
57	 * Copy text, preferring the asynchronous clipboard API.
58	 *
59	 * execCommand('copy') is deprecated but still the only thing that works without a secure
60	 * context or permission, so it stays as the fallback rather than being dropped.
61	 *
62	 * @param {string} text
63	 * @return {void}
64	 */
65	function copy( text, feedback ) {
66		if ( navigator.clipboard && navigator.clipboard.writeText ) {
67			navigator.clipboard.writeText( text ).then(
68				function () {
69					confirmCopy( feedback );
70				},
71				function () {
72					legacyCopy( text, feedback );
73				}
74			);
75
76			return;
77		}
78
79		legacyCopy( text, feedback );
80	}
81
82	/**
83	 * @param {string} text
84	 * @return {void}
85	 */
86	function legacyCopy( text, feedback ) {
87		var field = document.createElement( 'textarea' );
88
89		field.value = text;
90		// Fixed positioning keeps the page from scrolling to the element before it is removed.
91		field.style.position = 'fixed';
92		field.style.top = '0';
93		field.style.left = '-9999px';
94		field.setAttribute( 'readonly', 'readonly' );
95		field.setAttribute( 'aria-hidden', 'true' );
96
97		document.body.appendChild( field );
98		field.select();
99
100		try {
101			document.execCommand( 'copy' );
102			confirmCopy( feedback );
103		} catch ( e ) {
104			// Nothing useful to do: the browser refused, and there is no other path. The
105			// confirmation is deliberately not shown, so the visitor is not told it worked.
106		}
107
108		document.body.removeChild( field );
109	}
110
111	document.addEventListener( 'click', function ( event ) {
112		var trigger = event.target.closest ? event.target.closest( '[data-rm-copy]' ) : null;
113
114		if ( ! trigger ) {
115			return;
116		}
117
118		event.preventDefault();
119		copy( trigger.getAttribute( 'data-rm-copy' ), trigger.getAttribute( 'data-rm-copy-feedback' ) );
120	} );
121
122	/*
123	 * Links that must not navigate.
124	 *
125	 * These were href="#" with onclick="return false;" — accordion openers and dropdown triggers
126	 * that need to be focusable and styled as links but have nothing to go to. Telly already
127	 * carried a js-telly-noop class doing this for its category openers; this generalises it so
128	 * every theme uses one handler.
129	 *
130	 * A real <button> would be better markup, but swapping the element changes what the themes'
131	 * stylesheets match, which is a visual change this pass is not making.
132	 */
133	document.addEventListener( 'click', function ( event ) {
134		var link = event.target.closest ? event.target.closest( '[data-rm-noop]' ) : null;
135
136		if ( link ) {
137			event.preventDefault();
138		}
139	} );
140
141	/*
142	 * Share popups. The URL is opened in a sized window, which is what the inline handlers did;
143	 * a browser that blocks the popup falls back to opening normally rather than doing nothing.
144	 */
145	document.addEventListener( 'click', function ( event ) {
146		var trigger = event.target.closest ? event.target.closest( '[data-rm-share]' ) : null;
147
148		if ( ! trigger ) {
149			return;
150		}
151
152		event.preventDefault();
153
154		var url = trigger.getAttribute( 'data-rm-share' );
155		var name = trigger.getAttribute( 'data-rm-share-name' ) || 'share';
156		var features = trigger.getAttribute( 'data-rm-share-features' ) ||
157			'height=320,width=640,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,directories=no,status=no';
158
159		var opened = window.open( url, name, features );
160
161		if ( ! opened ) {
162			window.open( url, '_blank', 'noopener' );
163		}
164	} );
165
166	/*
167	 * Image fallbacks. The error event does not bubble, so this listens in the capture phase —
168	 * the same reason the inline onerror attributes existed. The attribute is removed after the
169	 * swap so a fallback that is itself missing cannot loop.
170	 */
171	document.addEventListener(
172		'error',
173		function ( event ) {
174			var image = event.target;
175
176			if ( ! image || image.tagName !== 'IMG' ) {
177				return;
178			}
179
180			var fallback = image.getAttribute( 'data-rm-fallback' );
181
182			if ( ! fallback || image.getAttribute( 'src' ) === fallback ) {
183				return;
184			}
185
186			image.removeAttribute( 'data-rm-fallback' );
187			image.setAttribute( 'src', fallback );
188		},
189		true
190	);
191}( document, window ) );

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.