PageSourceSearch

https://biodiversitygenomics.org/_static/??-eJyFjsEKwjAQRH/IuBSF6E…/bXv5I5MCtH35x+gF6/5MtiTtcP5eLDLHRXPWtQ=

js biodiversitygenomics.org collected 2026-10-02 01:06:51 UTC 24,105 bytes, 748 lines download raw bytes

1(()=>{"use strict";var t={51951(){},95209(t,e,n){n(51951);function s(t,e){const n=new XMLHttpRequest;n.onreadystatechange=()=>{if(4===n.readyState){if(n.status>=200&&n.status<300){const e=JSON.parse(n.responseText);return t.onSuccess(e)}return e?s(t,e-1):t.onError()}},n.open("GET",t.url),n.send()}function r(t,e){return Object.prototype.hasOwnProperty.call(t,e)}Array.prototype.forEach.call(document.querySelectorAll(".wp-block-newspack-blocks-homepage-articles.has-more-button"),function(t){const e=t.querySelector("[data-next]");if(!e)return;const n=t.querySelector("[data-posts]"),o=e.getAttribute("data-infinite-scroll");window.newspackBlocksIsFetching=window.newspackBlocksIsFetching||!1,window.newspackBlocksFetchQueue=window.newspackBlocksFetchQueue||[];let c=!1,i=!1;const a=()=>{i||(i=!0,u())},u=()=>{if(c)return!1;t.classList.remove("is-error"),t.classList.add("is-loading");const n=e.getAttribute("data-next")+"&exclude_ids="+function(){const t=document.querySelectorAll("[class^='wp-block-newspack-blocks'] [data-post-id]"),e=Array.from(t).map(t=>t.getAttribute("data-post-id"));return e.push(document.querySelector("div[data-current-post-id]").getAttribute("data-current-post-id")),[...new Set(e)]}().join(",");if(window.newspackBlocksIsFetching)return window.newspackBlocksFetchQueue.push(u),!1;window.newspackBlocksIsFetching=!0,s({url:n,onSuccess:l,onError:d},3)};function l(s){if(!function(t){let e=!1;t&&r(t,"items")&&Array.isArray(t.items)&&r(t,"next")&&"string"==typeof t.next&&(e=!0,!t.items.length||r(t.items[0],"html")&&"string"==typeof t.items[0].html||(e=!1));return e}(s))return d();if(s.items.length){const t=s.items.map(t=>t.html).join("");n.insertAdjacentHTML("beforeend",t)}s.next&&e.setAttribute("data-next",s.next),s.items.length&&s.next||(c=!0,t.classList.remove("has-more-button")),p()}function d(){t.classList.add("is-error"),p()}function p(){window.newspackBlocksIsFetching=!1,t.classList.remove("is-loading"),window.newspackBlocksFetchQueue.length&&window.newspackBlocksFetchQueue.shift()(),i=!1}if(e.addEventListener("click",a),o){new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&a()})},{root:null,rootMargin:"0px",threshold:1}).observe(e)}})}},e={};(function n(s){var r=e[s];if(void 0!==r)return r.exports;var o=e[s]={exports:{}};return t[s](o,o.exports,n),o.exports})(95209)})();;
2/**
3 * Comment Likes - JavaScript
4 *
5 * This handles liking and unliking comments, as well as viewing who has
6 * liked a particular comment.
7 *
8 * @dependency  Swipe (dynamically loaded when needed)
9 *
10 * @package     Comment_Likes
11 * @subpackage  JavaScript
12 */
13(function () {
14	function init() {
15		let extWin;
16		let extWinCheck;
17		let commentLikeEvent;
18
19		// Only run once.
20		if (window.comment_likes_loaded) {
21			return;
22		}
23		window.comment_likes_loaded = true;
24
25		// Client-side cache of who liked a particular comment to avoid
26		// having to hit the server multiple times for the same data.
27		const commentLikeCache = {};
28
29		let swipeLibPromise;
30
31		// Load the Swipe library, if it's not already loaded.
32		function swipeLibLoader() {
33			if (!swipeLibPromise) {
34				swipeLibPromise = new Promise((resolve, reject) => {
35					if (window.Swipe) {
36						resolve(window.Swipe);
37					} else {
38						const swipeScript = document.createElement('script');
39						swipeScript.src = comment_like_text.swipeUrl;
40						swipeScript.async = true;
41						document.body.appendChild(swipeScript);
42						swipeScript.addEventListener('load', () => resolve(window.Swipe));
43						swipeScript.addEventListener('error', error => reject(error));
44					}
45				});
46			}
47			return swipeLibPromise;
48		}
49
50		/**
51		 * Parse the comment ID from a comment like link.
52		 */
53		function getCommentId(link) {
54			const commentId =
55				link && link.getAttribute('href') && link.getAttribute('href').split('like_comment=');
56			return commentId[1].split('&_wpnonce=')[0];
57		}
58
59		/**
60		 * Handle an ajax action on the comment like link.
61		 */
62		function handleLinkAction(link, action, commentId, callback) {
63			const nonce =
64				link && link.getAttribute('href') && link.getAttribute('href').split('_wpnonce=')[1];
65
66			fetch('/wp-admin/admin-ajax.php', {
67				method: 'POST',
68				body: new URLSearchParams({
69					action: action,
70					_wpnonce: nonce,
71					like_comment: commentId,
72					blog_id: Number(link.dataset.blog),
73				}),
74				headers: {
75					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
76					'X-Requested-With': 'XMLHttpRequest',
77					Accept: 'application/json',
78					'cache-control': 'no-cache',
79					pragma: 'no-cache',
80				},
81			})
82				.then(response => response.json())
83				.then(callback);
84		}
85
86		function startPolling() {
87			// Append cookie polling login iframe to this window to wait for user to finish logging in (or cancel)
88			const loginIframe = document.createElement('iframe');
89			loginIframe.id = 'wp-login-polling-iframe';
90			loginIframe.src = 'https://wordpress.com/public.api/connect/?iframe=true';
91			document.body.appendChild(loginIframe);
92			loginIframe.style.display = 'none';
93		}
94
95		function stopPolling() {
96			const iframe = document.querySelector('#wp-login-polling-iframe');
97			if (iframe) {
98				iframe.remove();
99			}
100		}
101
102		function hide(el) {
103			if (el && el.style) {
104				el.style.display = 'none';
105			}
106		}
107
108		function show(el) {
109			if (el && el.style) {
110				el.style.removeProperty('display');
111			}
112		}
113
114		// Overlay used for displaying comment like info.
115		class Overlay {
116			constructor() {
117				// Overlay element.
118				this.el = document.createElement('div');
119				this.el.classList.add('comment-likes-overlay');
120				document.body.appendChild(this.el);
121				hide(this.el);
122
123				this.el.addEventListener('mouseenter', () => {
124					// Don't hide the overlay if the user is mousing over it.
125					overlay.cancelHide();
126				});
127
128				this.el.addEventListener('mouseleave', () => overlay.requestHide());
129
130				// Inner contents of overlay.
131				this.innerEl = null;
132
133				// Instance of the Swipe library.
134				this.swipe = null;
135
136				// Timeout used for hiding the overlay.
137				this.hideTimeout = null;
138			}
139
140			// Initialise the overlay for use, removing any old content.
141			clear() {
142				// Unload any previous instance of Swipe (to avoid leaking a global
143				// event handler). This is done before clearing the contents of the
144				// overlay because Swipe expects the slides to still be present.
145				if (this.swipe) {
146					this.swipe.kill();
147					this.swipe = null;
148				}
149				this.el.innerHTML = '';
150				this.innerEl = document.createElement('div');
151				this.innerEl.classList.add('inner');
152				this.el.appendChild(this.innerEl);
153			}
154
155			/**
156			 * Construct a list (<ul>) of user (gravatar, name) details.
157			 *
158			 * @param  data     liker data returned from the server
159			 * @param  klass    CSS class to apply to the <ul> element
160			 * @param  start    index of user to start at
161			 * @param  length   number of users to include in the list
162			 *
163			 * @return          A container element with the list
164			 */
165			getUserBits(data, klass, start, length) {
166				start = start || 0;
167				let last = start + (length || data.length);
168				last = last > data.length ? data.length : last;
169				const container = document.createElement('div');
170				container.classList.add('liker-list');
171				let html = `<ul class="${klass || ''}">`;
172				for (let i = start; i < last; ++i) {
173					const user = data[i];
174					html += `
175						<li>
176							<a rel="nofollow" title="${user.display_name_esc}" href="${user.profile_url_esc}">
177								<img src="${user.avatar_url_esc}" alt="${user.display_name_esc}" />
178								<span class="user-name">${user.display_name_esc}</span>
179							</a>
180						</li>
181					`;
182				}
183				html += '</ul>';
184				container.innerHTML = html;
185				return container;
186			}
187
188			/**
189			 * Render the display of who has liked this comment. The type of
190			 * display depends on how many people have liked the comment.
191			 * If more than 10 people have liked the comment, this function
192			 * renders navigation controls and sets up the Swipe library for
193			 * changing between pages.
194			 *
195			 * @param link  the element over which the user is hovering
196			 * @param data  the results retrieved from the server
197			 */
198			showLikes(link, data) {
199				this.clear();
200
201				link.dataset.likeCount = data.length;
202				if (data.length === 0) {
203					// No likers after all.
204					hide(this.el);
205					return;
206				}
207
208				this.innerEl.style.padding = '12px';
209
210				if (data.length < 6) {
211					// Only one column needed.
212					this.innerEl.style.maxWidth = '200px';
213					this.innerEl.innerHTML = '';
214					this.innerEl.appendChild(this.getUserBits(data, 'single'));
215					this.setPosition(link);
216				} else if (data.length < 11) {
217					// Two columns, but only one page.
218					this.innerEl.innerHTML = '';
219					this.innerEl.appendChild(this.getUserBits(data, 'double'));
220					this.setPosition(link);
221				} else {
222					// Multiple pages.
223					this.renderLikesWithPagination(data, link);
224				}
225			}
226
227			/**
228			 * Render multiple pages of likes with pagination controls.
229			 * This function is intended to be called by `showLikes` above.
230			 *
231			 * @param data  the results retrieved from the server
232			 */
233			renderLikesWithPagination(data, link) {
234				swipeLibLoader().then(() => {
235					const page_count = Math.ceil(data.length / 10);
236					// Swipe requires two nested containers.
237					const swipe = document.createElement('div');
238					swipe.classList.add('swipe');
239					this.innerEl.appendChild(swipe);
240
241					const wrap = document.createElement('div');
242					wrap.classList.add('swipe-wrap');
243					swipe.appendChild(wrap);
244
245					for (let i = 0; i < page_count; ++i) {
246						wrap.appendChild(this.getUserBits(data, 'double', i * 10, 10));
247					}
248
249					/**
250					 * Navigation controls.
251					 * This is based on the Newdash controls found in
252					 *    reader/recommendations-templates.php
253					 */
254					const nav = document.createElement('nav');
255					nav.classList.add('slider-nav');
256
257					let navContents = `
258						<a href="#" class="prev">
259							<span class="noticon noticon-previous" title="Previous" alt="<"></span>
260						</a>
261						<span class="position">
262					`;
263					for (let i = 0; i < page_count; ++i) {
264						navContents += `<em data-page="${i}" class="${i === 0 ? 'on' : ''}">&bull;</em>`;
265					}
266					navContents += `
267						</span>
268						<a href="#" class="next">
269							<span class="noticon noticon-next" title="Next" alt=">"></span>
270						</a>
271					`;
272					this.innerEl.appendChild(nav);
273					nav.innerHTML = navContents;
274
275					/** Set up Swipe. **/
276					// Swipe cannot be set up successfully unless its container
277					// is visible, so we show it now.
278					show(this.el);
279					this.setPosition(link);
280
281					this.swipe = new Swipe(swipe, {
282						callback: function (pos) {
283							// Update the pagination indicators.
284							//
285							// If there are exactly two pages, Swipe has a weird
286							// special case where it duplicates both pages and
287							// can return index 2 and 3 even though those aren't
288							// real pages (see swipe.js, line 47). To deal with
289							// this, we use the expression `pos % page_count`.
290							pos = pos % page_count;
291							nav.querySelectorAll('em').forEach(em => {
292								const page = Number(em.dataset.page);
293								em.setAttribute('class', pos === page ? 'on' : '');
294							});
295						},
296					});
297
298					nav.querySelectorAll('em').forEach(em => {
299						em.addEventListener('click', e => {
300							// Go to the page corresponding to the indicator clicked.
301							this.swipe.slide(Number(em.dataset.page));
302							e.preventDefault();
303						});
304					});
305					// Previous and next buttons.
306					nav.querySelector('.prev').addEventListener('click', e => {
307						this.swipe.prev();
308						e.preventDefault();
309					});
310					nav.querySelector('.next').addEventListener('click', e => {
311						this.swipe.next();
312						e.preventDefault();
313					});
314				});
315			}
316
317			/**
318			 * Open the overlay and show a loading message.
319			 */
320			showLoadingMessage(link) {
321				this.clear();
322				this.innerEl.textContent = comment_like_text.loading;
323				this.setPosition(link);
324			}
325
326			/**
327			 * Position the overlay near the current comment.
328			 *
329			 * @param link  element near which to position the overlay
330			 */
331			setPosition(link) {
332				// Prepare a down arrow icon for the bottom of the overlay.
333				const icon = document.createElement('span');
334				this.el.appendChild(icon);
335				icon.classList.add('icon', 'noticon', 'noticon-downarrow');
336				icon.style.textShadow = '0px 1px 1px rgb(223, 223, 223)';
337
338				const rect = link.getBoundingClientRect();
339				const win = document.defaultView;
340				const offset = {
341					top: rect.top + win.scrollY,
342					left: rect.left + win.scrollX,
343				};
344
345				// Take measurements with the element fully visible.
346				show(this.el);
347				let left = offset.left - (this.el.offsetWidth - link.offsetWidth) / 2;
348				left = left < 5 ? 5 : left;
349				let top = offset.top - this.el.offsetHeight + 5;
350				hide(this.el);
351
352				const adminBar = document.querySelector('#wpadminbar');
353
354				// Check if the overlay would appear off the screen.
355				if (top < win.scrollY + ((adminBar && adminBar.offsetHeight) || 0)) {
356					// We'll display the overlay beneath the link instead.
357					top = offset.top + link.offsetHeight;
358					// Instead of using the down arrow icon, use an up arrow.
359					icon.remove();
360					this.el.prepend(icon);
361					icon.classList.remove('noticon-downarrow');
362					icon.classList.add('noticon-uparrow');
363					icon.style.textShadow = '0px -1px 1px rgb(223, 223, 223)';
364					icon.style.verticalAlign = 'bottom';
365				}
366
367				this.el.style.left = `${left}px`;
368				this.el.style.top = `${top}px`;
369				show(this.el);
370
371				// The height of the arrow icon differs slightly between browsers,
372				// so we compute the margin here to make sure it isn't disjointed
373				// from the overlay.
374				icon.style.marginTop = `${icon.scrollHeight - 26}px`;
375				icon.style.marginBottom = `${20 - icon.scrollHeight}px`;
376
377				// Position the arrow to be horizontally centred on the link.
378				icon.style.paddingLeft = `${
379					offset.left - left + (link.offsetWidth - icon.scrollWidth) / 2
380				}px`;
381			}
382
383			/**
384			 * Return whether the overlay is visible.
385			 */
386			isVisible() {
387				return this.el.style.getPropertyValue('display') !== 'none';
388			}
389
390			/**
391			 * Request that the overlay be hidden after a short delay.
392			 */
393			requestHide() {
394				if (this.hideTimeout !== null) {
395					return;
396				}
397				this.hideTimeout = setTimeout(() => {
398					hide(this.el);
399					this.clear();
400				}, 300);
401			}
402
403			/**
404			 * Cancel a request to hide the overlay.
405			 */
406			cancelHide() {
407				if (this.hideTimeout !== null) {
408					clearTimeout(this.hideTimeout);
409					this.hideTimeout = null;
410				}
411			}
412		}
413
414		// Overlay used for displaying comment like info.
415		const overlay = new Overlay();
416
417		// The most recent comment for which the user has requested to see
418		// who liked it.
419		var relevantComment;
420
421		// Precache after this timeout.
422		var precacheTimeout = null;
423
424		/**
425		 * Fetch the like data for a particular comment.
426		 */
427		function fetchLikeData(link, commentId) {
428			commentLikeCache[commentId] = null;
429
430			const container = link && link.parentElement && link.parentElement.parentElement;
431			const star = container.querySelector('a.comment-like-link');
432			star &&
433				handleLinkAction(star, 'view_comment_likes', commentId, data => {
434					// Populate the cache.
435					commentLikeCache[commentId] = data;
436
437					// Only show the overlay if the user is interested.
438					if (overlay.isVisible() && relevantComment === commentId) {
439						overlay.showLikes(link, data);
440					}
441				});
442		}
443
444		function readCookie(c) {
445			const nameEQ = c + '=';
446			const cookieStrings = document.cookie.split(';');
447
448			for (let i = 0; i < cookieStrings.length; i++) {
449				let cookieString = cookieStrings[i];
450				while (cookieString.charAt(0) === ' ') {
451					cookieString = cookieString.substring(1, cookieString.length);
452				}
453				if (cookieString.indexOf(nameEQ) === 0) {
454					const chunk = cookieString.substring(nameEQ.length, cookieString.length);
455					const pairs = chunk.split('&');
456					const cookieData = {};
457					for (let num = pairs.length - 1; num >= 0; num--) {
458						const pair = pairs[num].split('=');
459						cookieData[pair[0]] = decodeURIComponent(pair[1]);
460					}
461					return cookieData;
462				}
463			}
464			return null;
465		}
466
467		function getServiceData() {
468			const data = readCookie('wpc_wpc');
469			if (data === null || typeof data.access_token === 'undefined' || !data.access_token) {
470				return false;
471			}
472			return data;
473		}
474
475		function readMessage(msg) {
476			const event = msg.data;
477
478			if (typeof event.event === 'undefined') {
479				return;
480			}
481
482			if (event.event === 'login' && event.success) {
483				extWinCheck = setInterval(function () {
484					if (!extWin || extWin.closed) {
485						clearInterval(extWinCheck);
486						if (getServiceData()) {
487							// Load page in an iframe to get the current comment nonce
488							const nonceIframe = document.createElement('iframe');
489							nonceIframe.id = 'wp-login-comment-nonce-iframe';
490							nonceIframe.style.display = 'none';
491							nonceIframe.src = commentLikeEvent + '';
492							document.body.appendChild(nonceIframe);
493
494							const commentLikeId = (commentLikeEvent + '')
495								.split('like_comment=')[1]
496								.split('&_wpnonce=')[0];
497							let c;
498
499							// Set a 5 second timeout to redirect to the comment page without doing the Like as a fallback
500							const commentLikeTimeout = setTimeout(() => {
501								window.location = commentLikeEvent;
502							}, 5000);
503
504							// Check for a new nonced redirect and use that if available before timing out
505							const commentLikeCheck = setInterval(() => {
506								const iframe = document.querySelector('#wp-login-comment-nonce-iframe');
507								if (iframe) {
508									c = iframe.querySelector(`#comment-like-${commentLikeId} .comment-like-link`);
509								}
510								if (c && typeof c.href !== 'undefined') {
511									clearTimeout(commentLikeTimeout);
512									clearInterval(commentLikeCheck);
513									window.location = c.href;
514								}
515							}, 100);
516						}
517					}
518				}, 100);
519
520				if (extWin) {
521					if (!extWin.closed) {
522						extWin.close();
523					}
524					extWin = false;
525				}
526
527				stopPolling();
528			}
529		}
530
531		if (typeof window.postMessage !== 'undefined') {
532			window.addEventListener('message', e => {
533				let message = e && e.data;
534				if (typeof message === 'string') {
535					try {
536						message = JSON.parse(message);
537					} catch (err) {
538						return;
539					}
540				}
541
542				const type = message && message.type;
543				if (type === 'loginMessage') {
544					readMessage(message);
545				}
546			});
547		}
548
549		document.body.addEventListener('click', e => {
550			let target = e.target;
551
552			// Don't do anything when clicking on the "X people" link.
553			if (target.matches('p.comment-likes a.view-likers')) {
554				e.preventDefault();
555				return;
556			}
557
558			// Retrieve the surrounding paragraph to the star, if it hasn't been liked.
559			const notLikedPar = target.closest('p.comment-not-liked');
560
561			// Return if not clicking on star or surrounding paragraph.
562			if (!target.matches('a.comment-like-link') && !notLikedPar) {
563				return;
564			}
565
566			// When a comment hasn't been liked, make the text clickable, too.
567			if (notLikedPar) {
568				target = notLikedPar.querySelector('a.comment-like-link');
569				if (!target) {
570					return;
571				}
572			}
573
574			if (target.classList.contains('needs-login')) {
575				e.preventDefault();
576				commentLikeEvent = target;
577				if (extWin) {
578					if (!extWin.closed) {
579						extWin.close();
580					}
581					extWin = false;
582				}
583
584				stopPolling();
585
586				const url = 'https://wordpress.com/public.api/connect/?action=request&service=wordpress';
587				extWin = window.open(
588					url,
589					'likeconn',
590					'status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=560,width=500'
591				);
592
593				startPolling();
594
595				return false;
596			}
597
598			// Record that the user likes or does not like this comment.
599			const commentId = getCommentId(target);
600			target.classList.add('loading');
601
602			let commentEl = document.querySelector(`p#comment-like-${commentId}`);
603			// Determine whether to like or unlike based on whether the comment is
604			// currently liked.
605			const action =
606				commentEl && commentEl.dataset.liked === 'comment-liked'
607					? 'unlike_comment'
608					: 'like_comment';
609			handleLinkAction(target, action, commentId, data => {
610				// Invalidate the like cache for this comment.
611				delete commentLikeCache[commentId];
612
613				const countEl = document.querySelector(`#comment-like-count-${data.context}`);
614				if (countEl) {
615					countEl.innerHTML = data.display;
616				}
617
618				commentEl = document.querySelector(`p#comment-like-${data.context}`);
619				if (action === 'like_comment') {
620					commentEl.classList.remove('comment-not-liked');
621					commentEl.classList.add('comment-liked');
622					commentEl.dataset.liked = 'comment-liked';
623				} else {
624					commentEl.classList.remove('comment-liked');
625					commentEl.classList.add('comment-not-liked');
626					commentEl.dataset.liked = 'comment-not-liked';
627				}
628
629				// Prefetch new data for this comment (if there are likers left).
630				const parent = target.closest('.comment-likes');
631				const link = parent && parent.querySelector('a.view-likers');
632				if (link) {
633					fetchLikeData(link, commentId);
634				}
635
636				target.classList.remove('loading');
637			});
638			e.preventDefault();
639			e.stopPropagation();
640		});
641
642		document.body.addEventListener(
643			'mouseenter',
644			function (e) {
645				if (!e.target.matches('p.comment-likes a.view-likers')) {
646					return;
647				}
648				// Show the user a list of who has liked this comment.
649
650				const link = e.target;
651				if (Number(link.dataset.likeCount || 0) === 0) {
652					// No one has liked this comment.
653					return;
654				}
655
656				// Don't hide the overlay.
657				overlay.cancelHide();
658
659				// Get the comment ID.
660				const container = link.parentElement && link.parentElement.parentElement;
661				const star = container && container.querySelector('a.comment-like-link');
662				const commentId = star && getCommentId(star);
663				relevantComment = commentId;
664
665				// Check if the list of likes for this comment is already in
666				// the cache.
667				if (commentId in commentLikeCache) {
668					const entry = commentLikeCache[commentId];
669					// Only display the likes if the ajax request is
670					// actually done.
671					if (entry !== null) {
672						overlay.showLikes(link, entry);
673					} else {
674						// Make sure the overlay is visible (in case
675						// the user moved the mouse away while loading
676						// but then came back before it finished
677						// loading).
678						overlay.showLoadingMessage(link);
679					}
680					return;
681				}
682
683				// Position the "Loading..." overlay.
684				overlay.showLoadingMessage(link);
685
686				// Fetch the data.
687				fetchLikeData(link, commentId);
688			},
689			true
690		);
691
692		document.body.addEventListener(
693			'mouseleave',
694			e => {
695				if (!e.target.matches('p.comment-likes a.view-likers')) {
696					return;
697				}
698				// User has moved cursor away - hide the overlay.
699				overlay.requestHide();
700			},
701			true
702		);
703
704		document.body.addEventListener(
705			'mouseenter',
706			e => {
707				if (!e.target.matches('.comment') || !e.target.querySelector('a.comment-like-link')) {
708					return;
709				}
710				// User is moving over a comment - precache the comment like data.
711				if (precacheTimeout !== null) {
712					clearTimeout(precacheTimeout);
713					precacheTimeout = null;
714				}
715
716				const star = e.target.querySelector('a.comment-like-link');
717				const parent = star.closest('.comment-likes');
718				const link = parent && parent.querySelector('a.view-likers');
719				if (!link || Number(link.dataset.likeCount || 0) === 0) {
720					// No likes.
721					return;
722				}
723				const commentId = getCommentId(star);
724				if (commentId in commentLikeCache) {
725					// Already in cache.
726					return;
727				}
728
729				precacheTimeout = setTimeout(() => {
730					precacheTimeout = null;
731					if (commentId in commentLikeCache) {
732						// Was cached in the interim.
733						return;
734					}
735					fetchLikeData(link, commentId);
736				}, 1000);
737			},
738			true
739		);
740	}
741
742	if (document.readyState !== 'loading') {
743		init();
744	} else {
745		document.addEventListener('DOMContentLoaded', init);
746	}
747})();
748;

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.