12,787
edits
(Try another event listener) |
(Add Purge link to page tools) |
||
(41 intermediate revisions by 2 users not shown) | |||
Line 521: | Line 521: | ||
//[This is the end of the section stolen from https://oldschool.runescape.wiki/w/MediaWiki:Gadget-highlightTable-core.js] | //[This is the end of the section stolen from https://oldschool.runescape.wiki/w/MediaWiki:Gadget-highlightTable-core.js] | ||
/ | // Sticky headers for tables | ||
function | |||
var | // Returns a list of header rows within a sticky table | ||
var | function getStickyTableHeaders(element) { | ||
var headHeight = document.getElementById('mw-header-container') | var rv = []; | ||
for (var rowIdx = 0; rowIdx < 10; rowIdx++) { | |||
var rowElem = element.getElementsByClassName('headerRow-' + rowIdx.toString()); | |||
if (rowElem.length === 0) { | |||
break; | |||
} | |||
rv.push(rowElem[0]); | |||
} | |||
return rv; | |||
} | |||
// Given a table element, sets the headers' 'top' property as required | |||
function setStickyHeaderTop(element) { | |||
var isOverflown = false; | |||
var parentElem = element.parentElement; | |||
if (parentElem !== undefined) { | |||
isOverflown = (parentElem.scrollHeight > parentElem.clientHeight || parentElem.scrollWidth > parentElem.clientWidth); | |||
} | |||
// Determine the height of the MediWiki header, if it is always visible at the top of the page. | |||
// If the parent div to the table is overflowing, then the header's top position is set in | |||
// relation to that parent element and this can be skipped | |||
var headHeight = 0; | |||
if (!isOverflown) { | |||
var headElem = document.getElementById('mw-header-container'); | |||
if ((headElem !== undefined) && (headElem !== null)) { | |||
var headStyles = getComputedStyle(headElem); | |||
if ((headStyles !== undefined) && (headStyles.position !== 'static')) { | |||
headHeight = headElem.offsetHeight; | |||
} | |||
} | |||
} | } | ||
for (var | |||
var | var cumulativeRowHeight = 0; | ||
var | var headElems = getStickyTableHeaders(element); | ||
var | for (var rowIdx = 0; rowIdx < headElems.length; rowIdx++) { | ||
// Find each header row in sequence. When found, set or remove the 'top' attribute as | |||
// required. If not found, then break | |||
var headElem = headElems[rowIdx]; | |||
var cellElems = headElem.getElementsByTagName('th'); | |||
var topPos = headHeight + cumulativeRowHeight; | |||
// Iterate over all header cells for the current header row | |||
for (var cellIdx = 0; cellIdx < cellElems.length; cellIdx++) { | |||
var cell = cellElems[cellIdx]; | |||
if ((isOverflown) && (cell.style.top !== undefined)) { | |||
// If the table has overflown, then unset the 'top' attribute | |||
cell.style.top = ''; | |||
} | } | ||
if ( | else { | ||
// Otherwise, set the 'top' attribute with the appropriate position | |||
cell.style.top = topPos.toString() + 'px'; | |||
for (var j = 0; j < | } | ||
} | |||
cumulativeRowHeight += headElem.offsetHeight - 1; | |||
} | |||
} | |||
// Initialize observers for stickyHeader tables. These enable attributes of table headers to be | |||
// adjusted as required when various elements are resized | |||
function initStickyObservers() { | |||
if (ResizeObserver !== undefined) { | |||
// If the headers are resized, then the header's top position (particularly the second | |||
// header) may need to be set again | |||
var obvHeaderResize = new ResizeObserver( | |||
function(entries) { | |||
var st = []; | |||
for (var i = 0; i < entries.length; i++) { | |||
var headerRow = entries[i].target; | |||
var stickyTable = headerRow.parentElement.parentElement; | |||
if (!st.includes(stickyTable)) { | |||
st.push(stickyTable); | |||
} | |||
} | |||
for (var j = 0; j < st.length; j++) { | |||
setStickyHeaderTop(st[j]); | |||
} | } | ||
} | |||
); | |||
// If the parent div to a table is overflowing, then the header's top position needs to | |||
// be set in relation to the top of that parent element | |||
var obvOverflow = new ResizeObserver( | |||
function(entries) { | |||
for (var i = 0; i < entries.length; i++) { | |||
var tableParent = entries[i].target; | |||
// The child elements will contain the table we want to set sticky headers for | |||
var stickyTables = tableParent.children; | |||
for (var j = 0; j < stickyTables.length; j++) { | |||
var stickyTable = stickyTables[j]; | |||
if (stickyTable.classList.contains('stickyHeader')) { | |||
setStickyHeaderTop(stickyTable); | |||
} | |||
} | |||
} | |||
} | |||
); | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
for (var i = 0; i < stickyTables.length; i++) { | |||
var stickyTable = stickyTables[i]; | |||
// Observe the table's parent for content overflows | |||
obvOverflow.observe(stickyTable.parentElement); | |||
var headElems = getStickyTableHeaders(stickyTable); | |||
for (var j = 0; j < headElems.length; j++) { | |||
// Observe the table's header rows for resizing | |||
obvHeaderResize.observe(headElems[j]); | |||
} | } | ||
} | } | ||
Line 550: | Line 636: | ||
} | } | ||
function initStickyHeaders() { | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
var | if (stickyTables.length > 0) { | ||
if ( | |||
var elemArticle = document.getElementsByTagName('article'); | var elemArticle = document.getElementsByTagName('article'); | ||
for (i = 0; i < stickyTables.length; i++) { | |||
var stickyTable = stickyTables[i]; | |||
// Sticky headers do not function well when Tabber containers/article tags. | |||
// Therefore identify any stickyHeader tables within these containers | |||
// and remove the stickyHeader class | |||
for (j = 0; j < elemArticle.length; j++) { | |||
if (elemArticle[j].contains(stickyTable)) { | |||
stickyTable.classList.remove('stickyHeader'); | |||
} | } | ||
} | |||
if (stickyTable.classList.contains('stickyHeader')) { | |||
// If the table is still sticky, initialize header positions | |||
setStickyHeaderTop(stickyTable); | |||
} | } | ||
} | } | ||
// Initialize | |||
// Initialize observers | |||
initStickyObservers(); | |||
// Reset sticky header positions when the window resizes, as this may | // Reset sticky header positions when the window resizes, as this may | ||
// affect visibility of fixed elements at the top of the page | // affect visibility of fixed elements at the top of the page | ||
$(window).resize(setStickyHeaderTop); | $(window).resize( | ||
function() { | |||
var stickyTables = document.getElementsByClassName('stickyHeader'); | |||
for (i = 0; i < stickyTables.length; i++) { | |||
setStickyHeaderTop(stickyTables[i]); | |||
} | |||
}); | |||
} | |||
} | |||
function initCollapsibleElements() { | |||
/* 2024-02-18 Allow collapsing of elements with class 'mw-collapsible', in line | |||
* with desktop view behaviour. Extension:MobileFrontend disables this, but | |||
* it is still desirable for our use case | |||
*/ | |||
mw.loader.using('jquery.makeCollapsible').then(function () { $('.mw-collapsible').makeCollapsible(); }); | |||
} | |||
function initWikiAppSidebar() { | |||
if (navigator.userAgent.indexOf('gonative melvorwiki') > -1) { | |||
var isLoggedIn = isUserLoggedIn(); | |||
var myFavs = { | |||
url: 'https://wiki.melvoridle.com/w/Special:Favoritelist', | |||
label: 'My Favourite Pages', | |||
subLinks: [], | |||
icon: 'fas fa-star' | |||
}; | |||
var signIn = { | |||
label: 'Sign In / Register', | |||
url: 'https://wiki.melvoridle.com/index.php?title=Special:UserLogin&returnto=Main+Page', | |||
subLinks: [] | |||
}; | |||
var accountManagement = { | |||
label: 'Account Management', | |||
url: '', | |||
isGrouping: true, | |||
subLinks: [ | |||
{ label: 'Preferences', url: 'https://wiki.melvoridle.com/w/Special:Preferences', subLinks: [] }, | |||
{ label: 'Logout', url: 'https://wiki.melvoridle.com/index.php?title=Special:UserLogout&returnto=Main+Page', subLinks: [] } | |||
], | |||
icon: 'fas fa-user-gear' | |||
}; | |||
var items = [ | |||
{ url: 'https://wiki.melvoridle.com/w/Main_Page', label: 'Home', subLinks: [], icon: 'fas fa-house' } | |||
]; | |||
if (isLoggedIn) { | |||
items.push(myFavs); | |||
} else { | |||
items.push(signIn); | |||
} | |||
items.push( | |||
{ label: 'Guides', url: 'https://wiki.melvoridle.com/w/Guides', icon: 'fas fa-book', subLinks: [] }, | |||
{ label: 'FAQ', url: 'https://wiki.melvoridle.com/w/FAQ', icon: 'fas fa-circle-question', subLinks: [] }, | |||
{ label: 'Changelog', url: 'https://wiki.melvoridle.com/w/Changelog', icon: 'fas fa-book-open', subLinks: [] }, | |||
{ label: 'Mod Creation', url: 'https://wiki.melvoridle.com/w/Mod_Creation', icon: 'fas fa-hammer', subLinks: [] } | |||
); | |||
if (isLoggedIn) { | |||
items.push(accountManagement); | |||
} | |||
items.push({ | |||
label: 'Special Tools', | |||
url: '', | |||
isGrouping: true, | |||
subLinks: [ | |||
{ label: 'Upload Files', url: 'https://wiki.melvoridle.com/w/Special:Upload', subLinks: [], icon: 'fas fa-upload' }, | |||
{ label: 'Special Pages', url: 'https://wiki.melvoridle.com/w/Special:SpecialPages', subLinks: [], icon: 'fas fa-file-powerpoint' } | |||
], | |||
icon: 'fas fa-gear' | |||
}); | |||
items.push({ | |||
label: 'Support Melvor Idle', | |||
url: '', | |||
isGrouping: true, | |||
subLinks: [ | |||
{ label: 'Buy Melvor Idle', url: 'https://wiki.melvoridle.com/w/Full_Version', subLinks: [] }, | |||
{ label: 'Buy Throne of the Herald', url: 'https://wiki.melvoridle.com/w/Throne_of_the_Herald_Expansion', subLinks: [] }, | |||
{ label: 'Buy Atlas of Discovery', url: 'https://wiki.melvoridle.com/w/Atlas_of_Discovery_Expansion', subLinks: [] }, | |||
{ label: 'Patreon', url: 'https://patreon.com/MelvorIdle', subLinks: [], icon: 'fab fa-patreon' } | |||
], | |||
icon: null | |||
}); | |||
items.push({ | |||
label: 'Melvor Idle Socials', | |||
url: '', | |||
isGrouping: true, | |||
subLinks: [ | |||
{ label: 'Discord', url: 'https://discord.gg/melvoridle', subLinks: [], icon: 'fab fa-discord' }, | |||
{ label: 'Reddit', url: 'https://reddit.com/r/MelvorIdle', icon: 'custom icon-reddit-alien', subLinks: [] }, | |||
{ label: 'Twitter', url: 'https://twitter.com/melvoridle', icon: 'custom icon-twitter', subLinks: [] }, | |||
{ label: 'Facebook', url: 'https://facebook.com/melvoridle', icon: 'custom icon-facebook', subLinks: [] }, | |||
{ label: 'Instagram', url: 'https://instagram.com/melvoridle', icon: 'custom icon-instagram', subLinks: [] } | |||
] | |||
}); | |||
median.sidebar.setItems({ "items": items, "enabled": true, "persist": false }); | |||
} | |||
} | |||
function isUserLoggedIn() { | |||
if (mw.config.get('wgUserName') === null) { | |||
return false; | |||
} else { | |||
return true; | |||
} | |||
} | |||
function addToPageTools() { | |||
if (isUserLoggedIn()) { | |||
$.when(mw.loader.using(['mediawiki.util']), $.ready).then( function() { | |||
mw.util.addPortletLink( | |||
'p-cactions', | |||
mw.util.getUrl() + '?action=purge', | |||
'Purge', | |||
't-purgecache', | |||
'Purge the cache for this page', | |||
null, | |||
null | |||
); | |||
}); | }); | ||
} | } | ||
} | |||
function showIOSAppDownloadLink() { | |||
var shouldShowDownload = /iPhone|iPad|iPod/i.test(window.navigator.userAgent) && window.navigator.userAgent.indexOf('gonative melvorwiki') === -1; | |||
if (shouldShowDownload) { | |||
$('.ios-app-download').removeClass('d-none'); | |||
} else { | |||
$('.ios-app-download').addClass('d-none'); | |||
} | |||
} | |||
function showAndroidAppDownloadLink() { | |||
var shouldShowDownload = /Android/i.test(window.navigator.userAgent) && window.navigator.userAgent.indexOf('gonative melvorwiki') === -1; | |||
if (shouldShowDownload) { | |||
$('.android-app-download').removeClass('d-none'); | |||
} else { | |||
$('.android-app-download').addClass('d-none'); | |||
} | |||
} | |||
$(document).ready(function () { | |||
// Table sticky headers | |||
initStickyHeaders(); | |||
// Collapsible elements (for Extension:MobileFrontend) | |||
initCollapsibleElements(); | |||
// Wiki app native navigation | |||
initWikiAppSidebar(); | |||
// Show iOS App download link | |||
showIOSAppDownloadLink(); | |||
// Show Android App download link | |||
showAndroidAppDownloadLink(); | |||
// Add links to Page Tools navigation | |||
addToPageTools(); | |||
}); | }); |