MediaWiki:Common.js

/* Any JavaScript here will be loaded for all users on every page load. */

// 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 imageName = banner.getAttribute('data-background-image'),            position = banner.getAttribute('data-position');

if (position === 'left' || position === 'right') { banner.removeAttribute('data-position'); banner.classList.add(position); }

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

/* function charmsNavboxInit { const navboxCharms = document.querySelectorAll('#charms-nav');
 * 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.
 * opening the navbox and loading all sprites.

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; });

// dev:BandcampPlayer.js mw.hook('wikipage.content').add(function($content) {	var $bandcamp = $content.find( '.bandcamp:not(.loaded)' );   if ( !$bandcamp.length ) return;

$bandcamp.each( function {		var elem = $( this );

var width = elem.attr( 'data-width' ), height = elem.attr( 'data-height' ), data_src = elem.attr( 'data-src' );

if ( !/^https?:\/\/bandcamp\.com\//.test( data_src ) ) return; elem.empty; var is_px = [ true, true ]; // width, height

if ( /%/.test( width ) || !/\d+/.test( width ) ) is_px[ 0 ] = false; if ( /%/.test( height ) ) is_px[ 1 ] = false;

var frame_width = parseFloat( width, 10 ) || 100; frame_height = height ? ( parseFloat( height, 10 ) || 'auto' ) : '';

$( ' ', {			style: 'border: 0',			width: frame_width + ( is_px[ 0 ] ? 'px' : '%' ),			height: frame_height + ( frame_height ? ( is_px[ 1 ] ? 'px': '%' ) : '' ),			src: data_src		}).appendTo( elem ); elem.addClass( 'loaded' ); }); });

// dev:ChromeToolbarColor function getAccentRgb { const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--theme-accent').substring(1).split(', '), toolbarColor = rgbToHex(Number(accentColor[0]), Number(accentColor[1]), Number(accentColor[2]));

function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? '0' + hex : hex; }

function rgbToHex(r, g, b) { return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b); }

return toolbarColor; }

window.ChromeToolbarColor = getAccentRgb;

/** * @name   ChromeToolbarColor * @desc   Sets the mobile Chrome toolbar color to wiki's community header color * * @author  Rail01 * @author KockaAdmiralac * @author Caburum */ ( function( mw, window ) {	// Double-loading prevention	if ( document.querySelector( 'meta[name="theme-color"]' ) ) return;

var color = getComputedStyle( document.documentElement ).getPropertyValue( '--theme-sticky-nav-background-color' );

if (!color) return;

// Create and configure tag var meta = document.createElement( 'meta' ); meta.setAttribute( 'name', 'theme-color' ); meta.setAttribute( 'content',		window.ChromeToolbarColor || color	);

// Add tag to document's head document.head.appendChild( meta ); } )( mediaWiki, this );