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' : ''}">•</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.