MediaWiki:Datatables.js: Difference between revisions
From PC Gaming Shelter
No edit summary |
No edit summary |
||
| (54 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
var selectors = [ | var selectors = ["#games > table"]; | ||
var seriesSelectors = ["#series > table"]; | |||
var assetSelectors = ["#assets > table"]; | |||
var playerSelectors = ["#players table"]; | |||
var clanSelectors = ["#clans table"]; | |||
]; | var allSelectors = selectors.concat( | ||
var seriesSelectors = [ | seriesSelectors, | ||
assetSelectors, | |||
playerSelectors, | |||
clanSelectors, | |||
); | |||
]; | |||
var | |||
]; | |||
var allSelectors = selectors.concat(seriesSelectors, assetSelectors); | |||
var found = false; | var found = false; | ||
for (var i = 0; i < allSelectors.length; i++) { | for (var i = 0; i < allSelectors.length; i++) { | ||
if (document.querySelector(allSelectors[i])) { | |||
found = true; | |||
break; | |||
} | |||
} | } | ||
| Line 30: | Line 22: | ||
if (found === true) { | if (found === true) { | ||
/* Load CSS. */ | |||
const dtsource = | |||
"https://cdn.datatables.net/v/dt/moment-2.29.4/dt-2.3.7/b-3.2.6/b-colvis-3.2.6/b-html5-3.2.6/b-print-3.2.6/cc-1.2.0/date-1.6.3/fh-4.0.5/r-3.0.8/sc-2.4.3/sb-1.8.4/sp-2.3.5/sl-3.1.3/sr-1.4.3/datatables.min.css"; | |||
if (!document.querySelector('link[href*="datatables.min.css"]')) { | |||
$("<link/>", { rel: "stylesheet", href: dtsource }).appendTo("head"); | |||
} | |||
if (!document.querySelector("#datatable-styles")) { | |||
$("<style/>", { | |||
id: "datatable-styles", | |||
text: /* css */ ` | |||
table.dataTable > tbody > tr > td { | table.dataTable > tbody > tr > td { | ||
padding-top: 0.25rem; | padding-top: 0.25rem; | ||
| Line 48: | Line 39: | ||
} | } | ||
table.dataTable, | |||
table.dataTable * { | |||
border: 0 !important; | |||
} | |||
.dt-loading { | .dt-loading { | ||
display: block !important; | display: block !important; | ||
| Line 80: | Line 72: | ||
} | } | ||
} | } | ||
div.dtsp-panesContainer div.dtsp-title { | |||
padding: 2px 0 !important; | |||
} | |||
.dtsp-bordered, .dtsp-bordered:hover, | .dtsp-bordered, | ||
.dtsp-searchPane, .dtsp-searchPane:hover { | .dtsp-bordered:hover, | ||
.dtsp-searchPane, | |||
.dtsp-searchPane:hover { | |||
border: 0 !important; | border: 0 !important; | ||
} | } | ||
.dtsp-topRow button:hover, | |||
.dtsp-topRow input:hover, | |||
.dtsp-topRow button:focus, | |||
.dtsp-topRow input:focus, | |||
.dtsp-topRow button, | |||
.dtsp-topRow input { | |||
margin: 0 !important; | |||
} | |||
.dtsp-paneButton.clearButton { | .dtsp-paneButton.clearButton { | ||
font-size: 2.5rem; | font-size: 2.5rem; | ||
line-height: 0; | |||
padding-bottom: 6px; | padding-bottom: 6px; | ||
} | } | ||
| Line 104: | Line 101: | ||
.dtsp-paneButton.clearButton:not(:disabled) { | .dtsp-paneButton.clearButton:not(:disabled) { | ||
color: #dc3545 !important; | color: #dc3545 !important; | ||
font-size: 3rem; | |||
} | } | ||
.dtsp-topRow { | |||
background-color: #1e2d59; | |||
background-image: url(/w/extensions/wikivisor/images/embossed-leather-pattern.png); | background-image: url(/w/extensions/wikivisor/images/embossed-leather-pattern.png); | ||
background-blend-mode: darken; | background-blend-mode: darken; | ||
background-size: cover; | background-size: cover; | ||
border: 1px solid rgba(255,255,255,0.2); | border: 1px solid rgba(255, 255, 255, 0.2); | ||
box-shadow: inset 0 0 30px rgba(0,0,0,0.75); | box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.75); | ||
} | |||
.dtsp-caret { | .dtsp-caret { | ||
align-items: end; | align-items: end; | ||
| Line 125: | Line 122: | ||
align-items: center; | align-items: center; | ||
} | } | ||
div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret { | div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret { | ||
top: unset !important; | |||
} | } | ||
| Line 141: | Line 138: | ||
content: "\\2303"; | content: "\\2303"; | ||
} | } | ||
div.dtsp-searchPane div.dtsp-topRow div.dtsp-searchCont input.dtsp-search { | div.dtsp-searchPane div.dtsp-topRow div.dtsp-searchCont input.dtsp-search { | ||
padding-left: 8px; | |||
} | |||
.dt-filter-toggle { | .dt-filter-toggle { | ||
| Line 247: | Line 244: | ||
.dt-filter-panel.is-open { | .dt-filter-panel.is-open { | ||
box-shadow: 0 0 20px rgba(255,255,255,0.2); | box-shadow: 0 0 20px rgba(255, 255, 255, 0.2); | ||
transform: translateX(0); | transform: translateX(0); | ||
} | } | ||
| Line 270: | Line 267: | ||
background: transparent; | background: transparent; | ||
border: 0; | border: 0; | ||
color: rgb(124,124,124); | color: rgb(124, 124, 124); | ||
font-size: 2.5rem; | font-size: 2.5rem; | ||
line-height: 1; | line-height: 1; | ||
| Line 276: | Line 273: | ||
width: 35px; | width: 35px; | ||
height: 35px; | height: 35px; | ||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | } | ||
| Line 356: | Line 353: | ||
width: 100% !important; | width: 100% !important; | ||
} | } | ||
aside.is-open .sidebar-drawer { | |||
display: inline-block; | |||
right: 0; | |||
left: 284px; | |||
border: 0; | |||
z-index: 3; | |||
} | |||
aside.dt-filter-panel { | |||
background-image: url(/w/images/d/de/File_Icon-Guardians-semitransparent.png); | |||
background-position: bottom; | |||
background-repeat: no-repeat; | |||
} | |||
.dt-search label { | |||
display: none; | |||
} | |||
#players tbody tr td:last-child { | |||
color: var(--bg-shade-6); | |||
} | |||
#players .player-games { | |||
align-items: baseline; | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 0.25rem 0.5rem; | |||
position: relative; | |||
} | |||
#players .player-games__more { | |||
display: inline-block; | |||
position: relative; | |||
margin-left: auto; | |||
} | |||
#players .player-games__toggle { | |||
background: none; | |||
color: var(--bs-link-color, #6ea8fe); | |||
cursor: pointer; | |||
font-size: 0.875em; | |||
list-style: none; | |||
padding: 0; | |||
white-space: nowrap; | |||
} | |||
#players .player-games__toggle::-webkit-details-marker { | |||
display: none; | |||
} | |||
#players .player-games__toggle:hover, | |||
#players .player-games__toggle:focus { | |||
text-decoration: underline; | |||
} | |||
#players .player-games__popup { | |||
background-color: var(--bg-shade-4, #11161b); | |||
border: 1px solid rgba(255, 255, 255, 0.125) !important; | |||
border-radius: 0.25rem; | |||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.35); | |||
color: var(--bs-body-color, #fff); | |||
display: flex; | |||
flex-direction: column; | |||
gap: 0.25rem; | |||
left: 0; | |||
line-height: 1.5; | |||
min-width: max-content; | |||
padding: 0.5rem 0.75rem; | |||
position: absolute; | |||
top: calc(100% + 0.25rem); | |||
white-space: nowrap; | |||
z-index: 20; | |||
} | |||
.player-games__popup::before { | |||
display: inline-block; | display: inline-block; | ||
width: 6px; | |||
height: 6px; | |||
content: ''; | |||
border-left: 3px solid rgba(255, 255, 255, .25); | |||
border-top: 3px solid rgba(255, 255, 255, .25); | |||
border-right: 3px solid transparent; | |||
border-bottom: 3px solid transparent; | |||
transform: rotate(45deg); | |||
margin-top: -12px; | |||
} | |||
`, | `, | ||
}).appendTo("head"); | |||
} | |||
$(allSelectors.join(",")).addClass("dt-loading").attr("aria-busy", "true"); | |||
/* Load JavaScript. */ | |||
$.when( | |||
mw.loader.getScript( | |||
"https://cdn.datatables.net/v/dt/moment-2.29.4/dt-2.3.7/b-3.2.6/b-colvis-3.2.6/b-html5-3.2.6/b-print-3.2.6/cc-1.2.0/date-1.6.3/fh-4.0.5/r-3.0.8/sc-2.4.3/sb-1.8.4/sp-2.3.5/sl-3.1.3/sr-1.4.3/datatables.min.js", | |||
), | |||
).then( | |||
() => { | |||
window.datatablesLoaded = true; | |||
initDataTable(); | |||
}, | |||
(e) => mw.log.error(e.message), | |||
); | |||
} | } | ||
| Line 393: | Line 463: | ||
function initDataTable() { | function initDataTable() { | ||
function getDataTableColumnCount(table) { | |||
var headerRow = | |||
table.tHead && table.tHead.rows.length | |||
? table.tHead.rows[table.tHead.rows.length - 1] | |||
: null; | |||
if (headerRow) { | |||
return headerRow.cells.length; | |||
} | |||
return table.rows.length ? table.rows[0].cells.length : 0; | |||
} | |||
function normaliseDataTableRows(table, expectedColumnCount) { | |||
$("tbody tr", table).each(function () { | |||
var row = this; | |||
var cellCount = row.cells.length; | |||
if (cellCount === 1 && row.cells[0].colSpan > 1) { | |||
return; | |||
} | |||
while (cellCount < expectedColumnCount) { | |||
row.appendChild(document.createElement("td")); | |||
cellCount++; | |||
} | |||
}); | |||
} | |||
function waitForTableToSettle(table, callback) { | |||
var settleTimer; | |||
var timeoutTimer; | |||
var observer; | |||
var settled = false; | |||
function finish() { | |||
if (settled) return; | |||
settled = true; | |||
clearTimeout(settleTimer); | |||
clearTimeout(timeoutTimer); | |||
if (observer) { | |||
observer.disconnect(); | |||
} | |||
callback(); | |||
} | |||
function scheduleFinish() { | |||
clearTimeout(settleTimer); | |||
settleTimer = setTimeout(finish, 500); | |||
} | |||
if (window.MutationObserver) { | |||
observer = new MutationObserver(scheduleFinish); | |||
observer.observe(table.tBodies[0] || table, { | |||
childList: true, | |||
subtree: true, | |||
}); | |||
} | } | ||
timeoutTimer = setTimeout(finish, 30000); | |||
scheduleFinish(); | |||
} | |||
function getAcceptsValues(html) { | |||
return (html || "") | |||
.split(/<br\s*\/?>/i) | |||
.map((value) => value.trim()) | |||
.filter(Boolean); | |||
} | |||
function getPlainAcceptsValues(html) { | |||
return getAcceptsValues(html) | |||
.map(function (value) { | |||
var container = document.createElement("div"); | |||
container.innerHTML = value; | |||
return (container.textContent || "").trim(); | |||
}) | |||
.filter(Boolean); | |||
} | |||
function renderPlayerGames(html) { | |||
var games = getAcceptsValues(html); | |||
if (games.length < 2) { | |||
return games[0] || ""; | |||
} | } | ||
function | return ( | ||
'<div class="player-games">' + | |||
'<span class="player-games__first">' + | |||
games[0] + | |||
"</span>" + | |||
'<details class="player-games__more">' + | |||
'<summary class="player-games__toggle">+' + | |||
(games.length - 1) + | |||
" more</summary>" + | |||
'<div class="player-games__popup" role="group" aria-label="Other games">' + | |||
games | |||
.slice(1) | |||
.map(function (game) { | |||
return "<div>" + game + "</div>"; | |||
}) | |||
.join("") + | |||
"</div></details></div>" | |||
); | |||
} | |||
function | function getPlainSearchPaneRender() { | ||
return { | |||
display: function (data) { | |||
return data; | |||
}, | |||
sp: function (data) { | |||
return getPlainAcceptsValues(data); | |||
}, | |||
}; | |||
} | |||
function getGameModeText(html) { | |||
var container = document.createElement("div"); | |||
var values = []; | |||
container.innerHTML = html || ""; | |||
values.push(container.textContent || ""); | |||
container.querySelectorAll("*").forEach(function (element) { | |||
["alt", "title", "aria-label", "class", "src"].forEach( | |||
function (attribute) { | |||
var value = element.getAttribute(attribute); | |||
if (value) { | |||
values.push(value); | |||
} | |||
}, | |||
); | |||
}); | |||
return values.join(" "); | |||
} | |||
function getGameModeValue(html) { | |||
var text = getGameModeText(html).toLowerCase().replace(/[_-]+/g, " "); | |||
if (/\b(single\s*player|singleplayer|solo|sp)\b/.test(text)) { | |||
return "SP"; | |||
} | |||
if (/\b(multi\s*player|multiplayer|mp)\b/.test(text)) { | |||
return "MP"; | |||
} | } | ||
if (/\b(co\s*op|coop|cooperative)\b/.test(text)) { | |||
return "Coop"; | |||
} | } | ||
function | return ""; | ||
} | |||
function getGameModeValues(html) { | |||
var selectedModes = new Set( | |||
getAcceptsValues(html).map(getGameModeValue).filter(Boolean), | |||
); | |||
return ["SP", "MP", "Coop"].filter(function (mode) { | |||
return selectedModes.has(mode); | |||
}); | |||
} | |||
function getGameModeDisplayValue(html, mode) { | |||
var container = document.createElement("div"); | |||
var link; | |||
container.innerHTML = html || ""; | |||
link = container.querySelector("a[href]"); | |||
if (!link) { | |||
return mode; | |||
} | } | ||
link = link.cloneNode(false); | |||
link.textContent = mode; | |||
return link.outerHTML; | |||
} | |||
function getGameModeDisplayValues(html) { | |||
var valuesByMode = {}; | |||
getAcceptsValues(html).forEach(function (value) { | |||
var mode = getGameModeValue(value); | |||
if (mode && !valuesByMode[mode]) { | |||
valuesByMode[mode] = getGameModeDisplayValue(value, mode); | |||
} | |||
}); | |||
return ["SP", "MP", "Coop"] | |||
.filter(function (mode) { | |||
return Object.prototype.hasOwnProperty.call(valuesByMode, mode); | |||
}) | |||
.map(function (mode) { | |||
return valuesByMode[mode]; | |||
}); | |||
} | |||
function normaliseColumnName(value) { | |||
return String(value || "") | |||
.trim() | |||
.toLowerCase() | |||
.replace(/[\s_-]+/g, ""); | |||
} | |||
function getTableConfigElement(table) { | |||
return ( | |||
table.closest( | |||
"[data-hidden-columns], [data-hide-columns], [data-dt-hidden-columns], [data-dt-hide-columns], [data-pane-columns], [data-search-pane-columns], [data-dt-pane-columns], [data-first-column-name], [data-first-column], [data-page-column-name], [data-dt-first-column-name]", | |||
) || table.parentElement | |||
); | |||
} | |||
function getFirstColumnName(table) { | |||
var configElement = getTableConfigElement(table); | |||
var columnName = configElement | |||
? configElement.getAttribute("data-first-column-name") || | |||
configElement.getAttribute("data-first-column") || | |||
configElement.getAttribute("data-page-column-name") || | |||
configElement.getAttribute("data-dt-first-column-name") | |||
: ""; | |||
return String(columnName || "Page").trim() || "Page"; | |||
} | |||
function getHiddenColumnIndexes(table, titles) { | |||
var configElement = getTableConfigElement(table); | |||
var rawValue = configElement | |||
? configElement.getAttribute("data-hidden-columns") || | |||
configElement.getAttribute("data-hide-columns") || | |||
configElement.getAttribute("data-dt-hidden-columns") || | |||
configElement.getAttribute("data-dt-hide-columns") | |||
: ""; | |||
var titleIndexes = {}; | |||
var hiddenIndexes = new Set(); | |||
titles.forEach(function (title, index) { | |||
titleIndexes[normaliseColumnName(title)] = index; | |||
}); | |||
String(rawValue || "") | |||
.split(/[,\|]/) | |||
.map(function (value) { | |||
return value.trim(); | |||
}) | |||
.filter(Boolean) | |||
.forEach(function (value) { | |||
var numberValue = Number(value); | |||
var normalisedValue = normaliseColumnName(value); | |||
if ( | |||
Number.isInteger(numberValue) && | |||
numberValue >= 0 && | |||
numberValue < titles.length | |||
) { | |||
hiddenIndexes.add(numberValue); | |||
return; | |||
} | } | ||
if ( | if ( | ||
Object.prototype.hasOwnProperty.call(titleIndexes, normalisedValue) | |||
) { | |||
hiddenIndexes.add(titleIndexes[normalisedValue]); | |||
} | } | ||
}); | |||
return hiddenIndexes; | |||
} | |||
function getPaneColumnIndexes(table, titles, fallbackIndexes) { | |||
var configElement = getTableConfigElement(table); | |||
var attributes = [ | |||
"data-pane-columns", | |||
"data-search-pane-columns", | |||
"data-dt-pane-columns", | |||
]; | |||
var configuredAttribute = attributes.find(function (attribute) { | |||
return configElement && configElement.hasAttribute(attribute); | |||
}); | |||
var titleIndexes = {}; | |||
var paneIndexes = []; | |||
if (!configuredAttribute) { | |||
return fallbackIndexes.slice(); | |||
} | } | ||
function | titles.forEach(function (title, index) { | ||
titleIndexes[normaliseColumnName(title)] = index; | |||
}); | |||
String(configElement.getAttribute(configuredAttribute) || "") | |||
.split(/[,\|]/) | |||
.map(function (value) { | |||
return value.trim(); | |||
}) | |||
.filter(Boolean) | |||
.forEach(function (value) { | |||
var numberValue = Number(value); | |||
var normalisedValue = normaliseColumnName(value); | |||
var index = Number.isInteger(numberValue) | |||
? numberValue | |||
: titleIndexes[normalisedValue]; | |||
if ( | |||
Number.isInteger(index) && | |||
index >= 0 && | |||
index < titles.length && | |||
paneIndexes.indexOf(index) === -1 | |||
) { | |||
paneIndexes.push(index); | |||
} | |||
}); | |||
return paneIndexes; | |||
} | |||
function getTableData(table, columnCount) { | |||
return Array.from(table.tBodies[0] ? table.tBodies[0].rows : []) | |||
.filter((row) => !(row.cells.length === 1 && row.cells[0].colSpan > 1)) | |||
.map((row) => { | |||
var data = Array.from(row.cells, (cell) => cell.innerHTML.trim()); | |||
while (data.length < columnCount) { | |||
data.push(""); | |||
} | } | ||
return data.slice(0, columnCount); | |||
}); | |||
} | |||
function buildTableLayout(table, titles) { | |||
var headerRow = document.createElement("tr"); | |||
var tableHead = document.createElement("thead"); | |||
titles.forEach((title) => { | |||
var headerCell = document.createElement("th"); | |||
headerCell.textContent = title; | |||
headerRow.appendChild(headerCell); | |||
}); | |||
table.replaceChildren(tableHead, document.createElement("tbody")); | |||
tableHead.appendChild(headerRow); | |||
} | |||
function suppressSearchPaneBorders(container) { | |||
if (!container) { | |||
return; | |||
} | } | ||
function | function removeBorderClass() { | ||
container.querySelectorAll(".dtsp-bordered").forEach(function (element) { | |||
element.classList.remove("dtsp-bordered"); | |||
}); | |||
} | |||
removeBorderClass(); | |||
new MutationObserver(removeBorderClass).observe(container, { | |||
attributes: true, | |||
attributeFilter: ["class"], | |||
childList: true, | |||
subtree: true, | |||
}); | |||
} | |||
function openFellowshipLinksInNewTab(dataTable) { | |||
dataTable | |||
.column(0) | |||
.nodes() | |||
.toArray() | |||
.forEach(function (cell) { | |||
cell.querySelectorAll("a").forEach(function (link) { | |||
link.target = "_blank"; | |||
}); | }); | ||
}); | |||
} | |||
function setupFilterToolbar(wrapper, toggle) { | |||
var toolbar = document.createElement("div"); | |||
var search = wrapper.querySelector(".dt-search"); | |||
var searchRow = search ? search.closest(".dt-layout-row") : null; | |||
toolbar.className = "dt-filter-toolbar"; | |||
if (searchRow && searchRow.parentNode) { | |||
searchRow.parentNode.insertBefore(toolbar, searchRow); | |||
} else { | |||
wrapper.insertBefore(toolbar, wrapper.firstChild); | |||
} | } | ||
toolbar.appendChild(toggle); | |||
if (search) { | |||
toolbar.appendChild(search); | |||
} | } | ||
if ( | |||
searchRow && | |||
searchRow.parentNode && | |||
!searchRow.querySelector(".dt-search") | |||
) { | |||
searchRow.remove(); | |||
} | } | ||
} | |||
function getCurrentPageName() { | |||
var pageName = ""; | |||
if (window.mw && mw.config) { | |||
pageName = mw.config.get("wgTitle") || mw.config.get("wgPageName") || ""; | |||
} | } | ||
if (!pageName) { | |||
pageName = | |||
(document.querySelector("#firstHeading") || {}).textContent || | |||
document.title || | |||
""; | |||
} | |||
return pageName.replace(/_/g, " ").trim(); | |||
} | |||
function getFilterPanelTitle(titles, hiddenColumnIndexes) { | |||
var pageName = getCurrentPageName(); | |||
if (hiddenColumnIndexes && hiddenColumnIndexes.size === 1) { | |||
return titles[Array.from(hiddenColumnIndexes)[0]] + ": " + pageName; | |||
} | |||
return pageName; | |||
} | |||
function setupFilterOffCanvas(dataTable, table, titles, hiddenColumnIndexes) { | |||
var wrapper = table.closest(".dt-container") || table.parentNode; | |||
var panes = wrapper ? wrapper.querySelector(".dtsp-panesContainer") : null; | |||
if (!wrapper || !panes) { | |||
return null; | |||
} | |||
if (wrapper.querySelector(".dt-filter-toggle")) { | |||
return document.getElementById( | |||
wrapper | |||
.querySelector(".dt-filter-toggle") | |||
.getAttribute("aria-controls"), | |||
); | |||
} | } | ||
var toggle = document.createElement("button"); | |||
var panel = document.createElement("aside"); | |||
var backdrop = document.createElement("div"); | |||
var drawer = document.createElement("button"); | |||
var header = document.createElement("div"); | |||
var title = document.createElement("h2"); | |||
var close = document.createElement("button"); | |||
var body = document.createElement("div"); | |||
var panelId = "dt-filter-panel-" + $(".dt-filter-panel").length; | |||
var lastFocusedElement = null; | |||
toggle.type = "button"; | |||
toggle.className = "dt-filter-toggle"; | |||
toggle.textContent = "Filters"; | |||
toggle.setAttribute("aria-controls", panelId); | |||
toggle.setAttribute("aria-expanded", "false"); | |||
panel.id = panelId; | |||
panel.className = "dt-filter-panel"; | |||
panel.setAttribute("aria-hidden", "true"); | |||
panel.setAttribute("aria-label", "Data filters"); | |||
backdrop.className = "dt-filter-backdrop"; | |||
drawer.className = "sidebar-drawer"; | |||
drawer.setAttribute("type", "button"); | |||
drawer.setAttribute("tabindex", "0"); | |||
header.className = "dt-filter-panel__header"; | |||
title.className = "dt-filter-panel__title"; | |||
title.textContent = getFilterPanelTitle(titles, hiddenColumnIndexes); | |||
close.type = "button"; | |||
close.className = "dt-filter-panel__close"; | |||
close.setAttribute("aria-label", "Close filters"); | |||
close.innerHTML = "×"; | |||
body.className = "dt-filter-panel__body"; | |||
header.append(title, close); | |||
body.appendChild(panes); | |||
panel.append(drawer, header, body); | |||
setupFilterToolbar(wrapper, toggle); | |||
document.body.append(backdrop, panel); | |||
function openPanel() { | |||
lastFocusedElement = document.activeElement; | |||
panel.classList.add("is-open"); | |||
backdrop.classList.add("is-open"); | |||
document.body.classList.add("dt-filter-panel-open"); | |||
panel.setAttribute("aria-hidden", "false"); | |||
toggle.setAttribute("aria-expanded", "true"); | |||
close.focus(); | |||
dataTable.columns.adjust(); | |||
} | } | ||
function | function closePanel() { | ||
panel.classList.remove("is-open"); | |||
backdrop.classList.remove("is-open"); | |||
document.body.classList.remove("dt-filter-panel-open"); | |||
panel.setAttribute("aria-hidden", "true"); | |||
toggle.setAttribute("aria-expanded", "false"); | |||
if (lastFocusedElement && document.contains(lastFocusedElement)) { | |||
lastFocusedElement.focus(); | |||
} | |||
dataTable.columns.adjust(); | |||
} | } | ||
function | toggle.addEventListener("click", openPanel); | ||
close.addEventListener("click", closePanel); | |||
drawer.addEventListener("click", closePanel); | |||
backdrop.addEventListener("click", closePanel); | |||
document.addEventListener("keydown", function (event) { | |||
if (event.key === "Escape" && panel.classList.contains("is-open")) { | |||
closePanel(); | |||
} | |||
}); | |||
return panel; | |||
} | |||
$(".dataTable") | |||
.not(allSelectors.join(",")) | |||
.each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | } | ||
normaliseDataTableRows(table, getDataTableColumnCount(table)); | |||
$(table).DataTable({ | |||
language: { | |||
search: "", | |||
searchPlaceholder: "Search", | |||
searchPanes: { | |||
title: { | |||
_: "Filters: %d", | |||
0: "", | |||
}, | |||
}, | |||
}, | |||
retrieve: true, | |||
order: [[0, "asc"]], | |||
pageLength: 1000, | |||
}); | }); | ||
} | }); | ||
}); | |||
$(seriesSelectors.join(",")).each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | |||
var titles = ["Series", "Games"]; | |||
/* | |||
* The source table is emitted as Games, Series. | |||
* DataTables uses Series, Games, so Series is column 0. | |||
*/ | |||
var data = getTableData(table, titles.length).map(function (row) { | |||
return { | |||
series: row[1], | |||
games: row[0], | |||
}; | |||
}); | |||
normaliseDataTableRows(table, titles.length); | |||
buildTableLayout(table, titles); | |||
try { | |||
var dataTable = new DataTable(table, { | |||
data: data, | |||
columns: [ | |||
{ | |||
data: "games", | |||
name: "Games", | |||
title: "Game", | |||
}, | |||
{ | |||
data: "series", | |||
name: "Series", | |||
title: "Series", | |||
visible: false, | |||
searchable: true, | |||
type: "string", | |||
}, | |||
], | |||
layout: { | |||
topStart: null, | |||
topEnd: null, | |||
top1: { | |||
searchBuilder: { | |||
columns: [1], | |||
preDefined: { | |||
logic: "AND", | |||
criteria: [ | |||
{ | |||
data: "series", | |||
origData: "series", | |||
condition: "=", | |||
type: "string", | |||
}, | |||
], | |||
}, | |||
}, | |||
}, | |||
top2: {}, | |||
}, | |||
language: { | |||
searchBuilder: { | |||
data: "Series", | |||
value: "Select a series", | |||
}, | |||
}, | |||
order: [[0, "asc"]], | |||
paging: false, | |||
initComplete: function () { | |||
var api = this.api(); | |||
var container = $(api.table().container()); | |||
function emptySeriesCriterion() { | |||
return { | |||
logic: "AND", | |||
criteria: [ | |||
{ | |||
data: "series", | |||
origData: "series", | |||
condition: "=", | |||
type: "string", | |||
}, | |||
], | |||
}; | |||
} | |||
function enableAllSeriesOption() { | |||
var valueSelect = container.find(".dtsb-value").first(); | |||
var emptyOption = valueSelect | |||
.find('option[value="--valueTitle--"]') | |||
.first(); | |||
emptyOption | |||
.prop("disabled", false) | |||
.prop("hidden", false) | |||
.text("All series"); | |||
} | |||
container[0].addEventListener( | |||
"change", | |||
function (event) { | |||
var select = event.target; | |||
if ( | |||
!select.matches(".dtsb-value") || | |||
select.value !== "--valueTitle--" | |||
) { | |||
return; | |||
} | |||
/* | |||
* The placeholder has no SearchBuilder value. | |||
* Stop its listener before it calls toString() | |||
* on undefined, then reset the criterion here. | |||
*/ | |||
event.stopImmediatePropagation(); | |||
api.searchBuilder.rebuild(emptySeriesCriterion(), false); | |||
api.draw(false); | |||
setTimeout(enableAllSeriesOption, 0); | |||
}, | |||
true, | |||
); | |||
setTimeout(enableAllSeriesOption, 0); | |||
}, | |||
}); | |||
dataTable.columns.adjust().draw(false); | |||
} catch (error) { | |||
mw.log.error("Could not initialise the Series DataTable:", error); | |||
} finally { | |||
$(table).removeClass("dt-loading").removeAttr("aria-busy").show(); | |||
} | |||
}); | |||
}); | |||
$(assetSelectors.join(",")).each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | |||
var titles = [ | |||
"Asset", | |||
"Type", | |||
"Subtype", | |||
"Mode", | |||
"Developer", | |||
"Released", | |||
"Status", | |||
"Description", | |||
"Version", | |||
"Files", | |||
"Game", | |||
]; | |||
var paneIndexes = [1, 2, 3, 4, 5, 6, 8]; | |||
if (mw.config.get("wgPageName") === "Mods") { | |||
paneIndexes = [10, 1, 2, 3, 4, 5, 6, 8]; | |||
} | |||
var hiddenColumns = [2, 3, 5, 6, 7, 8, 10]; | |||
var data = getTableData(table, titles.length); | |||
buildTableLayout(table, titles); | |||
var dataTable = new DataTable(table, { | |||
columns: titles.map(function (title, index) { | |||
var column = { | |||
title: title, | |||
visible: hiddenColumns.indexOf(index) === -1, | |||
searchPanes: { | |||
show: paneIndexes.indexOf(index) !== -1, | |||
}, | |||
}; | |||
if (index === 0) { | |||
column.render = function (cellData, type) { | |||
if (type === "display") { | |||
return ( | |||
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' + | |||
cellData + | |||
"</span>" | |||
); | |||
} | |||
return cellData; | |||
}; | |||
} | |||
if ([1, 2, 3, 4, 6, 8, 10].indexOf(index) !== -1) { | |||
return | column.render = { | ||
display: function (cellData) { | |||
if (index === 4) { | |||
return ( | |||
'<span style="white-space: nowrap">' + | |||
getAcceptsValues(cellData).join(", ") + | |||
"</span>" | |||
); | |||
} | |||
return cellData; | |||
}, | |||
sp: function (cellData) { | |||
return getPlainAcceptsValues(cellData); | |||
}, | |||
}; | |||
column.searchPanes.orthogonal = "sp"; | |||
} | |||
if (index === 9) { | |||
column.render = function (cellData, type) { | |||
if (type !== "display") { | |||
return cellData; | |||
} | |||
var wrapper = document.createElement("div"); | |||
wrapper.innerHTML = cellData; | |||
var links = Array.from(wrapper.querySelectorAll("a")); | |||
if (!links.length) { | |||
return ""; | |||
} | |||
var downloads = document.createElement("div"); | |||
links.forEach(function (sourceLink, linkIndex) { | |||
var link = sourceLink.cloneNode(true); | |||
link.textContent = | |||
links.length === 1 | |||
? "Download" | |||
: "Download " + (linkIndex + 1); | |||
link.target = "_blank"; | |||
link.rel = "noopener noreferrer nofollow"; | |||
downloads.appendChild(link); | |||
if (linkIndex < links.length - 1) { | |||
downloads.appendChild(document.createElement("br")); | |||
} | |||
}); | |||
return downloads.innerHTML; | |||
}; | |||
} | |||
return column; | |||
}), | |||
layout: { | |||
topStart: null, | |||
topEnd: null, | |||
top2: { | |||
searchPanes: { | |||
cascadePanes: true, | |||
dtOpts: { | |||
scroller: false, | |||
}, | |||
initCollapsed: true, | |||
layout: "columns-1", | |||
columns: paneIndexes, | |||
}, | |||
}, | |||
top1: { | |||
search: { | |||
className: "mx-0 w-100", | |||
}, | |||
}, | |||
}, | |||
order: [[0, "asc"]], | |||
language: { | |||
search: "", | |||
searchPlaceholder: "Search assets", | |||
searchPanes: { | |||
title: { | |||
_: "Filters: %d", | |||
0: "", | |||
}, | |||
}, | |||
}, | |||
paging: false, | |||
searchDelay: 400, | |||
}); | |||
var lastGlobalSearch = dataTable.search(); | |||
dataTable.on("draw.dt", function () { | |||
var globalSearch = dataTable.search(); | |||
openFellowshipLinksInNewTab(dataTable); | |||
if (globalSearch === lastGlobalSearch) { | |||
return; | |||
} | } | ||
lastGlobalSearch = globalSearch; | |||
paneIndexes.forEach(function (paneIndex) { | |||
dataTable.searchPanes.rebuildPane(paneIndex, true); | |||
}); | }); | ||
}); | |||
dataTable.rows.add(data).draw(); | |||
$(table).removeClass("dt-loading").removeAttr("aria-busy").show(); | |||
dataTable.searchPanes.rebuildPane(); | |||
var filterPanel = setupFilterOffCanvas( | |||
dataTable, | |||
table, | |||
titles, | |||
new Set(), | |||
); | |||
suppressSearchPaneBorders(filterPanel); | |||
dataTable.columns.adjust().draw(false); | |||
}); | |||
}); | |||
$(clanSelectors.join(",")).each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | |||
var titles = ["Clan", "Games", "Links", "Description", "Status"]; | |||
var paneIndexes = [1, 4]; | |||
var clanColorClasses = Array.from( | |||
table.tBodies[0] ? table.tBodies[0].rows : [], | |||
) | |||
.filter(function (row) { | |||
return !(row.cells.length === 1 && row.cells[0].colSpan > 1); | |||
}) | |||
.map(function (row) { | |||
return row.cells[0] ? row.cells[0].className : ""; | |||
}); | }); | ||
var data = getTableData(table, titles.length); | |||
buildTableLayout(table, titles); | |||
var dataTable = new DataTable(table, { | |||
data: data, | |||
columns: [ | |||
{ | |||
title: "Clan", | |||
createdCell: function (cell, cellData, rowData, rowIndex) { | |||
var colorClasses = clanColorClasses[rowIndex]; | |||
if (!colorClasses) { | |||
return; | return; | ||
} | } | ||
colorClasses | |||
.split(/\s+/) | |||
.filter(Boolean) | |||
.forEach(function (className) { | |||
cell.classList.add(className); | |||
}); | |||
}, | |||
render: function (cellData, type) { | |||
if (type === "display") { | |||
return ( | |||
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' + | |||
cellData + | |||
"</span>" | |||
); | |||
} | |||
return cellData; | |||
}, | |||
searchPanes: { | |||
show: false, | |||
}, | |||
}, | |||
{ | |||
title: "Games", | |||
visible: false, | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: true, | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Links", | |||
render: function (cellData, type) { | |||
if (type !== "display") { | |||
return cellData; | |||
} | |||
var container = document.createElement("div"); | |||
container.innerHTML = cellData || ""; | |||
container | |||
.querySelectorAll("a.external[href]") | |||
.forEach(function (link) { | |||
var hostname; | |||
try { | |||
hostname = new URL(link.href, window.location.href) | |||
.hostname; | |||
} catch (error) { | |||
hostname = ""; | |||
} | |||
if (hostname) { | |||
link.textContent = hostname.replace(/^www\./i, ""); | |||
} | |||
link.target = "_blank"; | |||
link.rel = "noopener noreferrer nofollow"; | |||
}); | |||
return container.innerHTML; | |||
}, | |||
searchPanes: { | |||
show: false, | |||
}, | |||
}, | |||
{ | |||
title: "Description", | |||
searchPanes: { | |||
show: false, | |||
}, | |||
}, | |||
{ | |||
title: "Status", | |||
visible: false, | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: true, | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
], | |||
layout: { | |||
topStart: null, | |||
topEnd: null, | |||
top2: { | |||
searchPanes: { | |||
cascadePanes: true, | |||
dtOpts: { | |||
scroller: false, | |||
}, | |||
initCollapsed: true, | |||
layout: "columns-1", | |||
columns: paneIndexes, | |||
}, | |||
}, | |||
top1: { | |||
search: { | |||
className: "mx-0 w-100", | |||
}, | |||
}, | |||
}, | |||
order: [[0, "asc"]], | |||
language: { | |||
search: "", | |||
searchPlaceholder: "Search clans", | |||
searchPanes: { | |||
title: { | |||
_: "Filters: %d", | |||
0: "", | |||
}, | |||
}, | |||
}, | |||
paging: false, | |||
searchDelay: 400, | |||
}); | |||
var lastGlobalSearch = dataTable.search(); | |||
dataTable.on("draw.dt", function () { | |||
var globalSearch = dataTable.search(); | |||
openFellowshipLinksInNewTab(dataTable); | |||
if (globalSearch === lastGlobalSearch) { | |||
return; | |||
} | |||
lastGlobalSearch = globalSearch; | |||
paneIndexes.forEach(function (paneIndex) { | |||
dataTable.searchPanes.rebuildPane(paneIndex, true); | |||
}); | |||
}); | |||
$(table).removeClass("dt-loading").removeAttr("aria-busy").show(); | |||
dataTable.searchPanes.rebuildPane(); | |||
var filterPanel = setupFilterOffCanvas( | |||
dataTable, | |||
table, | |||
titles, | |||
new Set(), | |||
); | |||
suppressSearchPaneBorders(filterPanel); | |||
dataTable.columns.adjust().draw(false); | |||
}); | |||
}); | |||
$(playerSelectors.join(",")).each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | |||
var fallbackTitles = [ | |||
"Player", | |||
"Games", | |||
"Clans", | |||
"Roles", | |||
"Clan (Role)", | |||
"Status", | |||
]; | |||
var headerCells = | |||
table.tHead && table.tHead.rows.length | |||
? Array.from(table.tHead.rows[table.tHead.rows.length - 1].cells) | |||
: []; | |||
var titles = fallbackTitles.map(function (fallbackTitle, index) { | |||
var title = headerCells[index] | |||
? headerCells[index].textContent.trim() | |||
: ""; | |||
return title || fallbackTitle; | |||
}); | |||
var hiddenColumnIndexes = new Set([2, 3, 5]); | |||
var paneIndexes = getPaneColumnIndexes(table, titles, [2, 3, 5]); | |||
var playerColorClasses = Array.from( | |||
table.tBodies[0] ? table.tBodies[0].rows : [], | |||
) | |||
.filter(function (row) { | |||
return !(row.cells.length === 1 && row.cells[0].colSpan > 1); | |||
}) | |||
.map(function (row) { | |||
return row.cells[0] ? row.cells[0].className : ""; | |||
}); | }); | ||
var data = getTableData(table, titles.length); | |||
buildTableLayout(table, titles); | |||
var dataTable = new DataTable(table, { | |||
columns: titles.map(function (title, index) { | |||
var column = { | |||
title: title, | |||
visible: !hiddenColumnIndexes.has(index), | |||
searchPanes: { | |||
show: paneIndexes.indexOf(index) !== -1, | |||
}, | |||
}; | |||
if (index === 0) { | |||
column.createdCell = function (cell, cellData, rowData, rowIndex) { | |||
var colorClasses = playerColorClasses[rowIndex]; | |||
if (!colorClasses) { | |||
return; | return; | ||
} | |||
colorClasses | |||
.split(/\s+/) | |||
.filter(Boolean) | |||
.forEach(function (className) { | |||
cell.classList.add(className); | |||
}); | |||
}; | |||
column.render = function (cellData, type) { | |||
if (type === "display") { | |||
return ( | |||
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' + | |||
cellData + | |||
"</span>" | |||
); | |||
} | |||
return cellData; | |||
}; | |||
} else if (index === 1) { | |||
column.render = function (cellData, type) { | |||
if (type === "display") { | |||
return renderPlayerGames(cellData); | |||
} | |||
if (type === "sp") { | |||
return getPlainAcceptsValues(cellData); | |||
} | |||
return cellData; | |||
}; | |||
if (paneIndexes.indexOf(index) !== -1) { | |||
column.searchPanes.orthogonal = "sp"; | |||
} | |||
} else if (paneIndexes.indexOf(index) !== -1) { | |||
column.render = { | |||
display: function (cellData) { | |||
return cellData; | |||
}, | |||
sp: function (cellData) { | |||
var container = document.createElement("div"); | |||
var linkedValues; | |||
var blockValues; | |||
container.innerHTML = cellData || ""; | |||
linkedValues = Array.from( | |||
container.querySelectorAll("a"), | |||
function (link) { | |||
return link.textContent.trim(); | |||
}, | |||
).filter(Boolean); | |||
if (linkedValues.length) { | |||
return linkedValues; | |||
} | |||
} | |||
blockValues = Array.from( | |||
container.children, | |||
function (element) { | |||
return element.textContent.trim(); | |||
}, | |||
).filter(Boolean); | |||
return blockValues.length | |||
? blockValues | |||
: getAcceptsValues(cellData); | |||
}, | |||
}; | |||
column.searchPanes.orthogonal = "sp"; | |||
} | |||
return column; | |||
}), | |||
layout: { | |||
topStart: null, | |||
topEnd: null, | |||
top2: { | |||
searchPanes: { | |||
cascadePanes: true, | |||
dtOpts: { | |||
scroller: false, | |||
}, | |||
initCollapsed: true, | |||
layout: "columns-1", | |||
columns: paneIndexes, | |||
}, | |||
}, | |||
top1: { | |||
search: { | |||
className: "mx-0 w-100", | |||
}, | |||
}, | |||
}, | |||
order: [[0, "asc"]], | |||
language: { | |||
search: "", | |||
searchPlaceholder: "Search players", | |||
searchPanes: { | |||
title: { | |||
_: "Filters: %d", | |||
0: "", | |||
}, | |||
}, | |||
}, | |||
paging: false, | |||
searchDelay: 400, | |||
}); | |||
var lastGlobalSearch = dataTable.search(); | |||
dataTable.on("draw.dt", function () { | |||
var globalSearch = dataTable.search(); | |||
openFellowshipLinksInNewTab(dataTable); | |||
if (globalSearch === lastGlobalSearch) { | |||
return; | |||
} | |||
lastGlobalSearch = globalSearch; | |||
paneIndexes.forEach(function (paneIndex) { | |||
dataTable.searchPanes.rebuildPane(paneIndex, true); | |||
}); | }); | ||
}); | |||
dataTable.rows.add(data).draw(); | |||
$(table).removeClass("dt-loading").removeAttr("aria-busy").show(); | |||
dataTable.searchPanes.rebuildPane(); | |||
var filterPanel = setupFilterOffCanvas( | |||
dataTable, | |||
table, | |||
titles, | |||
hiddenColumnIndexes, | |||
); | |||
suppressSearchPaneBorders(filterPanel); | |||
dataTable.columns.adjust().draw(false); | |||
}); | }); | ||
}); | |||
$(selectors.join(",")).each(function () { | |||
var table = this; | |||
waitForTableToSettle(table, function () { | |||
if ($.fn.dataTable.isDataTable(table)) { | |||
return; | |||
} | |||
var titles = [ | |||
getFirstColumnName(table), | |||
"Developer", | |||
"Publisher", | |||
"Genre", | |||
"Platform", | |||
"Mode", | |||
"Released", | |||
"Category", | |||
]; | |||
var hiddenColumnIndexes = getHiddenColumnIndexes(table, titles); | |||
var paneIndexes = [1, 2, 3, 4, 5, 7].filter(function (paneIndex) { | |||
return !hiddenColumnIndexes.has(paneIndex); | |||
}); | |||
var data = getTableData(table, titles.length); | |||
buildTableLayout(table, titles); | |||
var dataTable = new DataTable(table, { | |||
columns: [ | |||
{ | |||
title: titles[0], | |||
visible: !hiddenColumnIndexes.has(0), | |||
render: function (data, type) { | |||
if (type === "display") { | |||
return ( | |||
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' + | |||
data + | |||
"</span>" | |||
); | |||
} | |||
return data; | |||
}, | |||
searchPanes: { | |||
show: false, | |||
}, | |||
}, | |||
{ | |||
title: "Developer", | |||
visible: !hiddenColumnIndexes.has(1), | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(1), | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Publisher", | |||
visible: !hiddenColumnIndexes.has(2), | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(2), | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Genre", | |||
visible: !hiddenColumnIndexes.has(3), | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(3), | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Platform", | |||
visible: !hiddenColumnIndexes.has(4), | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(4), | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Mode", | |||
visible: !hiddenColumnIndexes.has(5), | |||
render: { | |||
display: function (data) { | |||
return getGameModeDisplayValues(data).join(", "); | |||
}, | |||
sp: function (data) { | |||
return getGameModeValues(data); | |||
}, | |||
}, | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(5), | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
{ | |||
title: "Released", | |||
visible: !hiddenColumnIndexes.has(6), | |||
searchPanes: { | |||
show: !hiddenColumnIndexes.has(6), | |||
}, | |||
}, | |||
{ | |||
title: "Category", | |||
visible: false, | |||
render: getPlainSearchPaneRender(), | |||
searchPanes: { | |||
show: true, | |||
orthogonal: "sp", | |||
}, | |||
}, | |||
], | |||
layout: { | |||
topStart: null, | |||
topEnd: null, | |||
top2: { | |||
searchPanes: { | |||
cascadePanes: true, | |||
dtOpts: { | |||
scroller: false, | |||
}, | |||
initCollapsed: true, | |||
layout: "columns-1", | |||
columns: paneIndexes, | |||
}, | |||
}, | |||
top1: { | |||
search: { | |||
className: "mx-0 w-100", | |||
}, | |||
}, | |||
}, | |||
order: [[0, "asc"]], | |||
language: { | |||
search: "", | |||
searchPlaceholder: "Search", | |||
searchPanes: { | |||
title: { | |||
_: "Filters: %d", | |||
0: "", | |||
}, | |||
}, | |||
}, | |||
paging: false, | |||
searchDelay: 400, | |||
}); | |||
var lastGlobalSearch = dataTable.search(); | |||
dataTable.on("draw.dt", function () { | |||
var globalSearch = dataTable.search(); | |||
openFellowshipLinksInNewTab(dataTable); | |||
if (globalSearch === lastGlobalSearch) { | |||
return; | |||
} | |||
lastGlobalSearch = globalSearch; | |||
paneIndexes.forEach(function (paneIndex) { | |||
dataTable.searchPanes.rebuildPane(paneIndex, true); | |||
}); | |||
}); | |||
var filterPanel; | |||
dataTable.rows.add(data).draw(); | |||
$(table).removeClass("dt-loading").removeAttr("aria-busy").show(); | |||
dataTable.searchPanes.rebuildPane(); | |||
filterPanel = setupFilterOffCanvas( | |||
dataTable, | |||
table, | |||
titles, | |||
hiddenColumnIndexes, | |||
); | |||
suppressSearchPaneBorders(filterPanel); | |||
dataTable.columns.adjust().draw(false); | |||
}); | }); | ||
}); | |||
} | } | ||
Latest revision as of 07:54, 12 August 2026
var selectors = ["#games > table"];
var seriesSelectors = ["#series > table"];
var assetSelectors = ["#assets > table"];
var playerSelectors = ["#players table"];
var clanSelectors = ["#clans table"];
var allSelectors = selectors.concat(
seriesSelectors,
assetSelectors,
playerSelectors,
clanSelectors,
);
var found = false;
for (var i = 0; i < allSelectors.length; i++) {
if (document.querySelector(allSelectors[i])) {
found = true;
break;
}
}
/* ------------- DataTables Loader and initialisation ---------------- */
if (found === true) {
/* Load CSS. */
const dtsource =
"https://cdn.datatables.net/v/dt/moment-2.29.4/dt-2.3.7/b-3.2.6/b-colvis-3.2.6/b-html5-3.2.6/b-print-3.2.6/cc-1.2.0/date-1.6.3/fh-4.0.5/r-3.0.8/sc-2.4.3/sb-1.8.4/sp-2.3.5/sl-3.1.3/sr-1.4.3/datatables.min.css";
if (!document.querySelector('link[href*="datatables.min.css"]')) {
$("<link/>", { rel: "stylesheet", href: dtsource }).appendTo("head");
}
if (!document.querySelector("#datatable-styles")) {
$("<style/>", {
id: "datatable-styles",
text: /* css */ `
table.dataTable > tbody > tr > td {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
table.dataTable,
table.dataTable * {
border: 0 !important;
}
.dt-loading {
display: block !important;
min-height: 4rem;
position: relative;
}
.dt-loading > * {
display: none !important;
}
.dt-loading::before {
animation: dt-loading 0.8s linear infinite;
border: 0.25rem solid #dee2e6;
border-radius: 50%;
border-top-color: #6c757d;
content: "";
height: 2rem;
left: calc(50% - 1rem);
position: absolute;
top: 1rem;
width: 2rem;
}
@keyframes dt-loading {
to {
transform: rotate(360deg);
}
}
div.dtsp-panesContainer div.dtsp-title {
padding: 2px 0 !important;
}
.dtsp-bordered,
.dtsp-bordered:hover,
.dtsp-searchPane,
.dtsp-searchPane:hover {
border: 0 !important;
}
.dtsp-topRow button:hover,
.dtsp-topRow input:hover,
.dtsp-topRow button:focus,
.dtsp-topRow input:focus,
.dtsp-topRow button,
.dtsp-topRow input {
margin: 0 !important;
}
.dtsp-paneButton.clearButton {
font-size: 2.5rem;
line-height: 0;
padding-bottom: 6px;
}
.dtsp-paneButton.clearButton:not(:disabled) {
color: #dc3545 !important;
font-size: 3rem;
}
.dtsp-topRow {
background-color: #1e2d59;
background-image: url(/w/extensions/wikivisor/images/embossed-leather-pattern.png);
background-blend-mode: darken;
background-size: cover;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.75);
}
.dtsp-caret {
align-items: end;
display: inline-flex !important;
font-size: 0 !important;
height: 1rem;
justify-content: center;
line-height: 1;
width: 1rem;
align-items: center;
}
div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret {
top: unset !important;
}
.dtsp-caret::before {
content: "\\2335";
font-size: 1rem;
line-height: 1;
}
[aria-expanded="true"] .dtsp-caret::before,
.dtsp-caret-u::before,
.dtsp-caret-up::before {
content: "\\2303";
}
div.dtsp-searchPane div.dtsp-topRow div.dtsp-searchCont input.dtsp-search {
padding-left: 8px;
}
.dt-filter-toggle {
align-items: center;
background: var(--bs-primary, #0d6efd);
border: 0;
border-radius: 0.25rem;
color: #fff;
display: inline-flex;
flex: 0 0 auto;
font-weight: 600;
gap: 0.375rem;
line-height: 1.25;
padding: 0.5rem 0.75rem;
}
.dt-filter-toolbar {
align-items: center;
display: flex;
gap: 0.75rem;
justify-content: space-between;
margin-bottom: 0.75rem;
width: 100%;
}
.dt-filter-toolbar .dt-search {
align-items: center;
display: flex;
flex: 1 1 auto;
gap: 0.5rem;
justify-content: flex-end;
margin: 0 !important;
}
.dt-filter-toolbar .dt-search input {
margin-left: 0 !important;
max-width: 24rem;
width: 100%;
}
@media (max-width: 576px) {
.dt-filter-toolbar {
align-items: stretch;
flex-direction: column;
}
.dt-filter-toolbar .dt-filter-toggle,
.dt-filter-toolbar .dt-search,
.dt-filter-toolbar .dt-search input {
max-width: none;
width: 100%;
}
}
.dt-filter-toggle:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
outline: 0;
}
.dt-filter-toggle::before {
content: "\\2630";
font-size: 1rem;
line-height: 1;
}
.dt-filter-backdrop {
background: rgba(0, 0, 0, 0);
inset: 0;
opacity: 0;
pointer-events: none;
position: fixed;
transition: opacity 180ms ease;
z-index: 1040;
}
.dt-filter-backdrop.is-open {
opacity: 1;
pointer-events: auto;
}
.dt-filter-panel {
background-color: #11161b !important;
border-color: var(--bg-content);
border-style: solid;
border-width: 0.5rem;
border-bottom-right-radius: 0.625rem;
border-top-right-radius: 0.625rem;
color: #fff;
display: flex;
flex-direction: column;
height: 100vh;
left: 0;
max-width: min(92vw, 20rem);
overflow: hidden;
position: fixed;
top: 0;
transform: translateX(-100%);
transition: transform 220ms ease;
width: 20rem;
z-index: 1050;
}
.dt-filter-panel.is-open {
box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
transform: translateX(0);
}
.dt-filter-panel__header {
align-items: center;
border-bottom: 1px solid var(--accent);
display: flex;
flex: 0 0 auto;
justify-content: space-between;
padding: 0.75rem 1rem;
}
.dt-filter-panel__title {
font-size: 1.4rem;
font-weight: 600;
margin: 0;
border-bottom: 0;
}
.dt-filter-panel__close {
background: transparent;
border: 0;
color: rgb(124, 124, 124);
font-size: 2.5rem;
line-height: 1;
padding: 0.125rem 0.375rem 6px 0.375rem;
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
}
.dt-filter-panel__body {
flex: 1 1 auto;
overflow: auto;
padding: 0.75rem;
}
.dt-filter-panel__body .dtsp-panesContainer {
margin: 0 !important;
}
.dt-filter-panel__body .dtsp-searchPanes {
display: block !important;
}
.dt-filter-panel__body .dtsp-searchPane {
margin-bottom: 0.75rem !important;
width: 100% !important;
}
body.dt-filter-panel-open {
overflow: hidden;
}
/*
* The series table uses SearchBuilder as a single, fixed selector.
* Keep only the criterion's value dropdown visible.
*/
#series .dt-container .dtsb-titleRow,
#series .dt-container .dtsb-logicContainer,
#series .dt-container .dtsb-data,
#series .dt-container .dtsb-condition,
#series .dt-container .dtsb-add,
#series .dt-container .dtsb-clearAll,
#series .dt-container .dtsb-clearGroup,
#series .dt-container .dtsb-search,
#series .dt-container .dtsb-buttonContainer,
#series .dt-container .dtsb-joiner,
#series .dt-container .dtsb-delete,
#series .dt-container .dtsb-left,
#series .dt-container .dtsb-right {
display: none !important;
}
#series .dt-container .dtsb-searchBuilder > :not(.dtsb-group),
#series .dt-container .dtsb-group > :not(.dtsb-criteria),
#series .dt-container .dtsb-criteria > :not(.dtsb-inputCont),
#series .dt-container .dtsb-inputCont > :not(select.dtsb-value) {
display: none !important;
}
#series .dt-container .dtsb-searchBuilder,
#series .dt-container .dtsb-group,
#series .dt-container .dtsb-criteria {
border: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
#series .dt-container .dtsb-criteria {
display: block !important;
}
#series .dt-container .dtsb-inputCont {
display: block !important;
width: min(100%, 24rem) !important;
}
#series .dt-container .dtsb-inputCont > :not(.dtsb-value) {
display: none !important;
}
#series .dt-container .dtsb-value {
display: block !important;
width: 100% !important;
}
aside.is-open .sidebar-drawer {
display: inline-block;
right: 0;
left: 284px;
border: 0;
z-index: 3;
}
aside.dt-filter-panel {
background-image: url(/w/images/d/de/File_Icon-Guardians-semitransparent.png);
background-position: bottom;
background-repeat: no-repeat;
}
.dt-search label {
display: none;
}
#players tbody tr td:last-child {
color: var(--bg-shade-6);
}
#players .player-games {
align-items: baseline;
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.5rem;
position: relative;
}
#players .player-games__more {
display: inline-block;
position: relative;
margin-left: auto;
}
#players .player-games__toggle {
background: none;
color: var(--bs-link-color, #6ea8fe);
cursor: pointer;
font-size: 0.875em;
list-style: none;
padding: 0;
white-space: nowrap;
}
#players .player-games__toggle::-webkit-details-marker {
display: none;
}
#players .player-games__toggle:hover,
#players .player-games__toggle:focus {
text-decoration: underline;
}
#players .player-games__popup {
background-color: var(--bg-shade-4, #11161b);
border: 1px solid rgba(255, 255, 255, 0.125) !important;
border-radius: 0.25rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.35);
color: var(--bs-body-color, #fff);
display: flex;
flex-direction: column;
gap: 0.25rem;
left: 0;
line-height: 1.5;
min-width: max-content;
padding: 0.5rem 0.75rem;
position: absolute;
top: calc(100% + 0.25rem);
white-space: nowrap;
z-index: 20;
}
.player-games__popup::before {
display: inline-block;
width: 6px;
height: 6px;
content: '';
border-left: 3px solid rgba(255, 255, 255, .25);
border-top: 3px solid rgba(255, 255, 255, .25);
border-right: 3px solid transparent;
border-bottom: 3px solid transparent;
transform: rotate(45deg);
margin-top: -12px;
}
`,
}).appendTo("head");
}
$(allSelectors.join(",")).addClass("dt-loading").attr("aria-busy", "true");
/* Load JavaScript. */
$.when(
mw.loader.getScript(
"https://cdn.datatables.net/v/dt/moment-2.29.4/dt-2.3.7/b-3.2.6/b-colvis-3.2.6/b-html5-3.2.6/b-print-3.2.6/cc-1.2.0/date-1.6.3/fh-4.0.5/r-3.0.8/sc-2.4.3/sb-1.8.4/sp-2.3.5/sl-3.1.3/sr-1.4.3/datatables.min.js",
),
).then(
() => {
window.datatablesLoaded = true;
initDataTable();
},
(e) => mw.log.error(e.message),
);
}
/* ------------- DataTables configuration ---------------------------- */
function initDataTable() {
function getDataTableColumnCount(table) {
var headerRow =
table.tHead && table.tHead.rows.length
? table.tHead.rows[table.tHead.rows.length - 1]
: null;
if (headerRow) {
return headerRow.cells.length;
}
return table.rows.length ? table.rows[0].cells.length : 0;
}
function normaliseDataTableRows(table, expectedColumnCount) {
$("tbody tr", table).each(function () {
var row = this;
var cellCount = row.cells.length;
if (cellCount === 1 && row.cells[0].colSpan > 1) {
return;
}
while (cellCount < expectedColumnCount) {
row.appendChild(document.createElement("td"));
cellCount++;
}
});
}
function waitForTableToSettle(table, callback) {
var settleTimer;
var timeoutTimer;
var observer;
var settled = false;
function finish() {
if (settled) return;
settled = true;
clearTimeout(settleTimer);
clearTimeout(timeoutTimer);
if (observer) {
observer.disconnect();
}
callback();
}
function scheduleFinish() {
clearTimeout(settleTimer);
settleTimer = setTimeout(finish, 500);
}
if (window.MutationObserver) {
observer = new MutationObserver(scheduleFinish);
observer.observe(table.tBodies[0] || table, {
childList: true,
subtree: true,
});
}
timeoutTimer = setTimeout(finish, 30000);
scheduleFinish();
}
function getAcceptsValues(html) {
return (html || "")
.split(/<br\s*\/?>/i)
.map((value) => value.trim())
.filter(Boolean);
}
function getPlainAcceptsValues(html) {
return getAcceptsValues(html)
.map(function (value) {
var container = document.createElement("div");
container.innerHTML = value;
return (container.textContent || "").trim();
})
.filter(Boolean);
}
function renderPlayerGames(html) {
var games = getAcceptsValues(html);
if (games.length < 2) {
return games[0] || "";
}
return (
'<div class="player-games">' +
'<span class="player-games__first">' +
games[0] +
"</span>" +
'<details class="player-games__more">' +
'<summary class="player-games__toggle">+' +
(games.length - 1) +
" more</summary>" +
'<div class="player-games__popup" role="group" aria-label="Other games">' +
games
.slice(1)
.map(function (game) {
return "<div>" + game + "</div>";
})
.join("") +
"</div></details></div>"
);
}
function getPlainSearchPaneRender() {
return {
display: function (data) {
return data;
},
sp: function (data) {
return getPlainAcceptsValues(data);
},
};
}
function getGameModeText(html) {
var container = document.createElement("div");
var values = [];
container.innerHTML = html || "";
values.push(container.textContent || "");
container.querySelectorAll("*").forEach(function (element) {
["alt", "title", "aria-label", "class", "src"].forEach(
function (attribute) {
var value = element.getAttribute(attribute);
if (value) {
values.push(value);
}
},
);
});
return values.join(" ");
}
function getGameModeValue(html) {
var text = getGameModeText(html).toLowerCase().replace(/[_-]+/g, " ");
if (/\b(single\s*player|singleplayer|solo|sp)\b/.test(text)) {
return "SP";
}
if (/\b(multi\s*player|multiplayer|mp)\b/.test(text)) {
return "MP";
}
if (/\b(co\s*op|coop|cooperative)\b/.test(text)) {
return "Coop";
}
return "";
}
function getGameModeValues(html) {
var selectedModes = new Set(
getAcceptsValues(html).map(getGameModeValue).filter(Boolean),
);
return ["SP", "MP", "Coop"].filter(function (mode) {
return selectedModes.has(mode);
});
}
function getGameModeDisplayValue(html, mode) {
var container = document.createElement("div");
var link;
container.innerHTML = html || "";
link = container.querySelector("a[href]");
if (!link) {
return mode;
}
link = link.cloneNode(false);
link.textContent = mode;
return link.outerHTML;
}
function getGameModeDisplayValues(html) {
var valuesByMode = {};
getAcceptsValues(html).forEach(function (value) {
var mode = getGameModeValue(value);
if (mode && !valuesByMode[mode]) {
valuesByMode[mode] = getGameModeDisplayValue(value, mode);
}
});
return ["SP", "MP", "Coop"]
.filter(function (mode) {
return Object.prototype.hasOwnProperty.call(valuesByMode, mode);
})
.map(function (mode) {
return valuesByMode[mode];
});
}
function normaliseColumnName(value) {
return String(value || "")
.trim()
.toLowerCase()
.replace(/[\s_-]+/g, "");
}
function getTableConfigElement(table) {
return (
table.closest(
"[data-hidden-columns], [data-hide-columns], [data-dt-hidden-columns], [data-dt-hide-columns], [data-pane-columns], [data-search-pane-columns], [data-dt-pane-columns], [data-first-column-name], [data-first-column], [data-page-column-name], [data-dt-first-column-name]",
) || table.parentElement
);
}
function getFirstColumnName(table) {
var configElement = getTableConfigElement(table);
var columnName = configElement
? configElement.getAttribute("data-first-column-name") ||
configElement.getAttribute("data-first-column") ||
configElement.getAttribute("data-page-column-name") ||
configElement.getAttribute("data-dt-first-column-name")
: "";
return String(columnName || "Page").trim() || "Page";
}
function getHiddenColumnIndexes(table, titles) {
var configElement = getTableConfigElement(table);
var rawValue = configElement
? configElement.getAttribute("data-hidden-columns") ||
configElement.getAttribute("data-hide-columns") ||
configElement.getAttribute("data-dt-hidden-columns") ||
configElement.getAttribute("data-dt-hide-columns")
: "";
var titleIndexes = {};
var hiddenIndexes = new Set();
titles.forEach(function (title, index) {
titleIndexes[normaliseColumnName(title)] = index;
});
String(rawValue || "")
.split(/[,\|]/)
.map(function (value) {
return value.trim();
})
.filter(Boolean)
.forEach(function (value) {
var numberValue = Number(value);
var normalisedValue = normaliseColumnName(value);
if (
Number.isInteger(numberValue) &&
numberValue >= 0 &&
numberValue < titles.length
) {
hiddenIndexes.add(numberValue);
return;
}
if (
Object.prototype.hasOwnProperty.call(titleIndexes, normalisedValue)
) {
hiddenIndexes.add(titleIndexes[normalisedValue]);
}
});
return hiddenIndexes;
}
function getPaneColumnIndexes(table, titles, fallbackIndexes) {
var configElement = getTableConfigElement(table);
var attributes = [
"data-pane-columns",
"data-search-pane-columns",
"data-dt-pane-columns",
];
var configuredAttribute = attributes.find(function (attribute) {
return configElement && configElement.hasAttribute(attribute);
});
var titleIndexes = {};
var paneIndexes = [];
if (!configuredAttribute) {
return fallbackIndexes.slice();
}
titles.forEach(function (title, index) {
titleIndexes[normaliseColumnName(title)] = index;
});
String(configElement.getAttribute(configuredAttribute) || "")
.split(/[,\|]/)
.map(function (value) {
return value.trim();
})
.filter(Boolean)
.forEach(function (value) {
var numberValue = Number(value);
var normalisedValue = normaliseColumnName(value);
var index = Number.isInteger(numberValue)
? numberValue
: titleIndexes[normalisedValue];
if (
Number.isInteger(index) &&
index >= 0 &&
index < titles.length &&
paneIndexes.indexOf(index) === -1
) {
paneIndexes.push(index);
}
});
return paneIndexes;
}
function getTableData(table, columnCount) {
return Array.from(table.tBodies[0] ? table.tBodies[0].rows : [])
.filter((row) => !(row.cells.length === 1 && row.cells[0].colSpan > 1))
.map((row) => {
var data = Array.from(row.cells, (cell) => cell.innerHTML.trim());
while (data.length < columnCount) {
data.push("");
}
return data.slice(0, columnCount);
});
}
function buildTableLayout(table, titles) {
var headerRow = document.createElement("tr");
var tableHead = document.createElement("thead");
titles.forEach((title) => {
var headerCell = document.createElement("th");
headerCell.textContent = title;
headerRow.appendChild(headerCell);
});
table.replaceChildren(tableHead, document.createElement("tbody"));
tableHead.appendChild(headerRow);
}
function suppressSearchPaneBorders(container) {
if (!container) {
return;
}
function removeBorderClass() {
container.querySelectorAll(".dtsp-bordered").forEach(function (element) {
element.classList.remove("dtsp-bordered");
});
}
removeBorderClass();
new MutationObserver(removeBorderClass).observe(container, {
attributes: true,
attributeFilter: ["class"],
childList: true,
subtree: true,
});
}
function openFellowshipLinksInNewTab(dataTable) {
dataTable
.column(0)
.nodes()
.toArray()
.forEach(function (cell) {
cell.querySelectorAll("a").forEach(function (link) {
link.target = "_blank";
});
});
}
function setupFilterToolbar(wrapper, toggle) {
var toolbar = document.createElement("div");
var search = wrapper.querySelector(".dt-search");
var searchRow = search ? search.closest(".dt-layout-row") : null;
toolbar.className = "dt-filter-toolbar";
if (searchRow && searchRow.parentNode) {
searchRow.parentNode.insertBefore(toolbar, searchRow);
} else {
wrapper.insertBefore(toolbar, wrapper.firstChild);
}
toolbar.appendChild(toggle);
if (search) {
toolbar.appendChild(search);
}
if (
searchRow &&
searchRow.parentNode &&
!searchRow.querySelector(".dt-search")
) {
searchRow.remove();
}
}
function getCurrentPageName() {
var pageName = "";
if (window.mw && mw.config) {
pageName = mw.config.get("wgTitle") || mw.config.get("wgPageName") || "";
}
if (!pageName) {
pageName =
(document.querySelector("#firstHeading") || {}).textContent ||
document.title ||
"";
}
return pageName.replace(/_/g, " ").trim();
}
function getFilterPanelTitle(titles, hiddenColumnIndexes) {
var pageName = getCurrentPageName();
if (hiddenColumnIndexes && hiddenColumnIndexes.size === 1) {
return titles[Array.from(hiddenColumnIndexes)[0]] + ": " + pageName;
}
return pageName;
}
function setupFilterOffCanvas(dataTable, table, titles, hiddenColumnIndexes) {
var wrapper = table.closest(".dt-container") || table.parentNode;
var panes = wrapper ? wrapper.querySelector(".dtsp-panesContainer") : null;
if (!wrapper || !panes) {
return null;
}
if (wrapper.querySelector(".dt-filter-toggle")) {
return document.getElementById(
wrapper
.querySelector(".dt-filter-toggle")
.getAttribute("aria-controls"),
);
}
var toggle = document.createElement("button");
var panel = document.createElement("aside");
var backdrop = document.createElement("div");
var drawer = document.createElement("button");
var header = document.createElement("div");
var title = document.createElement("h2");
var close = document.createElement("button");
var body = document.createElement("div");
var panelId = "dt-filter-panel-" + $(".dt-filter-panel").length;
var lastFocusedElement = null;
toggle.type = "button";
toggle.className = "dt-filter-toggle";
toggle.textContent = "Filters";
toggle.setAttribute("aria-controls", panelId);
toggle.setAttribute("aria-expanded", "false");
panel.id = panelId;
panel.className = "dt-filter-panel";
panel.setAttribute("aria-hidden", "true");
panel.setAttribute("aria-label", "Data filters");
backdrop.className = "dt-filter-backdrop";
drawer.className = "sidebar-drawer";
drawer.setAttribute("type", "button");
drawer.setAttribute("tabindex", "0");
header.className = "dt-filter-panel__header";
title.className = "dt-filter-panel__title";
title.textContent = getFilterPanelTitle(titles, hiddenColumnIndexes);
close.type = "button";
close.className = "dt-filter-panel__close";
close.setAttribute("aria-label", "Close filters");
close.innerHTML = "×";
body.className = "dt-filter-panel__body";
header.append(title, close);
body.appendChild(panes);
panel.append(drawer, header, body);
setupFilterToolbar(wrapper, toggle);
document.body.append(backdrop, panel);
function openPanel() {
lastFocusedElement = document.activeElement;
panel.classList.add("is-open");
backdrop.classList.add("is-open");
document.body.classList.add("dt-filter-panel-open");
panel.setAttribute("aria-hidden", "false");
toggle.setAttribute("aria-expanded", "true");
close.focus();
dataTable.columns.adjust();
}
function closePanel() {
panel.classList.remove("is-open");
backdrop.classList.remove("is-open");
document.body.classList.remove("dt-filter-panel-open");
panel.setAttribute("aria-hidden", "true");
toggle.setAttribute("aria-expanded", "false");
if (lastFocusedElement && document.contains(lastFocusedElement)) {
lastFocusedElement.focus();
}
dataTable.columns.adjust();
}
toggle.addEventListener("click", openPanel);
close.addEventListener("click", closePanel);
drawer.addEventListener("click", closePanel);
backdrop.addEventListener("click", closePanel);
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && panel.classList.contains("is-open")) {
closePanel();
}
});
return panel;
}
$(".dataTable")
.not(allSelectors.join(","))
.each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
normaliseDataTableRows(table, getDataTableColumnCount(table));
$(table).DataTable({
language: {
search: "",
searchPlaceholder: "Search",
searchPanes: {
title: {
_: "Filters: %d",
0: "",
},
},
},
retrieve: true,
order: [[0, "asc"]],
pageLength: 1000,
});
});
});
$(seriesSelectors.join(",")).each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
var titles = ["Series", "Games"];
/*
* The source table is emitted as Games, Series.
* DataTables uses Series, Games, so Series is column 0.
*/
var data = getTableData(table, titles.length).map(function (row) {
return {
series: row[1],
games: row[0],
};
});
normaliseDataTableRows(table, titles.length);
buildTableLayout(table, titles);
try {
var dataTable = new DataTable(table, {
data: data,
columns: [
{
data: "games",
name: "Games",
title: "Game",
},
{
data: "series",
name: "Series",
title: "Series",
visible: false,
searchable: true,
type: "string",
},
],
layout: {
topStart: null,
topEnd: null,
top1: {
searchBuilder: {
columns: [1],
preDefined: {
logic: "AND",
criteria: [
{
data: "series",
origData: "series",
condition: "=",
type: "string",
},
],
},
},
},
top2: {},
},
language: {
searchBuilder: {
data: "Series",
value: "Select a series",
},
},
order: [[0, "asc"]],
paging: false,
initComplete: function () {
var api = this.api();
var container = $(api.table().container());
function emptySeriesCriterion() {
return {
logic: "AND",
criteria: [
{
data: "series",
origData: "series",
condition: "=",
type: "string",
},
],
};
}
function enableAllSeriesOption() {
var valueSelect = container.find(".dtsb-value").first();
var emptyOption = valueSelect
.find('option[value="--valueTitle--"]')
.first();
emptyOption
.prop("disabled", false)
.prop("hidden", false)
.text("All series");
}
container[0].addEventListener(
"change",
function (event) {
var select = event.target;
if (
!select.matches(".dtsb-value") ||
select.value !== "--valueTitle--"
) {
return;
}
/*
* The placeholder has no SearchBuilder value.
* Stop its listener before it calls toString()
* on undefined, then reset the criterion here.
*/
event.stopImmediatePropagation();
api.searchBuilder.rebuild(emptySeriesCriterion(), false);
api.draw(false);
setTimeout(enableAllSeriesOption, 0);
},
true,
);
setTimeout(enableAllSeriesOption, 0);
},
});
dataTable.columns.adjust().draw(false);
} catch (error) {
mw.log.error("Could not initialise the Series DataTable:", error);
} finally {
$(table).removeClass("dt-loading").removeAttr("aria-busy").show();
}
});
});
$(assetSelectors.join(",")).each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
var titles = [
"Asset",
"Type",
"Subtype",
"Mode",
"Developer",
"Released",
"Status",
"Description",
"Version",
"Files",
"Game",
];
var paneIndexes = [1, 2, 3, 4, 5, 6, 8];
if (mw.config.get("wgPageName") === "Mods") {
paneIndexes = [10, 1, 2, 3, 4, 5, 6, 8];
}
var hiddenColumns = [2, 3, 5, 6, 7, 8, 10];
var data = getTableData(table, titles.length);
buildTableLayout(table, titles);
var dataTable = new DataTable(table, {
columns: titles.map(function (title, index) {
var column = {
title: title,
visible: hiddenColumns.indexOf(index) === -1,
searchPanes: {
show: paneIndexes.indexOf(index) !== -1,
},
};
if (index === 0) {
column.render = function (cellData, type) {
if (type === "display") {
return (
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
cellData +
"</span>"
);
}
return cellData;
};
}
if ([1, 2, 3, 4, 6, 8, 10].indexOf(index) !== -1) {
column.render = {
display: function (cellData) {
if (index === 4) {
return (
'<span style="white-space: nowrap">' +
getAcceptsValues(cellData).join(", ") +
"</span>"
);
}
return cellData;
},
sp: function (cellData) {
return getPlainAcceptsValues(cellData);
},
};
column.searchPanes.orthogonal = "sp";
}
if (index === 9) {
column.render = function (cellData, type) {
if (type !== "display") {
return cellData;
}
var wrapper = document.createElement("div");
wrapper.innerHTML = cellData;
var links = Array.from(wrapper.querySelectorAll("a"));
if (!links.length) {
return "";
}
var downloads = document.createElement("div");
links.forEach(function (sourceLink, linkIndex) {
var link = sourceLink.cloneNode(true);
link.textContent =
links.length === 1
? "Download"
: "Download " + (linkIndex + 1);
link.target = "_blank";
link.rel = "noopener noreferrer nofollow";
downloads.appendChild(link);
if (linkIndex < links.length - 1) {
downloads.appendChild(document.createElement("br"));
}
});
return downloads.innerHTML;
};
}
return column;
}),
layout: {
topStart: null,
topEnd: null,
top2: {
searchPanes: {
cascadePanes: true,
dtOpts: {
scroller: false,
},
initCollapsed: true,
layout: "columns-1",
columns: paneIndexes,
},
},
top1: {
search: {
className: "mx-0 w-100",
},
},
},
order: [[0, "asc"]],
language: {
search: "",
searchPlaceholder: "Search assets",
searchPanes: {
title: {
_: "Filters: %d",
0: "",
},
},
},
paging: false,
searchDelay: 400,
});
var lastGlobalSearch = dataTable.search();
dataTable.on("draw.dt", function () {
var globalSearch = dataTable.search();
openFellowshipLinksInNewTab(dataTable);
if (globalSearch === lastGlobalSearch) {
return;
}
lastGlobalSearch = globalSearch;
paneIndexes.forEach(function (paneIndex) {
dataTable.searchPanes.rebuildPane(paneIndex, true);
});
});
dataTable.rows.add(data).draw();
$(table).removeClass("dt-loading").removeAttr("aria-busy").show();
dataTable.searchPanes.rebuildPane();
var filterPanel = setupFilterOffCanvas(
dataTable,
table,
titles,
new Set(),
);
suppressSearchPaneBorders(filterPanel);
dataTable.columns.adjust().draw(false);
});
});
$(clanSelectors.join(",")).each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
var titles = ["Clan", "Games", "Links", "Description", "Status"];
var paneIndexes = [1, 4];
var clanColorClasses = Array.from(
table.tBodies[0] ? table.tBodies[0].rows : [],
)
.filter(function (row) {
return !(row.cells.length === 1 && row.cells[0].colSpan > 1);
})
.map(function (row) {
return row.cells[0] ? row.cells[0].className : "";
});
var data = getTableData(table, titles.length);
buildTableLayout(table, titles);
var dataTable = new DataTable(table, {
data: data,
columns: [
{
title: "Clan",
createdCell: function (cell, cellData, rowData, rowIndex) {
var colorClasses = clanColorClasses[rowIndex];
if (!colorClasses) {
return;
}
colorClasses
.split(/\s+/)
.filter(Boolean)
.forEach(function (className) {
cell.classList.add(className);
});
},
render: function (cellData, type) {
if (type === "display") {
return (
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
cellData +
"</span>"
);
}
return cellData;
},
searchPanes: {
show: false,
},
},
{
title: "Games",
visible: false,
render: getPlainSearchPaneRender(),
searchPanes: {
show: true,
orthogonal: "sp",
},
},
{
title: "Links",
render: function (cellData, type) {
if (type !== "display") {
return cellData;
}
var container = document.createElement("div");
container.innerHTML = cellData || "";
container
.querySelectorAll("a.external[href]")
.forEach(function (link) {
var hostname;
try {
hostname = new URL(link.href, window.location.href)
.hostname;
} catch (error) {
hostname = "";
}
if (hostname) {
link.textContent = hostname.replace(/^www\./i, "");
}
link.target = "_blank";
link.rel = "noopener noreferrer nofollow";
});
return container.innerHTML;
},
searchPanes: {
show: false,
},
},
{
title: "Description",
searchPanes: {
show: false,
},
},
{
title: "Status",
visible: false,
render: getPlainSearchPaneRender(),
searchPanes: {
show: true,
orthogonal: "sp",
},
},
],
layout: {
topStart: null,
topEnd: null,
top2: {
searchPanes: {
cascadePanes: true,
dtOpts: {
scroller: false,
},
initCollapsed: true,
layout: "columns-1",
columns: paneIndexes,
},
},
top1: {
search: {
className: "mx-0 w-100",
},
},
},
order: [[0, "asc"]],
language: {
search: "",
searchPlaceholder: "Search clans",
searchPanes: {
title: {
_: "Filters: %d",
0: "",
},
},
},
paging: false,
searchDelay: 400,
});
var lastGlobalSearch = dataTable.search();
dataTable.on("draw.dt", function () {
var globalSearch = dataTable.search();
openFellowshipLinksInNewTab(dataTable);
if (globalSearch === lastGlobalSearch) {
return;
}
lastGlobalSearch = globalSearch;
paneIndexes.forEach(function (paneIndex) {
dataTable.searchPanes.rebuildPane(paneIndex, true);
});
});
$(table).removeClass("dt-loading").removeAttr("aria-busy").show();
dataTable.searchPanes.rebuildPane();
var filterPanel = setupFilterOffCanvas(
dataTable,
table,
titles,
new Set(),
);
suppressSearchPaneBorders(filterPanel);
dataTable.columns.adjust().draw(false);
});
});
$(playerSelectors.join(",")).each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
var fallbackTitles = [
"Player",
"Games",
"Clans",
"Roles",
"Clan (Role)",
"Status",
];
var headerCells =
table.tHead && table.tHead.rows.length
? Array.from(table.tHead.rows[table.tHead.rows.length - 1].cells)
: [];
var titles = fallbackTitles.map(function (fallbackTitle, index) {
var title = headerCells[index]
? headerCells[index].textContent.trim()
: "";
return title || fallbackTitle;
});
var hiddenColumnIndexes = new Set([2, 3, 5]);
var paneIndexes = getPaneColumnIndexes(table, titles, [2, 3, 5]);
var playerColorClasses = Array.from(
table.tBodies[0] ? table.tBodies[0].rows : [],
)
.filter(function (row) {
return !(row.cells.length === 1 && row.cells[0].colSpan > 1);
})
.map(function (row) {
return row.cells[0] ? row.cells[0].className : "";
});
var data = getTableData(table, titles.length);
buildTableLayout(table, titles);
var dataTable = new DataTable(table, {
columns: titles.map(function (title, index) {
var column = {
title: title,
visible: !hiddenColumnIndexes.has(index),
searchPanes: {
show: paneIndexes.indexOf(index) !== -1,
},
};
if (index === 0) {
column.createdCell = function (cell, cellData, rowData, rowIndex) {
var colorClasses = playerColorClasses[rowIndex];
if (!colorClasses) {
return;
}
colorClasses
.split(/\s+/)
.filter(Boolean)
.forEach(function (className) {
cell.classList.add(className);
});
};
column.render = function (cellData, type) {
if (type === "display") {
return (
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
cellData +
"</span>"
);
}
return cellData;
};
} else if (index === 1) {
column.render = function (cellData, type) {
if (type === "display") {
return renderPlayerGames(cellData);
}
if (type === "sp") {
return getPlainAcceptsValues(cellData);
}
return cellData;
};
if (paneIndexes.indexOf(index) !== -1) {
column.searchPanes.orthogonal = "sp";
}
} else if (paneIndexes.indexOf(index) !== -1) {
column.render = {
display: function (cellData) {
return cellData;
},
sp: function (cellData) {
var container = document.createElement("div");
var linkedValues;
var blockValues;
container.innerHTML = cellData || "";
linkedValues = Array.from(
container.querySelectorAll("a"),
function (link) {
return link.textContent.trim();
},
).filter(Boolean);
if (linkedValues.length) {
return linkedValues;
}
blockValues = Array.from(
container.children,
function (element) {
return element.textContent.trim();
},
).filter(Boolean);
return blockValues.length
? blockValues
: getAcceptsValues(cellData);
},
};
column.searchPanes.orthogonal = "sp";
}
return column;
}),
layout: {
topStart: null,
topEnd: null,
top2: {
searchPanes: {
cascadePanes: true,
dtOpts: {
scroller: false,
},
initCollapsed: true,
layout: "columns-1",
columns: paneIndexes,
},
},
top1: {
search: {
className: "mx-0 w-100",
},
},
},
order: [[0, "asc"]],
language: {
search: "",
searchPlaceholder: "Search players",
searchPanes: {
title: {
_: "Filters: %d",
0: "",
},
},
},
paging: false,
searchDelay: 400,
});
var lastGlobalSearch = dataTable.search();
dataTable.on("draw.dt", function () {
var globalSearch = dataTable.search();
openFellowshipLinksInNewTab(dataTable);
if (globalSearch === lastGlobalSearch) {
return;
}
lastGlobalSearch = globalSearch;
paneIndexes.forEach(function (paneIndex) {
dataTable.searchPanes.rebuildPane(paneIndex, true);
});
});
dataTable.rows.add(data).draw();
$(table).removeClass("dt-loading").removeAttr("aria-busy").show();
dataTable.searchPanes.rebuildPane();
var filterPanel = setupFilterOffCanvas(
dataTable,
table,
titles,
hiddenColumnIndexes,
);
suppressSearchPaneBorders(filterPanel);
dataTable.columns.adjust().draw(false);
});
});
$(selectors.join(",")).each(function () {
var table = this;
waitForTableToSettle(table, function () {
if ($.fn.dataTable.isDataTable(table)) {
return;
}
var titles = [
getFirstColumnName(table),
"Developer",
"Publisher",
"Genre",
"Platform",
"Mode",
"Released",
"Category",
];
var hiddenColumnIndexes = getHiddenColumnIndexes(table, titles);
var paneIndexes = [1, 2, 3, 4, 5, 7].filter(function (paneIndex) {
return !hiddenColumnIndexes.has(paneIndex);
});
var data = getTableData(table, titles.length);
buildTableLayout(table, titles);
var dataTable = new DataTable(table, {
columns: [
{
title: titles[0],
visible: !hiddenColumnIndexes.has(0),
render: function (data, type) {
if (type === "display") {
return (
'<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
data +
"</span>"
);
}
return data;
},
searchPanes: {
show: false,
},
},
{
title: "Developer",
visible: !hiddenColumnIndexes.has(1),
render: getPlainSearchPaneRender(),
searchPanes: {
show: !hiddenColumnIndexes.has(1),
orthogonal: "sp",
},
},
{
title: "Publisher",
visible: !hiddenColumnIndexes.has(2),
render: getPlainSearchPaneRender(),
searchPanes: {
show: !hiddenColumnIndexes.has(2),
orthogonal: "sp",
},
},
{
title: "Genre",
visible: !hiddenColumnIndexes.has(3),
render: getPlainSearchPaneRender(),
searchPanes: {
show: !hiddenColumnIndexes.has(3),
orthogonal: "sp",
},
},
{
title: "Platform",
visible: !hiddenColumnIndexes.has(4),
render: getPlainSearchPaneRender(),
searchPanes: {
show: !hiddenColumnIndexes.has(4),
orthogonal: "sp",
},
},
{
title: "Mode",
visible: !hiddenColumnIndexes.has(5),
render: {
display: function (data) {
return getGameModeDisplayValues(data).join(", ");
},
sp: function (data) {
return getGameModeValues(data);
},
},
searchPanes: {
show: !hiddenColumnIndexes.has(5),
orthogonal: "sp",
},
},
{
title: "Released",
visible: !hiddenColumnIndexes.has(6),
searchPanes: {
show: !hiddenColumnIndexes.has(6),
},
},
{
title: "Category",
visible: false,
render: getPlainSearchPaneRender(),
searchPanes: {
show: true,
orthogonal: "sp",
},
},
],
layout: {
topStart: null,
topEnd: null,
top2: {
searchPanes: {
cascadePanes: true,
dtOpts: {
scroller: false,
},
initCollapsed: true,
layout: "columns-1",
columns: paneIndexes,
},
},
top1: {
search: {
className: "mx-0 w-100",
},
},
},
order: [[0, "asc"]],
language: {
search: "",
searchPlaceholder: "Search",
searchPanes: {
title: {
_: "Filters: %d",
0: "",
},
},
},
paging: false,
searchDelay: 400,
});
var lastGlobalSearch = dataTable.search();
dataTable.on("draw.dt", function () {
var globalSearch = dataTable.search();
openFellowshipLinksInNewTab(dataTable);
if (globalSearch === lastGlobalSearch) {
return;
}
lastGlobalSearch = globalSearch;
paneIndexes.forEach(function (paneIndex) {
dataTable.searchPanes.rebuildPane(paneIndex, true);
});
});
var filterPanel;
dataTable.rows.add(data).draw();
$(table).removeClass("dt-loading").removeAttr("aria-busy").show();
dataTable.searchPanes.rebuildPane();
filterPanel = setupFilterOffCanvas(
dataTable,
table,
titles,
hiddenColumnIndexes,
);
suppressSearchPaneBorders(filterPanel);
dataTable.columns.adjust().draw(false);
});
});
}
