MediaWiki:Common.js

/* Any JavaScript here will be loaded for all users on every page load. */ (function {   // Headline link button.    function addHeadlineLinks {        if (document.querySelector('.mw-headline .copy-button')) return;

const headlines = document.querySelectorAll('.mw-parser-output :is(h1, h2:not(.pi-item, #mw-toc-heading), h3:not(.pi-data-label), h4, h5, h6)'), linkIconHTML = ' ', buttonEvents = ['click', 'keydown', 'pointerdown'];

if (!headlines) return;

headlines.forEach(function(headline) {           const headlineTitle = headline.innerText,                textElement = headline.querySelector('.mw-headline'),                linkButtonHTML = ' ' + linkIconHTML + '  Copy link Link copied   '

textElement.insertAdjacentHTML('beforeend', linkButtonHTML);

const linkButton = headline.querySelector('.copy-button');

buttonEvents.forEach(function(event) {               linkButton.addEventListener(event, copyShareLink);            }); });

function copyShareLink(event) { if (event.type === 'click' ||               event.type === 'pointerdown' ||                event.type === 'keydown' && event.key === ' ' ||                event.type === 'keydown' && event.key === 'Enter') {

event.preventDefault; const target = event.target, constructedURL = mw.config.get.wgServer + mw.config.get.wgArticlePath.replace('$1', '') + mw.config.get.wgRelevantPageName, sectionId = encodeURI(target.closest('.mw-headline').getAttribute('id')), url = constructedURL + '#' + sectionId;

navigator.clipboard.writeText(url); target.classList.add('active');

setTimeout(function {                   target.classList.remove('active');                }, 1000); }       }    }

// Carousel. function mpCarouselInit { if (!document.querySelector('.mp-carousel')) return;

const classes = { wrapper: 'mp-carousel', slide: 'mp-carousel--slide', currentSlide: 'current', link: 'mp-carousel--link', timerBar: 'mp-carousel--timer-bar', slidesList: 'mp-carousel--slides-list', slidesListInner: 'mp-carousel--slides-list-inner', slidesListThumbnail: 'mp-carousel--slides-thumbnail', contentWrapper: 'mp-carousel--content', titleWrapper: 'mp-carousel--title-wrapper', title: 'mp-carousel--title', descriptionWrapper: 'mp-carousel--description-wrapper', description: 'mp-carousel--description', ctaWrapper: 'mp-carousel--cta-wrapper', buttonPrev: 'mp-carousel--button-prev', buttonNext: 'mp-carousel--button-next', },           carouselWrapper = document.querySelector('.' + classes.wrapper), carouselTimerBar = document.createElement('div'), carouselSlidesList = document.createElement('div'), carouselSlidesListInner = document.createElement('div'), carouselPrevButton = document.createElement('button'), carouselNextButton = document.createElement('button'), breakpoints = [851, 1100, 1340], url = mw.config.get.wgServer + mw.config.get.wgArticlePath.replace('$1', ''); var carouselSlides = document.querySelectorAll('.' + classes.slide), windowWidth = window.innerWidth, closestBreakpoint = breakpoints.reduce(function(prev, curr) {               return (Math.abs(curr - windowWidth) < Math.abs(prev - windowWidth) ? curr : prev);           });

carouselTimerBar.className = classes.timerBar; carouselSlidesList.className = classes.slidesList; carouselSlidesListInner.className = classes.slidesListInner; carouselWrapper.append(carouselSlidesList); carouselWrapper.append(carouselTimerBar); carouselSlidesList.append(carouselSlidesListInner);

for (var i = 0; i < carouselSlides.length; i++) { const currentSlide = carouselSlides[i], slideData = { image: currentSlide.getAttribute('data-image'), title: currentSlide.getAttribute('data-title'), description: currentSlide.getAttribute('data-description'), link: currentSlide.getAttribute('data-link'), ctaText: currentSlide.getAttribute('data-cta') || 'View more' },               slideContent = document.createElement('div'), slideLink = document.createElement('a'), slideTitleWrapper = document.createElement('div'), slideTitle = document.createElement('span'), slideDescriptionWrapper = document.createElement('div'), slideDescription = document.createElement('p'), slideCtaWrapper = document.createElement('div'), slidesListThumbnail = document.createElement('div'), imageUrl = 'url("/w/Special:Filepath/' + slideData.image + '?width=' + closestBreakpoint + '")';

if (i === 0) currentSlide.classList.add(classes.currentSlide); slideContent.className = classes.contentWrapper; slideLink.className = classes.link; slideTitleWrapper.className = classes.titleWrapper; slideTitle.className = classes.title; slideDescriptionWrapper.className = classes.descriptionWrapper; slideDescription.className = classes.description; slideCtaWrapper.className = classes.ctaWrapper; slidesListThumbnail.className = classes.slidesListThumbnail; if (i === 0) slidesListThumbnail.classList.add(classes.currentSlide);

slideLink.setAttribute('href', encodeURI(slideData.link)); slideLink.setAttribute('target', '_blank'); slideLink.innerText = slideData.ctaText; slideTitle.innerText = slideData.title; slideDescription.innerText = slideData.description;

currentSlide.append(slideContent); slideContent.append(slideTitleWrapper); slideContent.append(slideDescriptionWrapper); slideContent.append(slideCtaWrapper); slideTitleWrapper.append(slideTitle); slideDescriptionWrapper.append(slideDescription); slideCtaWrapper.append(slideLink); carouselSlidesListInner.append(slidesListThumbnail);

slidesListThumbnail.style.backgroundImage = imageUrl; currentSlide.style.backgroundImage = imageUrl; }

function mpCarouselAutoProgress { carouselSlides = document.querySelectorAll('.' + classes.slide); const firstSlide = document.querySelector('.' + classes.slide), currentSlide = document.querySelector('.' + classes.slide + '.' + classes.currentSlide), prevSlide = currentSlide.previousElementSibling, nextSlide = currentSlide.nextElementSibling, lastSlide = carouselSlides[carouselSlides.length - 1], firstThumbnail = document.querySelector('.' + classes.slidesListThumbnail), currentThumbnail = document.querySelector('.' + classes.slidesListThumbnail + '.' + classes.currentSlide), prevThumbnail = currentThumbnail.previousElementSibling, nextThumbnail = currentThumbnail.nextElementSibling, lastThumbnail = document.querySelectorAll('.' + classes.slidesListThumbnail)[carouselSlides.length - 1];

if (prevSlide) { const isPrevSlideActive = prevSlide.classList.contains(classes.currentSlide);

if (isPrevSlideActive) { prevSlide.classList.remove(classes.currentSlide); prevThumbnail.classList.remove(classes.currentSlide); }           }

if (nextThumbnail === null) { lastSlide.classList.remove(classes.currentSlide); lastThumbnail.classList.remove(classes.currentSlide); firstSlide.classList.add(classes.currentSlide); firstThumbnail.classList.add(classes.currentSlide); } else { currentSlide.classList.remove(classes.currentSlide); currentThumbnail.classList.remove(classes.currentSlide); nextSlide.classList.add(classes.currentSlide); nextThumbnail.classList.add(classes.currentSlide); }       }

carouselTimerBar.addEventListener('animationiteration', mpCarouselAutoProgress); }

