PageSourceSearch

https://laaid.org/_static/??-eJyVzUsOwjAMRdENEaygCsQAsRZIHep8nEBst…mh8cdqMp+Uf7pWdUOH185ov9jTY89EehiF8AOLVVoI=

js laaid.org collected 2026-09-24 10:20:23 UTC 47,845 bytes, 1,577 lines download raw bytes

1/* global wpcom_reblog */
2
3var jetpackLikesWidgetBatch = [];
4var jetpackLikesMasterReady = false;
5
6// Due to performance problems on pages with a large number of widget iframes that need to be loaded,
7// we are limiting the processing at any instant to unloaded widgets that are currently in viewport,
8// plus this constant that will allow processing of widgets above and bellow the current fold.
9// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users.
10var jetpackLikesLookAhead = 2000; // pixels
11
12// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view.
13var jetpackCommentLikesLoadedWidgets = [];
14
15var jetpackLikesDocReadyPromise = new Promise( resolve => {
16	if ( document.readyState !== 'loading' ) {
17		resolve();
18	} else {
19		window.addEventListener( 'DOMContentLoaded', () => resolve() );
20	}
21} );
22
23function JetpackLikesPostMessage( message, target ) {
24	if ( typeof message === 'string' ) {
25		try {
26			message = JSON.parse( message );
27		} catch ( e ) {
28			return;
29		}
30	}
31
32	if ( target && typeof target.postMessage === 'function' ) {
33		try {
34			target.postMessage(
35				JSON.stringify( {
36					type: 'likesMessage',
37					data: message,
38				} ),
39				'*'
40			);
41		} catch ( e ) {
42			return;
43		}
44	}
45}
46
47function JetpackLikesBatchHandler() {
48	const requests = [];
49	document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => {
50		if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) {
51			return;
52		}
53
54		if ( ! jetpackIsScrolledIntoView( widget ) ) {
55			return;
56		}
57
58		jetpackLikesWidgetBatch.push( widget.id );
59
60		var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/,
61			match = regex.exec( widget.id ),
62			info;
63
64		if ( ! match || match.length !== 5 ) {
65			return;
66		}
67
68		info = {
69			blog_id: match[ 2 ],
70			width: widget.width,
71		};
72
73		if ( 'post' === match[ 1 ] ) {
74			info.post_id = match[ 3 ];
75		} else if ( 'comment' === match[ 1 ] ) {
76			info.comment_id = match[ 3 ];
77		}
78
79		info.obj_id = match[ 4 ];
80
81		requests.push( info );
82	} );
83
84	if ( requests.length > 0 ) {
85		JetpackLikesPostMessage(
86			{ event: 'initialBatch', requests: requests },
87			window.frames[ 'likes-master' ]
88		);
89	}
90}
91
92function JetpackLikesMessageListener( event ) {
93	let message = event && event.data;
94	if ( typeof message === 'string' ) {
95		try {
96			message = JSON.parse( message );
97		} catch ( err ) {
98			return;
99		}
100	}
101
102	const type = message && message.type;
103	const data = message && message.data;
104
105	if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) {
106		return;
107	}
108
109	// We only allow messages from one origin
110	const allowedOrigin = 'https://widgets.wp.com';
111	if ( allowedOrigin !== event.origin ) {
112		return;
113	}
114
115	switch ( data.event ) {
116		case 'masterReady':
117			jetpackLikesDocReadyPromise.then( () => {
118				jetpackLikesMasterReady = true;
119
120				const stylesData = {
121					event: 'injectStyles',
122				};
123				const sdTextColor = document.querySelector( '.sd-text-color' );
124				const sdLinkColor = document.querySelector( '.sd-link-color' );
125				const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {};
126				const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {};
127
128				// enable reblogs if they are enabled for the page
129				if ( document.body.classList.contains( 'jetpack-reblog-enabled' ) ) {
130					JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
131				}
132
133				stylesData.textStyles = {
134					color: sdTextColorStyles[ 'color' ],
135					fontFamily: sdTextColorStyles[ 'font-family' ],
136					fontSize: sdTextColorStyles[ 'font-size' ],
137					direction: sdTextColorStyles[ 'direction' ],
138					fontWeight: sdTextColorStyles[ 'font-weight' ],
139					fontStyle: sdTextColorStyles[ 'font-style' ],
140					textDecoration: sdTextColorStyles[ 'text-decoration' ],
141				};
142
143				stylesData.linkStyles = {
144					color: sdLinkColorStyles[ 'color' ],
145					fontFamily: sdLinkColorStyles[ 'font-family' ],
146					fontSize: sdLinkColorStyles[ 'font-size' ],
147					textDecoration: sdLinkColorStyles[ 'text-decoration' ],
148					fontWeight: sdLinkColorStyles[ 'font-weight' ],
149					fontStyle: sdLinkColorStyles[ 'font-style' ],
150				};
151
152				JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] );
153
154				JetpackLikesBatchHandler();
155			} );
156
157			break;
158
159		// We're keeping this for planned future follow ups.
160		// @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
161		case 'showLikeWidget':
162			break;
163
164		// We're keeping this for planned future follow ups.
165		// @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
166		case 'showCommentLikeWidget':
167			break;
168
169		case 'killCommentLikes':
170			// If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
171			document
172				.querySelectorAll( '.jetpack-comment-likes-widget-wrapper' )
173				.forEach( wrapper => wrapper.remove() );
174			break;
175
176		case 'clickReblogFlair':
177			if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) {
178				wpcom_reblog.toggle_reblog_box_flair( data.obj_id, data.post_id );
179			}
180			break;
181
182		case 'hideOtherGravatars': {
183			hideLikersPopover();
184			break;
185		}
186
187		case 'showOtherGravatars': {
188			const container = document.querySelector( '#likes-other-gravatars' );
189
190			if ( ! container ) {
191				break;
192			}
193
194			const list = container.querySelector( 'ul' );
195
196			container.style.display = 'none';
197			list.innerHTML = '';
198
199			container
200				.querySelectorAll( '.likes-text span' )
201				.forEach( item => ( item.textContent = data.totalLikesLabel ) );
202
203			( data.likers || [] ).forEach( async ( liker, index ) => {
204				if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
205					// We only display gravatars with http or https schema
206					return;
207				}
208
209				const element = document.createElement( 'li' );
210				list.append( element );
211
212				element.innerHTML = `
213				<a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
214					<img src="${ encodeURI( liker.avatar_URL ) }"
215						alt=""
216						style="width: 28px; height: 28px;" />
217					<span></span>
218				</a>
219				`;
220
221
222				// Add some extra attributes through native methods, to ensure strings are sanitized.
223				element.classList.add( liker.css_class );
224				element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name );
225				element.querySelector( 'span' ).innerText = liker.name;
226
227				if ( index === data.likers.length - 1 ) {
228					element.addEventListener( 'keydown', ( e ) => {
229						if ( e.key === 'Tab' && ! e.shiftKey ) {
230							e.preventDefault();
231							hideLikersPopover();
232
233							JetpackLikesPostMessage(
234								{ event: 'focusLikesCount', parent: data.parent },
235								window.frames[ 'likes-master' ]
236							);
237						}
238					} );
239				}
240			} );
241
242			const positionPopup = function() {
243				const containerStyle = getComputedStyle(container);
244				const isRtl = containerStyle.direction === 'rtl';
245
246				const el = document.querySelector( `*[name='${ data.parent }']` );
247				const rect = el.getBoundingClientRect();
248				const win = el.ownerDocument.defaultView;
249
250				const offset = {
251					top: rect.top + win.pageYOffset,
252					left: rect.left + win.pageXOffset,
253				};
254
255				// don't display yet or we get skewed window.innerWidth later
256				container.style.display = 'none';
257
258				let containerLeft = 0;
259				container.style.top = offset.top + data.position.top - 1 + 'px';
260
261				if ( isRtl ) {
262					const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
263					// 24px is the width of the avatar + 4px is the padding between avatars
264					containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
265					container.style.transform = 'translateX(-100%)';
266				} else {
267					containerLeft = offset.left + data.position.left;
268				}
269				container.style.left = containerLeft + 'px';
270
271				// Container width - padding
272				const initContainerWidth = data.width - 20;
273				const rowLength = Math.floor( initContainerWidth / 37 );
274				// # of rows + (avatar + avatar padding) + text above + container padding
275				let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22;
276				if ( height > 204 ) {
277					height = 204;
278				}
279
280				// If the popup is overflows viewport width, we should show it on the next line
281				// Push it offscreen to calculated rendered width
282				const windowWidth = win.innerWidth;
283				container.style.left = '-9999px';
284				container.style.display = 'block';
285
286				// If the popup exceeds the viewport width,
287				// flip the position of the popup.
288				const containerWidth = container.offsetWidth;
289				const containerRight = containerLeft + containerWidth;
290
291				if ( containerRight > windowWidth && ! isRtl) {
292					containerLeft = rect.left + rect.width - containerWidth;
293				} else if ( containerLeft - containerWidth < 0 && isRtl ) {
294					container.style.transform = 'none';
295					containerLeft = rect.left;
296				}
297				// Set the container left
298				container.style.left = containerLeft + 'px';
299
300				container.setAttribute( 'aria-hidden', 'false' );
301			}
302
303			positionPopup();
304			container.focus();
305
306			const debounce = function( func, wait ) {
307				var timeout;
308				return function() {
309					var context = this;
310					var args = arguments;
311					clearTimeout( timeout );
312					timeout = setTimeout( function() {
313						func.apply( context, args );
314					}, wait );
315				};
316			};
317
318			const debouncedPositionPopup = debounce( positionPopup, 100 );
319
320			// Keep a reference of this function in the element itself
321			// so that we can destroy it later
322			container.__resizeHandler = debouncedPositionPopup;
323
324			// When window is resized, resize the popup.
325			window.addEventListener( "resize", debouncedPositionPopup );
326		}
327	}
328}
329
330window.addEventListener( 'message', JetpackLikesMessageListener );
331
332function hideLikersPopover() {
333	const container = document.querySelector( '#likes-other-gravatars' );
334
335	if ( container ) {
336		container.style.display = 'none';
337		container.setAttribute( 'aria-hidden', 'true' );
338
339		// Remove the resize event listener and cleanup.
340		const resizeHandler = container.__resizeHandler;
341		if ( resizeHandler ) {
342			window.removeEventListener( "resize", resizeHandler );
343			delete container.__resizeHandler;
344		}
345	}
346}
347
348document.addEventListener( 'click', hideLikersPopover );
349
350function JetpackLikesWidgetQueueHandler() {
351	var wrapperID;
352
353	if ( ! jetpackLikesMasterReady ) {
354		setTimeout( JetpackLikesWidgetQueueHandler, 500 );
355		return;
356	}
357
358	// Restore widgets to initial unloaded state when they are scrolled out of view.
359	jetpackUnloadScrolledOutWidgets();
360
361	var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
362
363	if ( unloadedWidgetsInView.length > 0 ) {
364		// Grab any unloaded widgets for a batch request
365		JetpackLikesBatchHandler();
366	}
367
368	for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) {
369		wrapperID = unloadedWidgetsInView[ i ].id;
370
371		if ( ! wrapperID ) {
372			continue;
373		}
374
375		jetpackLoadLikeWidgetIframe( wrapperID );
376	}
377}
378
379function jetpackLoadLikeWidgetIframe( wrapperID ) {
380	if ( typeof wrapperID === 'undefined' ) {
381		return;
382	}
383
384	const wrapper = document.querySelector( '#' + wrapperID );
385	wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() );
386
387	const placeholder = wrapper.querySelector( '.likes-widget-placeholder' );
388
389	// Post like iframe
390	if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) {
391		const postLikesFrame = document.createElement( 'iframe' );
392
393		postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' );
394		postLikesFrame.name = wrapper.dataset.name;
395		postLikesFrame.src = wrapper.dataset.src;
396		postLikesFrame.height = '55px';
397		postLikesFrame.width = '100%';
398		postLikesFrame.frameBorder = '0';
399		postLikesFrame.scrolling = 'no';
400		postLikesFrame.title = wrapper.dataset.title;
401
402		placeholder.after( postLikesFrame );
403	}
404
405	// Comment like iframe
406	if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) {
407		const commentLikesFrame = document.createElement( 'iframe' );
408
409		commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame';
410		commentLikesFrame.name = wrapper.dataset.name;
411		commentLikesFrame.src = wrapper.dataset.src;
412		commentLikesFrame.height = '18px';
413		commentLikesFrame.width = '100%';
414		commentLikesFrame.frameBorder = '0';
415		commentLikesFrame.scrolling = 'no';
416
417		wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame );
418
419		jetpackCommentLikesLoadedWidgets.push( commentLikesFrame );
420	}
421
422	wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
423	wrapper.classList.add( 'jetpack-likes-widget-loading' );
424
425	wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => {
426		JetpackLikesPostMessage(
427			{ event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
428			window.frames[ 'likes-master' ]
429		);
430
431		wrapper.classList.remove( 'jetpack-likes-widget-loading' );
432		wrapper.classList.add( 'jetpack-likes-widget-loaded' );
433	} );
434}
435
436function jetpackGetUnloadedWidgetsInView() {
437	const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' );
438
439	return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) );
440}
441
442function jetpackIsScrolledIntoView( element ) {
443	const top = element.getBoundingClientRect().top;
444	const bottom = element.getBoundingClientRect().bottom;
445
446	// Allow some slack above and bellow the fold with jetpackLikesLookAhead,
447	// with the aim of hiding the transition from unloaded to loaded widget from users.
448	return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead;
449}
450
451function jetpackUnloadScrolledOutWidgets() {
452	for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) {
453		const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ];
454
455		if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) {
456			const widgetWrapper =
457				currentWidgetIframe &&
458				currentWidgetIframe.parentElement &&
459				currentWidgetIframe.parentElement.parentElement;
460
461			// Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed.
462			widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
463			widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
464			widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
465
466			// Bring back the loading placeholder into view.
467			widgetWrapper
468				.querySelectorAll( '.comment-likes-widget-placeholder' )
469				.forEach( item => ( item.style.display = 'block' ) );
470
471			// Remove it from the list of loaded widgets.
472			jetpackCommentLikesLoadedWidgets.splice( i, 1 );
473
474			// Remove comment like widget iFrame.
475			currentWidgetIframe.remove();
476		}
477	}
478}
479
480var jetpackWidgetsDelayedExec = function ( after, fn ) {
481	var timer;
482	return function () {
483		clearTimeout( timer );
484		timer = setTimeout( fn, after );
485	};
486};
487
488var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler );
489
490// Load initial batch of widgets, prior to any scrolling events.
491JetpackLikesWidgetQueueHandler();
492
493// Add event listener to execute queue handler after scroll.
494window.addEventListener( 'scroll', jetpackOnScrollStopped, true );
495;
496/**
497 * Comment Likes - JavaScript
498 *
499 * This handles liking and unliking comments, as well as viewing who has
500 * liked a particular comment.
501 *
502 * @dependency  Swipe (dynamically loaded when needed)
503 *
504 * @package     Comment_Likes
505 * @subpackage  JavaScript
506 */
507(function () {
508	function init() {
509		let extWin;
510		let extWinCheck;
511		let commentLikeEvent;
512
513		// Only run once.
514		if (window.comment_likes_loaded) {
515			return;
516		}
517		window.comment_likes_loaded = true;
518
519		// Client-side cache of who liked a particular comment to avoid
520		// having to hit the server multiple times for the same data.
521		const commentLikeCache = {};
522
523		let swipeLibPromise;
524
525		// Load the Swipe library, if it's not already loaded.
526		function swipeLibLoader() {
527			if (!swipeLibPromise) {
528				swipeLibPromise = new Promise((resolve, reject) => {
529					if (window.Swipe) {
530						resolve(window.Swipe);
531					} else {
532						const swipeScript = document.createElement('script');
533						swipeScript.src = comment_like_text.swipeUrl;
534						swipeScript.async = true;
535						document.body.appendChild(swipeScript);
536						swipeScript.addEventListener('load', () => resolve(window.Swipe));
537						swipeScript.addEventListener('error', error => reject(error));
538					}
539				});
540			}
541			return swipeLibPromise;
542		}
543
544		/**
545		 * Parse the comment ID from a comment like link.
546		 */
547		function getCommentId(link) {
548			const commentId =
549				link && link.getAttribute('href') && link.getAttribute('href').split('like_comment=');
550			return commentId[1].split('&_wpnonce=')[0];
551		}
552
553		/**
554		 * Handle an ajax action on the comment like link.
555		 */
556		function handleLinkAction(link, action, commentId, callback) {
557			const nonce =
558				link && link.getAttribute('href') && link.getAttribute('href').split('_wpnonce=')[1];
559
560			fetch('/wp-admin/admin-ajax.php', {
561				method: 'POST',
562				body: new URLSearchParams({
563					action: action,
564					_wpnonce: nonce,
565					like_comment: commentId,
566					blog_id: Number(link.dataset.blog),
567				}),
568				headers: {
569					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
570					'X-Requested-With': 'XMLHttpRequest',
571					Accept: 'application/json',
572					'cache-control': 'no-cache',
573					pragma: 'no-cache',
574				},
575			})
576				.then(response => response.json())
577				.then(callback);
578		}
579
580		function startPolling() {
581			// Append cookie polling login iframe to this window to wait for user to finish logging in (or cancel)
582			const loginIframe = document.createElement('iframe');
583			loginIframe.id = 'wp-login-polling-iframe';
584			loginIframe.src = 'https://wordpress.com/public.api/connect/?iframe=true';
585			document.body.appendChild(loginIframe);
586			loginIframe.style.display = 'none';
587		}
588
589		function stopPolling() {
590			const iframe = document.querySelector('#wp-login-polling-iframe');
591			if (iframe) {
592				iframe.remove();
593			}
594		}
595
596		function hide(el) {
597			if (el && el.style) {
598				el.style.display = 'none';
599			}
600		}
601
602		function show(el) {
603			if (el && el.style) {
604				el.style.removeProperty('display');
605			}
606		}
607
608		// Overlay used for displaying comment like info.
609		class Overlay {
610			constructor() {
611				// Overlay element.
612				this.el = document.createElement('div');
613				this.el.classList.add('comment-likes-overlay');
614				document.body.appendChild(this.el);
615				hide(this.el);
616
617				this.el.addEventListener('mouseenter', () => {
618					// Don't hide the overlay if the user is mousing over it.
619					overlay.cancelHide();
620				});
621
622				this.el.addEventListener('mouseleave', () => overlay.requestHide());
623
624				// Inner contents of overlay.
625				this.innerEl = null;
626
627				// Instance of the Swipe library.
628				this.swipe = null;
629
630				// Timeout used for hiding the overlay.
631				this.hideTimeout = null;
632			}
633
634			// Initialise the overlay for use, removing any old content.
635			clear() {
636				// Unload any previous instance of Swipe (to avoid leaking a global
637				// event handler). This is done before clearing the contents of the
638				// overlay because Swipe expects the slides to still be present.
639				if (this.swipe) {
640					this.swipe.kill();
641					this.swipe = null;
642				}
643				this.el.innerHTML = '';
644				this.innerEl = document.createElement('div');
645				this.innerEl.classList.add('inner');
646				this.el.appendChild(this.innerEl);
647			}
648
649			/**
650			 * Construct a list (<ul>) of user (gravatar, name) details.
651			 *
652			 * @param  data     liker data returned from the server
653			 * @param  klass    CSS class to apply to the <ul> element
654			 * @param  start    index of user to start at
655			 * @param  length   number of users to include in the list
656			 *
657			 * @return          A container element with the list
658			 */
659			getUserBits(data, klass, start, length) {
660				start = start || 0;
661				let last = start + (length || data.length);
662				last = last > data.length ? data.length : last;
663				const container = document.createElement('div');
664				container.classList.add('liker-list');
665				let html = `<ul class="${klass || ''}">`;
666				for (let i = start; i < last; ++i) {
667					const user = data[i];
668					html += `
669						<li>
670							<a rel="nofollow" title="${user.display_name_esc}" href="${user.profile_url_esc}">
671								<img src="${user.avatar_url_esc}" alt="${user.display_name_esc}" />
672								<span class="user-name">${user.display_name_esc}</span>
673							</a>
674						</li>
675					`;
676				}
677				html += '</ul>';
678				container.innerHTML = html;
679				return container;
680			}
681
682			/**
683			 * Render the display of who has liked this comment. The type of
684			 * display depends on how many people have liked the comment.
685			 * If more than 10 people have liked the comment, this function
686			 * renders navigation controls and sets up the Swipe library for
687			 * changing between pages.
688			 *
689			 * @param link  the element over which the user is hovering
690			 * @param data  the results retrieved from the server
691			 */
692			showLikes(link, data) {
693				this.clear();
694
695				link.dataset.likeCount = data.length;
696				if (data.length === 0) {
697					// No likers after all.
698					hide(this.el);
699					return;
700				}
701
702				this.innerEl.style.padding = '12px';
703
704				if (data.length < 6) {
705					// Only one column needed.
706					this.innerEl.style.maxWidth = '200px';
707					this.innerEl.innerHTML = '';
708					this.innerEl.appendChild(this.getUserBits(data, 'single'));
709					this.setPosition(link);
710				} else if (data.length < 11) {
711					// Two columns, but only one page.
712					this.innerEl.innerHTML = '';
713					this.innerEl.appendChild(this.getUserBits(data, 'double'));
714					this.setPosition(link);
715				} else {
716					// Multiple pages.
717					this.renderLikesWithPagination(data, link);
718				}
719			}
720
721			/**
722			 * Render multiple pages of likes with pagination controls.
723			 * This function is intended to be called by `showLikes` above.
724			 *
725			 * @param data  the results retrieved from the server
726			 */
727			renderLikesWithPagination(data, link) {
728				swipeLibLoader().then(() => {
729					const page_count = Math.ceil(data.length / 10);
730					// Swipe requires two nested containers.
731					const swipe = document.createElement('div');
732					swipe.classList.add('swipe');
733					this.innerEl.appendChild(swipe);
734
735					const wrap = document.createElement('div');
736					wrap.classList.add('swipe-wrap');
737					swipe.appendChild(wrap);
738
739					for (let i = 0; i < page_count; ++i) {
740						wrap.appendChild(this.getUserBits(data, 'double', i * 10, 10));
741					}
742
743					/**
744					 * Navigation controls.
745					 * This is based on the Newdash controls found in
746					 *    reader/recommendations-templates.php
747					 */
748					const nav = document.createElement('nav');
749					nav.classList.add('slider-nav');
750
751					let navContents = `
752						<a href="#" class="prev">
753							<span class="noticon noticon-previous" title="Previous" alt="<"></span>
754						</a>
755						<span class="position">
756					`;
757					for (let i = 0; i < page_count; ++i) {
758						navContents += `<em data-page="${i}" class="${i === 0 ? 'on' : ''}">&bull;</em>`;
759					}
760					navContents += `
761						</span>
762						<a href="#" class="next">
763							<span class="noticon noticon-next" title="Next" alt=">"></span>
764						</a>
765					`;
766					this.innerEl.appendChild(nav);
767					nav.innerHTML = navContents;
768
769					/** Set up Swipe. **/
770					// Swipe cannot be set up successfully unless its container
771					// is visible, so we show it now.
772					show(this.el);
773					this.setPosition(link);
774
775					this.swipe = new Swipe(swipe, {
776						callback: function (pos) {
777							// Update the pagination indicators.
778							//
779							// If there are exactly two pages, Swipe has a weird
780							// special case where it duplicates both pages and
781							// can return index 2 and 3 even though those aren't
782							// real pages (see swipe.js, line 47). To deal with
783							// this, we use the expression `pos % page_count`.
784							pos = pos % page_count;
785							nav.querySelectorAll('em').forEach(em => {
786								const page = Number(em.dataset.page);
787								em.setAttribute('class', pos === page ? 'on' : '');
788							});
789						},
790					});
791
792					nav.querySelectorAll('em').forEach(em => {
793						em.addEventListener('click', e => {
794							// Go to the page corresponding to the indicator clicked.
795							this.swipe.slide(Number(em.dataset.page));
796							e.preventDefault();
797						});
798					});
799					// Previous and next buttons.
800					nav.querySelector('.prev').addEventListener('click', e => {
801						this.swipe.prev();
802						e.preventDefault();
803					});
804					nav.querySelector('.next').addEventListener('click', e => {
805						this.swipe.next();
806						e.preventDefault();
807					});
808				});
809			}
810
811			/**
812			 * Open the overlay and show a loading message.
813			 */
814			showLoadingMessage(link) {
815				this.clear();
816				this.innerEl.textContent = comment_like_text.loading;
817				this.setPosition(link);
818			}
819
820			/**
821			 * Position the overlay near the current comment.
822			 *
823			 * @param link  element near which to position the overlay
824			 */
825			setPosition(link) {
826				// Prepare a down arrow icon for the bottom of the overlay.
827				const icon = document.createElement('span');
828				this.el.appendChild(icon);
829				icon.classList.add('icon', 'noticon', 'noticon-downarrow');
830				icon.style.textShadow = '0px 1px 1px rgb(223, 223, 223)';
831
832				const rect = link.getBoundingClientRect();
833				const win = document.defaultView;
834				const offset = {
835					top: rect.top + win.scrollY,
836					left: rect.left + win.scrollX,
837				};
838
839				// Take measurements with the element fully visible.
840				show(this.el);
841				let left = offset.left - (this.el.offsetWidth - link.offsetWidth) / 2;
842				left = left < 5 ? 5 : left;
843				let top = offset.top - this.el.offsetHeight + 5;
844				hide(this.el);
845
846				const adminBar = document.querySelector('#wpadminbar');
847
848				// Check if the overlay would appear off the screen.
849				if (top < win.scrollY + ((adminBar && adminBar.offsetHeight) || 0)) {
850					// We'll display the overlay beneath the link instead.
851					top = offset.top + link.offsetHeight;
852					// Instead of using the down arrow icon, use an up arrow.
853					icon.remove();
854					this.el.prepend(icon);
855					icon.classList.remove('noticon-downarrow');
856					icon.classList.add('noticon-uparrow');
857					icon.style.textShadow = '0px -1px 1px rgb(223, 223, 223)';
858					icon.style.verticalAlign = 'bottom';
859				}
860
861				this.el.style.left = `${left}px`;
862				this.el.style.top = `${top}px`;
863				show(this.el);
864
865				// The height of the arrow icon differs slightly between browsers,
866				// so we compute the margin here to make sure it isn't disjointed
867				// from the overlay.
868				icon.style.marginTop = `${icon.scrollHeight - 26}px`;
869				icon.style.marginBottom = `${20 - icon.scrollHeight}px`;
870
871				// Position the arrow to be horizontally centred on the link.
872				icon.style.paddingLeft = `${
873					offset.left - left + (link.offsetWidth - icon.scrollWidth) / 2
874				}px`;
875			}
876
877			/**
878			 * Return whether the overlay is visible.
879			 */
880			isVisible() {
881				return this.el.style.getPropertyValue('display') !== 'none';
882			}
883
884			/**
885			 * Request that the overlay be hidden after a short delay.
886			 */
887			requestHide() {
888				if (this.hideTimeout !== null) {
889					return;
890				}
891				this.hideTimeout = setTimeout(() => {
892					hide(this.el);
893					this.clear();
894				}, 300);
895			}
896
897			/**
898			 * Cancel a request to hide the overlay.
899			 */
900			cancelHide() {
901				if (this.hideTimeout !== null) {
902					clearTimeout(this.hideTimeout);
903					this.hideTimeout = null;
904				}
905			}
906		}
907
908		// Overlay used for displaying comment like info.
909		const overlay = new Overlay();
910
911		// The most recent comment for which the user has requested to see
912		// who liked it.
913		var relevantComment;
914
915		// Precache after this timeout.
916		var precacheTimeout = null;
917
918		/**
919		 * Fetch the like data for a particular comment.
920		 */
921		function fetchLikeData(link, commentId) {
922			commentLikeCache[commentId] = null;
923
924			const container = link && link.parentElement && link.parentElement.parentElement;
925			const star = container.querySelector('a.comment-like-link');
926			star &&
927				handleLinkAction(star, 'view_comment_likes', commentId, data => {
928					// Populate the cache.
929					commentLikeCache[commentId] = data;
930
931					// Only show the overlay if the user is interested.
932					if (overlay.isVisible() && relevantComment === commentId) {
933						overlay.showLikes(link, data);
934					}
935				});
936		}
937
938		function readCookie(c) {
939			const nameEQ = c + '=';
940			const cookieStrings = document.cookie.split(';');
941
942			for (let i = 0; i < cookieStrings.length; i++) {
943				let cookieString = cookieStrings[i];
944				while (cookieString.charAt(0) === ' ') {
945					cookieString = cookieString.substring(1, cookieString.length);
946				}
947				if (cookieString.indexOf(nameEQ) === 0) {
948					const chunk = cookieString.substring(nameEQ.length, cookieString.length);
949					const pairs = chunk.split('&');
950					const cookieData = {};
951					for (let num = pairs.length - 1; num >= 0; num--) {
952						const pair = pairs[num].split('=');
953						cookieData[pair[0]] = decodeURIComponent(pair[1]);
954					}
955					return cookieData;
956				}
957			}
958			return null;
959		}
960
961		function getServiceData() {
962			const data = readCookie('wpc_wpc');
963			if (data === null || typeof data.access_token === 'undefined' || !data.access_token) {
964				return false;
965			}
966			return data;
967		}
968
969		function readMessage(msg) {
970			const event = msg.data;
971
972			if (typeof event.event === 'undefined') {
973				return;
974			}
975
976			if (event.event === 'login' && event.success) {
977				extWinCheck = setInterval(function () {
978					if (!extWin || extWin.closed) {
979						clearInterval(extWinCheck);
980						if (getServiceData()) {
981							// Load page in an iframe to get the current comment nonce
982							const nonceIframe = document.createElement('iframe');
983							nonceIframe.id = 'wp-login-comment-nonce-iframe';
984							nonceIframe.style.display = 'none';
985							nonceIframe.src = commentLikeEvent + '';
986							document.body.appendChild(nonceIframe);
987
988							const commentLikeId = (commentLikeEvent + '')
989								.split('like_comment=')[1]
990								.split('&_wpnonce=')[0];
991							let c;
992
993							// Set a 5 second timeout to redirect to the comment page without doing the Like as a fallback
994							const commentLikeTimeout = setTimeout(() => {
995								window.location = commentLikeEvent;
996							}, 5000);
997
998							// Check for a new nonced redirect and use that if available before timing out
999							const commentLikeCheck = setInterval(() => {
1000								const iframe = document.querySelector('#wp-login-comment-nonce-iframe');
1001								if (iframe) {
1002									c = iframe.querySelector(`#comment-like-${commentLikeId} .comment-like-link`);
1003								}
1004								if (c && typeof c.href !== 'undefined') {
1005									clearTimeout(commentLikeTimeout);
1006									clearInterval(commentLikeCheck);
1007									window.location = c.href;
1008								}
1009							}, 100);
1010						}
1011					}
1012				}, 100);
1013
1014				if (extWin) {
1015					if (!extWin.closed) {
1016						extWin.close();
1017					}
1018					extWin = false;
1019				}
1020
1021				stopPolling();
1022			}
1023		}
1024
1025		if (typeof window.postMessage !== 'undefined') {
1026			window.addEventListener('message', e => {
1027				let message = e && e.data;
1028				if (typeof message === 'string') {
1029					try {
1030						message = JSON.parse(message);
1031					} catch (err) {
1032						return;
1033					}
1034				}
1035
1036				const type = message && message.type;
1037				if (type === 'loginMessage') {
1038					readMessage(message);
1039				}
1040			});
1041		}
1042
1043		document.body.addEventListener('click', e => {
1044			let target = e.target;
1045
1046			// Don't do anything when clicking on the "X people" link.
1047			if (target.matches('p.comment-likes a.view-likers')) {
1048				e.preventDefault();
1049				return;
1050			}
1051
1052			// Retrieve the surrounding paragraph to the star, if it hasn't been liked.
1053			const notLikedPar = target.closest('p.comment-not-liked');
1054
1055			// Return if not clicking on star or surrounding paragraph.
1056			if (!target.matches('a.comment-like-link') && !notLikedPar) {
1057				return;
1058			}
1059
1060			// When a comment hasn't been liked, make the text clickable, too.
1061			if (notLikedPar) {
1062				target = notLikedPar.querySelector('a.comment-like-link');
1063				if (!target) {
1064					return;
1065				}
1066			}
1067
1068			if (target.classList.contains('needs-login')) {
1069				e.preventDefault();
1070				commentLikeEvent = target;
1071				if (extWin) {
1072					if (!extWin.closed) {
1073						extWin.close();
1074					}
1075					extWin = false;
1076				}
1077
1078				stopPolling();
1079
1080				const url = 'https://wordpress.com/public.api/connect/?action=request&service=wordpress';
1081				extWin = window.open(
1082					url,
1083					'likeconn',
1084					'status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=560,width=500'
1085				);
1086
1087				startPolling();
1088
1089				return false;
1090			}
1091
1092			// Record that the user likes or does not like this comment.
1093			const commentId = getCommentId(target);
1094			target.classList.add('loading');
1095
1096			let commentEl = document.querySelector(`p#comment-like-${commentId}`);
1097			// Determine whether to like or unlike based on whether the comment is
1098			// currently liked.
1099			const action =
1100				commentEl && commentEl.dataset.liked === 'comment-liked'
1101					? 'unlike_comment'
1102					: 'like_comment';
1103			handleLinkAction(target, action, commentId, data => {
1104				// Invalidate the like cache for this comment.
1105				delete commentLikeCache[commentId];
1106
1107				const countEl = document.querySelector(`#comment-like-count-${data.context}`);
1108				if (countEl) {
1109					countEl.innerHTML = data.display;
1110				}
1111
1112				commentEl = document.querySelector(`p#comment-like-${data.context}`);
1113				if (action === 'like_comment') {
1114					commentEl.classList.remove('comment-not-liked');
1115					commentEl.classList.add('comment-liked');
1116					commentEl.dataset.liked = 'comment-liked';
1117				} else {
1118					commentEl.classList.remove('comment-liked');
1119					commentEl.classList.add('comment-not-liked');
1120					commentEl.dataset.liked = 'comment-not-liked';
1121				}
1122
1123				// Prefetch new data for this comment (if there are likers left).
1124				const parent = target.closest('.comment-likes');
1125				const link = parent && parent.querySelector('a.view-likers');
1126				if (link) {
1127					fetchLikeData(link, commentId);
1128				}
1129
1130				target.classList.remove('loading');
1131			});
1132			e.preventDefault();
1133			e.stopPropagation();
1134		});
1135
1136		document.body.addEventListener(
1137			'mouseenter',
1138			function (e) {
1139				if (!e.target.matches('p.comment-likes a.view-likers')) {
1140					return;
1141				}
1142				// Show the user a list of who has liked this comment.
1143
1144				const link = e.target;
1145				if (Number(link.dataset.likeCount || 0) === 0) {
1146					// No one has liked this comment.
1147					return;
1148				}
1149
1150				// Don't hide the overlay.
1151				overlay.cancelHide();
1152
1153				// Get the comment ID.
1154				const container = link.parentElement && link.parentElement.parentElement;
1155				const star = container && container.querySelector('a.comment-like-link');
1156				const commentId = star && getCommentId(star);
1157				relevantComment = commentId;
1158
1159				// Check if the list of likes for this comment is already in
1160				// the cache.
1161				if (commentId in commentLikeCache) {
1162					const entry = commentLikeCache[commentId];
1163					// Only display the likes if the ajax request is
1164					// actually done.
1165					if (entry !== null) {
1166						overlay.showLikes(link, entry);
1167					} else {
1168						// Make sure the overlay is visible (in case
1169						// the user moved the mouse away while loading
1170						// but then came back before it finished
1171						// loading).
1172						overlay.showLoadingMessage(link);
1173					}
1174					return;
1175				}
1176
1177				// Position the "Loading..." overlay.
1178				overlay.showLoadingMessage(link);
1179
1180				// Fetch the data.
1181				fetchLikeData(link, commentId);
1182			},
1183			true
1184		);
1185
1186		document.body.addEventListener(
1187			'mouseleave',
1188			e => {
1189				if (!e.target.matches('p.comment-likes a.view-likers')) {
1190					return;
1191				}
1192				// User has moved cursor away - hide the overlay.
1193				overlay.requestHide();
1194			},
1195			true
1196		);
1197
1198		document.body.addEventListener(
1199			'mouseenter',
1200			e => {
1201				if (!e.target.matches('.comment') || !e.target.querySelector('a.comment-like-link')) {
1202					return;
1203				}
1204				// User is moving over a comment - precache the comment like data.
1205				if (precacheTimeout !== null) {
1206					clearTimeout(precacheTimeout);
1207					precacheTimeout = null;
1208				}
1209
1210				const star = e.target.querySelector('a.comment-like-link');
1211				const parent = star.closest('.comment-likes');
1212				const link = parent && parent.querySelector('a.view-likers');
1213				if (!link || Number(link.dataset.likeCount || 0) === 0) {
1214					// No likes.
1215					return;
1216				}
1217				const commentId = getCommentId(star);
1218				if (commentId in commentLikeCache) {
1219					// Already in cache.
1220					return;
1221				}
1222
1223				precacheTimeout = setTimeout(() => {
1224					precacheTimeout = null;
1225					if (commentId in commentLikeCache) {
1226						// Was cached in the interim.
1227						return;
1228					}
1229					fetchLikeData(link, commentId);
1230				}, 1000);
1231			},
1232			true
1233		);
1234	}
1235
1236	if (document.readyState !== 'loading') {
1237		init();
1238	} else {
1239		document.addEventListener('DOMContentLoaded', init);
1240	}
1241})();
1242;
1243/**
1244 * Makes "skip to content" link work correctly in IE9, Chrome, and Opera
1245 * for better accessibility.
1246 *
1247 * @link http://www.nczonline.net/blog/2013/01/15/fixing-skip-to-content-links/
1248 */
1249
1250 ( function() {
1251	var isWebkit = navigator.userAgent.toLowerCase().indexOf( 'webkit' ) > -1,
1252		isOpera  = navigator.userAgent.toLowerCase().indexOf( 'opera' )  > -1,
1253		isIE     = navigator.userAgent.toLowerCase().indexOf( 'msie' )   > -1;
1254
1255	if ( ( isWebkit || isOpera || isIE ) && document.getElementById && window.addEventListener ) {
1256		window.addEventListener( 'hashchange', function() {
1257			var id = location.hash.substring( 1 ),
1258				element;
1259
1260			if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) {
1261				return;
1262			}
1263
1264			element = document.getElementById( id );
1265
1266			if ( element ) {
1267				if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) {
1268					element.tabIndex = -1;
1269				}
1270
1271				element.focus();
1272
1273				// Repositions the window on jump-to-anchor to account for admin bar and border height.
1274				window.scrollBy( 0, -53 );
1275			}
1276		}, false );
1277	}
1278} )();
1279;
1280/* global screenReaderText */
1281/**
1282 * Theme functions file.
1283 *
1284 * Contains handlers for navigation and widget area.
1285 */
1286( function () {
1287	'use strict';
1288
1289	var mainNav, masthead, menuToggle, siteNavigation, socialNavigation, siteHeaderMenu, resizeTimer;
1290
1291	function matches( el, sel ) {
1292		if ( Element.prototype.matches ) {
1293			return el.matches( sel );
1294		}
1295
1296		if ( Element.prototype.msMatchesSelector ) {
1297			return el.msMatchesSelector( sel );
1298		}
1299	}
1300
1301	// Based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
1302	function closest( el, sel ) {
1303		if ( el.closest ) {
1304			return el.closest( sel );
1305		}
1306
1307		var current = el;
1308
1309		do {
1310			if ( matches( el, sel ) ) {
1311				return current;
1312			}
1313			current = current.parentElement || current.parentNode;
1314		} while ( current !== null && current.nodeType === 1 );
1315
1316		return null;
1317	}
1318
1319	function forEachNode( parent, selector, fn ) {
1320		var nodes = parent.querySelectorAll( selector );
1321		for ( var i = 0; i < nodes.length; i++ ) {
1322			fn( nodes[ i ] );
1323		}
1324	}
1325
1326	function initMainNavigation( container ) {
1327		forEachNode( container, '.menu-item-has-children > a', function ( el ) {
1328			// Add dropdown toggle that displays child menu items.
1329			var dropdownToggle = document.createElement( 'button' );
1330			dropdownToggle.className = 'dropdown-toggle';
1331			dropdownToggle.setAttribute( 'aria-expanded', 'false' );
1332
1333			var span = document.createElement( 'span' );
1334			span.className = 'screen-reader-text';
1335			span.textContent = screenReaderText.expand;
1336
1337			dropdownToggle.appendChild( span );
1338
1339			el.parentNode.insertBefore( dropdownToggle, el.nextSibling );
1340		} );
1341
1342		// Toggle buttons and submenu items with active children menu items.
1343		forEachNode( container, '.current-menu-ancestor > button', function ( el ) {
1344			el.classList.add( 'toggled-on' );
1345		} );
1346		forEachNode( container, '.current-menu-ancestor > .sub-menu', function ( el ) {
1347			el.classList.add( 'toggled-on' );
1348		} );
1349
1350		// Add menu items with submenus to aria-haspopup="true".
1351		forEachNode( container, '.menu-item-has-children', function ( el ) {
1352			el.setAttribute( 'aria-haspopup', 'true' );
1353		} );
1354
1355		forEachNode( container, '.dropdown-toggle', function ( el ) {
1356			el.addEventListener( 'click', function ( e ) {
1357				var screenReaderSpan = el.querySelector( '.screen-reader-text' );
1358
1359				e.preventDefault();
1360				el.classList.toggle( 'toggled-on' );
1361				var next = el.nextElementSibling;
1362				if ( next && matches( next, '.children, .sub-menu' ) ) {
1363					next.classList.toggle( 'toggled-on' );
1364				}
1365
1366				el.setAttribute(
1367					'aria-expanded',
1368					el.getAttribute( 'aria-expanded' ) === 'false' ? 'true' : 'false'
1369				);
1370				screenReaderSpan.textContent =
1371					screenReaderSpan.textContent === screenReaderText.expand ? screenReaderText.collapse: screenReaderText.expand;
1372			} );
1373		} );
1374	}
1375
1376	mainNav = document.querySelector( '.main-navigation' );
1377	if ( mainNav ) {
1378		initMainNavigation( mainNav );
1379	}
1380
1381	masthead = document.querySelector( '#masthead' );
1382	menuToggle = masthead && masthead.querySelector( '#menu-toggle' );
1383	siteHeaderMenu = masthead && masthead.querySelector( '#site-header-menu' );
1384	siteNavigation = masthead && masthead.querySelector( '#site-navigation' );
1385	socialNavigation = masthead && masthead.querySelector( '#social-navigation' );
1386
1387	// Enable menuToggle.
1388	( function () {
1389		// Return early if menuToggle is missing.
1390		if ( ! menuToggle ) {
1391			return;
1392		}
1393
1394		var controls = [ menuToggle, siteNavigation, socialNavigation ].filter( Boolean );
1395
1396		// Add an initial values for the attribute.
1397		controls.forEach( function ( el ) {
1398			el.setAttribute( 'aria-expanded', 'false' );
1399		} );
1400
1401		menuToggle.addEventListener( 'click', function () {
1402			menuToggle.classList.toggle( 'toggled-on' );
1403			siteHeaderMenu.classList.toggle( 'toggled-on' );
1404
1405			var newVal = menuToggle.getAttribute( 'aria-expanded' ) === 'false' ? 'true' : 'false';
1406			controls.forEach( function ( el ) {
1407				el.setAttribute( 'aria-expanded', newVal );
1408			} );
1409		} );
1410	} )();
1411
1412	// Fix sub-menus for touch devices and better focus for hidden submenu items for accessibility.
1413	( function () {
1414		if ( ! siteNavigation || ! siteNavigation.children.length ) {
1415			return;
1416		}
1417
1418		// Init `focus` class to allow submenu access on tablets.
1419		function initFocusClassTouchScreen() {
1420			document.body.addEventListener( 'touchstart', function ( e ) {
1421				if ( ! closest( e.target, '.main-navigation li' ) ) {
1422					forEachNode( document, '.main-navigation li', function ( el ) {
1423						el.classList.remove( 'focus' );
1424					} );
1425				}
1426			} );
1427			forEachNode( siteNavigation, '.menu-item-has-children > a', function ( link ) {
1428				link.addEventListener( 'touchstart', function ( e ) {
1429					if ( window.innerWidth >= 910 ) {
1430						var el = link.parentElement;
1431
1432						if ( matches( el, 'li' ) && ! el.classList.contains( 'focus' ) ) {
1433							e.preventDefault();
1434							el.classList.toggle( 'focus' );
1435							var children = el.parentElement.children;
1436							for ( var i = 0; i < children.length; i++ ) {
1437								if ( children[ i ] !== el ) {
1438									children[ i ].classList.remove( 'focus' );
1439								}
1440							}
1441						}
1442					}
1443				} );
1444			} );
1445		}
1446
1447		function toggleFocus( el ) {
1448			var item = closest( el, '.menu-item' );
1449			if ( item ) {
1450				item.classList.toggle( 'focus' );
1451			}
1452		}
1453
1454		if ( 'ontouchstart' in window ) {
1455			initFocusClassTouchScreen();
1456		}
1457
1458		forEachNode( siteNavigation, 'a', function ( link ) {
1459			link.addEventListener( 'focus', toggleFocus( link ) );
1460			link.addEventListener( 'blur', toggleFocus( link ) );
1461		} );
1462	} )();
1463
1464	// Add the default ARIA attributes for the menu toggle and the navigations.
1465	function onResizeARIA() {
1466		if ( window.innerWidth < 910 ) {
1467			if ( menuToggle && menuToggle.classList.contains( 'toggled-on' ) ) {
1468				menuToggle.setAttribute( 'aria-expanded', 'true' );
1469			} else {
1470				menuToggle.setAttribute( 'aria-expanded', 'false' );
1471			}
1472
1473			if ( siteHeaderMenu && siteHeaderMenu.classList.contains( 'toggled-on' ) ) {
1474				siteNavigation && siteNavigation.setAttribute( 'aria-expanded', 'true' );
1475				socialNavigation && socialNavigation.setAttribute( 'aria-expanded', 'true' );
1476			} else {
1477				siteNavigation && siteNavigation.setAttribute( 'aria-expanded', 'false' );
1478				socialNavigation && socialNavigation.setAttribute( 'aria-expanded', 'false' );
1479			}
1480
1481			menuToggle && menuToggle.setAttribute( 'aria-controls', 'site-navigation social-navigation' );
1482		} else {
1483			menuToggle && menuToggle.removeAttribute( 'aria-expanded' );
1484			siteNavigation && siteNavigation.removeAttribute( 'aria-expanded' );
1485			socialNavigation && socialNavigation.removeAttribute( 'aria-expanded' );
1486			menuToggle && menuToggle.removeAttribute( 'aria-controls' );
1487		}
1488	}
1489
1490	// Add 'below-entry-meta' class to elements.
1491	function belowEntryMetaClass( selector ) {
1492		if (
1493			document.body.classList.contains( 'page' ) ||
1494			document.body.classList.contains( 'search' ) ||
1495			document.body.classList.contains( 'single-attachment' ) ||
1496			document.body.classList.contains( 'error404' )
1497		) {
1498			return;
1499		}
1500
1501		forEachNode( document, '.entry-content', function ( content ) {
1502			forEachNode( content, selector, function ( element ) {
1503				var elementPosTop = element.getBoundingClientRect().top;
1504				var article = closest( element, 'article' );
1505				var entryFooter = article && article.querySelector( '.entry-footer' );
1506				var entryFooterPosBottom =
1507					entryFooter ? entryFooter.getBoundingClientRect().top + entryFooter.offsetHeight + 28	: null;
1508				var caption = closest( element, 'figure' );
1509				var next = element.nextSibling;
1510				var figcaption = next && matches( next, 'figcaption' ) ? next : null;
1511				var newImg;
1512
1513				// Add 'below-entry-meta' to elements below the entry meta.
1514				if ( entryFooterPosBottom !== null && elementPosTop > entryFooterPosBottom ) {
1515					// Check if full-size images and captions are larger than or equal to 840px.
1516					if ( selector === 'img.size-full' || selector === '.wp-block-image img' ) {
1517						// Create an image to find native image width of resized images (i.e. max-width: 100%).
1518						newImg = new Image();
1519						newImg.src = element.getAttribute( 'src' );
1520						newImg.onload = function () {
1521							if ( newImg.width >= 840 ) {
1522								// Check if an image in an image block has a width attribute; if its value is less than 840, return.
1523								if (
1524									selector === '.wp-block-image img' &&
1525									element.hasAttribute( 'width' ) &&
1526									element.getAttribute( 'width' ) < 840
1527								) {
1528									return;
1529								}
1530
1531								element.classList.add( 'below-entry-meta' );
1532
1533								if ( caption && caption.classList.contains( 'wp-caption' ) ) {
1534									caption.classList.add( 'below-entry-meta' );
1535									caption.removeAttribute( 'style' );
1536								}
1537
1538								if ( figcaption ) {
1539									figcaption.classList.add( 'below-entry-meta' );
1540								}
1541							}
1542						};
1543					} else {
1544						element.classList.add( 'below-entry-meta' );
1545					}
1546				} else {
1547					element.classList.remove( 'below-entry-meta' );
1548					caption && caption.classList.remove( 'below-entry-meta' );
1549				}
1550			} );
1551		} );
1552	}
1553
1554	function init() {
1555		window.addEventListener( 'load', onResizeARIA );
1556		window.addEventListener( 'resize', function () {
1557			clearTimeout( resizeTimer );
1558			resizeTimer = setTimeout( function () {
1559				belowEntryMetaClass( 'img.size-full' );
1560				belowEntryMetaClass( 'blockquote.alignleft, blockquote.alignright' );
1561				belowEntryMetaClass( '.wp-block-image img' );
1562			}, 300 );
1563			onResizeARIA();
1564		} );
1565
1566		belowEntryMetaClass( 'img.size-full' );
1567		belowEntryMetaClass( 'blockquote.alignleft, blockquote.alignright' );
1568		belowEntryMetaClass( '.wp-block-image img' );
1569	}
1570
1571	if ( document.readyState === 'loading' ) {
1572		document.addEventListener( 'DOMContentLoaded', init );
1573	} else {
1574		init();
1575	}
1576} )();
1577;

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.