MediaWiki:Common.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
// Add a dynamic fixed header to tables with:
// 1) table with "header-fixed" class
// 2) a table row with "header-row" class
(function (mw, $) {
'use strict';
mw.loader.using('jquery.tablesorter', function () {
var ts = $.fn.tablesorter;
$.fn.tablesorter = function () {
ts.apply(this, arguments);
initializeStickyHeader();
};
function initializeStickyHeader() {
var tables = [];
var $tables = $('.header-fixed');
if ($tables.length == 0) return;
$tables.each(function () {
// If this table is under tabber AND is hidden,
// temporarily make element visible so we can pull its width data later
var tabberTabParent = $(this).parent('.tabbertab')[0];
var originalStyleDisplay;
if (tabberTabParent) {
originalStyleDisplay = tabberTabParent.style.display;
tabberTabParent.style.visibility = 'hidden';
tabberTabParent.style.display = 'block';
}
// Build fixed-header-table content
var $headerRow = $(this).find('.header-row');
var $newHeaderContent = $('<tbody>');
$newHeaderContent.append($headerRow.clone());
// Add fixed-header-table colgroup to fix width
var $columnsOld = $headerRow.find(
'td:not([colspan]),th:not([colspan])'
);
var $newHeaderColgroup = $('<colgroup>');
for (var i = 0; i < $columnsOld.length; i++) {
var width = $columnsOld[i].getBoundingClientRect().width;
$newHeaderColgroup.append($('<col>').css('width', width));
}
$(this).before(
$('<table>')
.addClass('header-fixed-helper')
.addClass('wikitable')
.css('position', 'sticky')
.css('top', '0')
.css('z-index', '9')
.css('display', 'none')
.css('background-color', '#fff')
.css('margin-top', '0')
.css('margin-bottom', '0')
.css('table-layout', 'fixed')
.append($newHeaderColgroup)
.append($newHeaderContent)
);
tables.push({
table: this,
helper: this.previousSibling,
});
$(this.previousSibling).css('width', $(this).width() + 1);
// Restore original tab display status
// if this table was under tabber and hidden
if (tabberTabParent) {
tabberTabParent.style.visibility = '';
tabberTabParent.style.display = originalStyleDisplay;
}
});
$(window).on('scroll', function () {
var scrollOffset = $(this).scrollTop();
for (var i = 0; i < tables.length; i++) {
var tableOffset = $(tables[i].table).offset().top;
var tableHeight = $(tables[i].table).height();
var helperHeight = $(tables[i].helper).height();
if (
scrollOffset > tableOffset &&
scrollOffset < tableOffset + tableHeight - helperHeight
) {
if ($(tables[i].helper).is(':hidden')) $(tables[i].helper).show();
} else {
$(tables[i].helper).hide();
}
}
});
}
});
})(mediaWiki, jQuery);
/* also in MediaWiki:AudioPlayer.js */
(function (mw, $) {
var audioplayerz = document.getElementsByClassName('audio-button');
var audioPlayer = function () {
var audioplayerx = this;
var audioplayery = audioplayerx.getElementsByTagName('audio')[0];
if (audioplayery.paused) {
var audio = document.getElementsByTagName('audio'),
audioplayeri = audio.length;
while (audioplayeri--) {
audio[audioplayeri].pause();
audio[audioplayeri].currentTime = 0;
}
var audioplayerj = document.getElementsByClassName(
'audio-button playing'
);
while (audioplayerj.length > 0) {
audioplayerj[0].className = 'audio-button';
}
audioplayery.play();
audioplayerx.className = 'audio-button playing';
audioplayery.addEventListener('timeupdate', audioUpdate, false);
audioplayery.addEventListener(
'canplaythrough',
function () {
audioplayerd = audioplayery.duration;
},
false
);
} else {
audioplayery.pause();
audioplayery.currentTime = 0;
audioplayerx.className = 'audio-button';
}
};
function audioUpdate() {
if (this.currentTime == audioplayerd) {
this.parentElement.className = 'audio-button';
this.removeEventListener('timeupdate', audioUpdate, false);
}
}
for (
var audioplayeri = 0;
audioplayeri < audioplayerz.length;
audioplayeri++
) {
audioplayerz[audioplayeri].addEventListener('click', audioPlayer, false);
}
})(mediaWiki, jQuery);
(function (mw, $) {
function checkExist(selector, cb) {
var i = 0;
var handler = setInterval(function () {
i += 1;
if (i > 50) clearInterval(handler);
if (document.querySelector(selector)) {
cb.call();
clearInterval(handler);
}
}, 200);
}
checkExist('.gallery-swap-images', function () {
var swap = function (el) {
$el = $(el);
$active = $el.find('.active');
$next = $active.next();
if ($next.length === 0) {
$next = $el.children().eq(0);
}
$active.removeClass('active');
$next.addClass('active');
};
$('.gallery-swap-images').each(function () {
var self = this;
var delay = $(this).hasClass('gallery-swap-images--speed-fast')
? 2000
: 7000;
// Shuffle
if ($(this).hasClass('gallery-swap-images--shuffle')) {
$(this).html(
$(this)
.children()
.sort(function () {
return Math.random() - 0.5;
})
);
}
// Put everything inside wrapper with layout info
var width = $(this).find('img')[0].getAttribute('width');
var height = $(this).find('img')[0].getAttribute('height');
var div = document.createElement('div');
div.classList.add('wrapper');
div.style['width'] = width + 'px';
div.style['max-width'] = width + 'px';
div.style['aspect-ratio'] = (width / height).toFixed(3);
$(div).append($(this).children());
$(this).append(div);
// Activate
$(this).addClass('enabled');
$(div).children().first().addClass('active');
setInterval(function () {
swap(div);
}, delay);
});
});
})(mediaWiki, jQuery);
/*
This was previously known as Widget:LocalTimeHelper
Used in Template:EventCountdown, Template:EventTime
*/
(function () {
'use strict';
var verifyTime = function (value) {
var time = parseInt(value, 10);
if (isNaN(time)) time = 0;
return time * 1000;
};
var verifyText = function (text, defaultText, hasTime) {
if (text === undefined) return defaultText;
if (hasTime && !text.includes('%s')) text = text + ' %s.';
return text;
};
var formatTime = function (days, hours, minutes, seconds) {
var result = '';
result += days ? ' ' + days.toString() + 'd' : '';
result += hours ? ' ' + hours.toString() + 'h' : '';
result += minutes ? ' ' + minutes.toString() + 'm' : '';
result += seconds ? ' ' + Math.max(seconds - 1, 0).toString() + 's' : '';
return result;
};
var updateCountdownText = function (el) {
var now = new Date();
var startDate = new Date(parseInt(el.dataset.startTime, 10));
var endDate = new Date(parseInt(el.dataset.endTime, 10));
var startDiff = startDate.getTime() - now.getTime();
var endDiff = endDate.getTime() - now.getTime();
var text = el.dataset.textEnd;
var diff = startDiff;
if (startDiff > 1000) {
if (startDate < endDate) text = el.dataset.textStart;
} else if (endDiff > 1000) {
diff = endDiff;
} else {
text = el.dataset.textAfter;
}
if (diff < 1000) {
el.innerHTML = text; // + ' (start: ' + startDate.toUTCString() + ', end: ' + endDate.toUTCString() + ')';
return;
}
diff = diff / 1000;
var days = Math.floor(diff / 86400);
var hours = Math.floor((diff % 86400) / 3600);
var minutes = Math.floor((diff % 3600) / 60);
var seconds = ((diff % 3600) % 60).toFixed(0);
if (days === 1) {
hours += 24;
days = 0;
}
if (hours > 1 || days > 0) seconds = 0;
if (days > 0) minutes = 0;
var result = formatTime(days, hours, minutes, seconds);
var interval = 1000;
if (days > 0) {
interval = 60 * 60 * 1000;
} else if (hours > 1) {
interval = 60 * 1000;
}
el.innerHTML = text.replace('%s', result); // + ' (start: ' + startDate.toUTCString() + ', end: ' + endDate.toUTCString() + ', diff: ' + diff +', interval: ' + interval + ')';
window.setTimeout(function () {
updateCountdownText(el);
}, interval);
};
const nth = function (d) {
if (d > 3 && d < 21) return 'th';
switch (d % 10) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
};
var updateTimeText = function (el, time) {
var months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
var date = new Date(time);
var output = '';
if (el.dataset.format == 'hm' || el.dataset.format == 'H:i') {
output =
('000' + date.getHours()).slice(-2) +
':' +
('000' + date.getMinutes()).slice(-2);
} else if (el.dataset.format == 'md') {
output =
months[date.getMonth()] + ' ' + date.getDate() + nth(date.getDate());
} else if (el.dataset.format == 'mdy') {
output =
months[date.getMonth()] +
' ' +
date.getDate() +
nth(date.getDate()) +
', ' +
date.getFullYear();
} else {
output =
('000' + date.getHours()).slice(-2) +
':' +
('000' + date.getMinutes()).slice(-2) +
', ' +
months[date.getMonth()] +
' ' +
date.getDate() +
nth(date.getDate()) +
', ' +
date.getFullYear();
}
el.innerHTML = output;
};
var ready = function () {
var elements = document.querySelectorAll('.localtime');
Array.from(elements).forEach(function (el) {
var startTime = verifyTime(el.dataset.start);
var endTime = verifyTime(el.dataset.end);
var time = verifyTime(el.dataset.time);
el.dataset.textStart = verifyText(
el.dataset.textStart,
'Starts in %s.',
true
);
el.dataset.textEnd = verifyText(el.dataset.textEnd, 'Ends in %s.', true);
el.dataset.textAfter = verifyText(
el.dataset.textAfter,
'Event has ended.',
false
);
if (startTime + endTime > 0) {
el.dataset.startTime = startTime;
el.dataset.endTime = endTime;
updateCountdownText(el);
} else if (time > 0) {
updateTimeText(el, time);
}
});
};
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', ready);
} else {
ready();
}
})();
/*
Mobile Tooltip Hover/Touch Fix
Prevent link from opening when you first tap a link with tooltip.
Tapping the link again opens the link.
*/
(function () {
document.addEventListener('touchstart', function () {
$('.tooltip.hover').removeClass('hover');
});
$('.tooltip').on('touchstart', function (e) {
if (!$(this).hasClass('hover')) {
e.preventDefault();
e.stopPropagation();
$('.tooltip.hover').removeClass('hover');
$(this).addClass('hover');
}
});
})();
/* Add [edit] link to newsposts */
(function () {
if (!mw.config.get('wgUserId')) return;
document
.querySelectorAll("span.mw-headline[id^='20']")
.forEach(function (el) {
// Stop if it already has edit link
if (el.nextSibling && el.nextSibling.classList.contains('mw-editsection'))
return;
const date = el.textContent;
const link =
'' +
'<span class="mw-editsection">' +
'<span class="mw-editsection-bracket">[</span>' +
'<a href="/News:WhatsNew_' +
date +
'/edit">edit</a>' +
'<span class="mw-editsection-bracket">]</span>' +
'</span>';
el.insertAdjacentHTML('afterend', link);
});
})();
/**
* Tabber - Make links with matching a Tabber hash work when clicked
*/
(function (mw, $) {
/** @type {Map<string, HTMLElement>} */
var shorthandMap = new Map();
/**
* @param {HashChangeEvent=} ev
*/
function handleFuzzyHash(ev) {
var hash = location.hash;
var id = hash.slice(1);
if (!document.getElementById(id)) {
var tab = shorthandMap.get(hash.replace(/-\d+$/, ''));
if (tab) {
tab.click();
// if direct call from hook instead of hashchange event
if (!ev) {
tab.scrollIntoView({ behavior: 'instant' });
}
}
}
}
$(function () {
addEventListener('hashchange', handleFuzzyHash);
});
/**
* @param {HTMLElement} container
*/
function handleNewTabs(container) {
/** @type {HTMLAnchorElement[]} */
var tabberTabs = Array.from(
container.querySelectorAll('a.tabber__tab')
).filter(function (el) {
return el.getAttribute('href') === el.hash;
});
/**
* Intercept this link click and turn it into a tab activation instead
*
* @param {HTMLAnchorElement} link
* @param {HTMLAnchorElement} tab
*/
function handleLink(link, tab) {
link.classList.add('tabber__link');
link.addEventListener('click', function (e) {
e.preventDefault();
tab.click();
});
}
/**
* @param {string} hash
* @param {boolean} exact
*/
function makeSelector(hash, exact) {
if (exact) {
return 'a:not(.tabber__link):not(.tabber__tab)[href="'.concat(
hash,
'"]'
);
}
var prefix = hash.replace(/-\d+$/, '');
return 'a:not(.tabber__link):not(.tabber__tab)[href|="'.concat(
prefix,
'"]'
);
}
tabberTabs.forEach(function (tab) {
var prefix = tab.hash.replace(/-\d+$/, '');
if (prefix !== '#' && !shorthandMap.has(prefix)) {
shorthandMap.set(prefix, tab);
}
const tabContainer = tab.closest('.tabber.tabber--live');
// handle links inside this tabber's contents first, to handle transclusions more safely
// there's still a risk of conflict when there's nested tabbers,
// but addressing that is significantly more difficult
tabContainer
.querySelectorAll(makeSelector(tab.hash, true))
.forEach(handleThisLink);
// assuming this is a transclusion, also handle fuzzy indices that got mismatched
tabContainer
.querySelectorAll(makeSelector(tab.hash, false))
.forEach(handleThisLink);
/**
* @param {HTMLAnchorElement} link
*/
function handleThisLink(link) {
handleLink(link, tab);
}
});
tabberTabs.forEach(function (tab) {
// handle all document links that are exact id matches next
document
.querySelectorAll(makeSelector(tab.hash, true))
.forEach(handleThisLink);
/**
* @param {HTMLAnchorElement} link
*/
function handleThisLink(link) {
handleLink(link, tab);
}
});
tabberTabs.forEach(function (tab) {
// if there are still any leftover links after processing all tabs, finally deal with them
document
.querySelectorAll(makeSelector(tab.hash, false))
.forEach(handleThisLink);
/**
* @param {HTMLAnchorElement} link
*/
function handleThisLink(link) {
handleLink(link, tab);
}
});
handleFuzzyHash();
}
/** @type {Set<HTMLElement>} */
var waiting = new Set();
/** @type {MutationObserver=} */
var observer;
mw.hook('wikipage.content').add(function (containersArg) {
/** @type {HTMLElement[]} */
var containers = Array.from(containersArg);
var tabberStyle = document.getElementById('tabber-style');
if (tabberStyle && tabberStyle.parentNode) {
// tabberNeuer has not yet gotten to run -- wait for it
containers.forEach(function (container) {
waiting.add(container);
});
if (!observer) {
observer = new MutationObserver(function (records) {
if (
records.some(function (record) {
return Array.from(record.removedNodes).includes(tabberStyle);
})
) {
observer.disconnect();
observer = undefined;
waiting.forEach(function (container) {
handleNewTabs(container);
});
waiting.clear();
}
});
observer.observe(tabberStyle.parentNode, { childList: true });
}
} else {
containers.forEach(function (container) {
handleNewTabs(container);
});
}
});
})(mediaWiki, $);