// Banner images. function loadBanners { const banners = document.querySelectorAll('.img-banner[data-background-image]');

if (!banners) return;

banners.forEach(function(banner) {           const image = banner.getAttribute('data-background-image');

banner.removeAttribute('data-background-image'); banner.style.setProperty('--banner-image', 'url(/w/Special:Filepath/Area_' + image + '.png)'); });   }

/*	** This script replaces all images in Template:HK Nav Charms for a single ** atlas image that contains all charms' sprites and use the image's position ** to display each charm in a that replaces the original image. **	** This is to reduce the amount of network requests and save data when ** opening the navbox and loading all sprites. */   function charmsNavboxInit { const navboxCharms = document.querySelectorAll('#charms-nav');

if (!navboxCharms) return;

const navboxImages = document.querySelectorAll('#charms-nav img'), navboxSelfUrl = document.querySelector('#charms-nav td > center > strong'), shrinkFactor = 5, atlasWidth = 1260, atlasUrl = 'https://hk.fandom.com/Special:Filepath/Charms-atlas.webp?width=' + (atlasWidth / shrinkFactor), floor = Math.floor, imageCellWidth = 180 / shrinkFactor, imageCellHeight = 162 / shrinkFactor, imageRows = 7, imageColumns = 7, imageOffsetX = 3, imageOffsetY = 2, head = document.head, navboxCss = '\ .charm-nav-img {\n\ background-image: url(' + atlasUrl + ');\n\ background-repeat: no-repeat;\n\ display: inline-block;\n\ height: 30px;\n\ width: 30px;\n\ }\n\ \n\ align-items: center;\n\ display: flex !important;\n\ gap: 8px;\n\ }\n\ \n\ .selflink {\n\ color: var(--theme-page-text-color);\n\ font-weight: 700;\n\ }\n\ \n\ .selflink:hover {\n\ color: var(--theme-page-text-color);\n\ text-decoration: none;\n\ text-shadow: none;\n\ }\           ';
 * 1) charms-nav td a {\n\

head.insertAdjacentHTML('beforeend', navboxCss);

// Remove useless elements from table cells. if (document.querySelector('#charms-nav .center') || document.querySelector('#charms-nav br')) { document.querySelectorAll('#charms-nav .center, #charms-nav br').forEach(function(e) {               e.remove;            }); }

// Update self link in navbox to be an actual link with no function. // This way the image updater script below works properly. if (navboxSelfUrl) { const selfUrlText = navboxSelfUrl.textContent, selfUrlAnchor = document.createElement('a');

selfUrlAnchor.setAttribute('title', selfUrlText); selfUrlAnchor.classList.add('mw-selflink', 'selflink'); selfUrlAnchor.textContent = selfUrlText; navboxSelfUrl.insertAdjacentElement('afterend', selfUrlAnchor); navboxSelfUrl.remove; }

// Now that the self link is an actual link, select all links in the navbox. const navboxUrls = document.querySelectorAll('#charms-nav td a');

// Delete the previous images. navboxImages.forEach(function removeImages(image) {           image.remove;        });

// Add a new image using an atlas in which all Charms are in. This // decreases data usage. navboxUrls.forEach(function addNewImages(url) {           const index = Array.from(navboxUrls).indexOf(url),                newImage = document.createElement('div');

newImage.classList.add('charm-nav-img'); newImage.style.backgroundPosition = ((cellRowNum(index) * -imageCellWidth) - imageOffsetX) + 'px ' + ((floor(index / imageColumns) * -imageCellHeight) - imageOffsetY) + 'px';

url.prepend(newImage); });

function cellRowNum(i) { if (isNaN(i) || !Number.isInteger(i)) { throw new Error('Invalid input: expected an integer but instead got "' + i + '".'); } else { return Math.abs(i) % imageColumns; }       }    }

mw.hook('wikipage.content').add(function {       addHeadlineLinks;        loadBanners;        // charmsNavboxInit;        mpCarouselInit;	}); });