PC Gaming Shelter
An archive dedicated to preserving PC Gaming history and more

MediaWiki:Datatables.js: Difference between revisions

From PC Gaming Shelter
No edit summary
No edit summary
 
(85 intermediate revisions by the same user not shown)
Line 1: Line 1:
var selectors = [
var selectors = ["#games > table"];
    "#games > table",
var seriesSelectors = ["#series > table"];
    "[data-datatable='games'] > table",
var assetSelectors = ["#assets > table"];
    "[data-table='games'] > table",
var playerSelectors = ["#players table"];
    "[data-table-type='games'] > table"
var clanSelectors = ["#clans table"];
];
var allSelectors = selectors.concat(
var seriesSelectors = [
  seriesSelectors,
    "#series > table",
  assetSelectors,
    "[data-datatable='series'] > table",
  playerSelectors,
    "[data-table='series'] > table",
  clanSelectors,
    "[data-table-type='series'] > table"
);
];
var allSelectors = selectors.concat(seriesSelectors);


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])) {
  if (document.querySelector(allSelectors[i])) {
        found = true;
    found = true;
        break;
    break;
    }
  }
}
}


Line 24: Line 22:


if (found === true) {
if (found === true) {
    /* Load CSS. */
  /* Load CSS. */
    const dtsource =
  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";
    "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"]')) {
  if (!document.querySelector('link[href*="datatables.min.css"]')) {
        $("<link/>", { rel: "stylesheet", href: dtsource }).appendTo("head");
    $("<link/>", { rel: "stylesheet", href: dtsource }).appendTo("head");
    }
  }


    if (!document.querySelector("#datatable-styles")) {
  if (!document.querySelector("#datatable-styles")) {
        $("<style/>", {
    $("<style/>", {
            id: "datatable-styles",
      id: "datatable-styles",
            text: `
      text: /* css */ `
           
         table.dataTable > tbody > tr > td {
         table.dataTable > tbody > tr > td {
           padding-top: 0.25rem;
           padding-top: 0.25rem;
Line 42: Line 39:
         }
         }


table.dataTable, table.dataTable * {
        table.dataTable,
  border: 0 !important;
        table.dataTable * {
}
          border: 0 !important;
        }
 
         .dt-loading {
         .dt-loading {
           display: block !important;
           display: block !important;
Line 74: Line 72:
           }
           }
         }
         }
          
 
div.dtsp-panesContainer div.dtsp-title {
         div.dtsp-panesContainer div.dtsp-title {
padding: 2px 0 !important;
          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:hover,
.dtsp-topRow button:focus, .dtsp-topRow input:focus,
        .dtsp-topRow input:hover,
.dtsp-topRow button, .dtsp-topRow input {
        .dtsp-topRow button:focus,
margin: 0 !important;
        .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;
          line-height: 0;
           padding-bottom: 6px;
           padding-bottom: 6px;
         }
         }
Line 98: Line 101:
         .dtsp-paneButton.clearButton:not(:disabled) {
         .dtsp-paneButton.clearButton:not(:disabled) {
           color: #dc3545 !important;
           color: #dc3545 !important;
  font-size: 3rem;
          font-size: 3rem;
         }
         }


.dtsp-topRow {
        .dtsp-topRow {
  background-color: #1e2d59;
          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 119: 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;      
          top: unset !important;
         }
         }


Line 135: 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;
          padding-left: 8px;
}
        }


         .dt-filter-toggle {
         .dt-filter-toggle {
Line 241: 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 264: 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 270: Line 273:
           width: 35px;
           width: 35px;
           height: 35px;
           height: 35px;
  display: flex;
          display: flex;
  align-items: center;
          align-items: center;
  justify-content: center;
          justify-content: center;
         }
         }


Line 302: Line 305:
         * Keep only the criterion's value dropdown visible.
         * Keep only the criterion's value dropdown visible.
         */
         */
         .dt-series-container .dtsb-titleRow,
         #series .dt-container .dtsb-titleRow,
         .dt-series-container .dtsb-logicContainer,
        #series .dt-container .dtsb-logicContainer,
         .dt-series-container .dtsb-data,
         #series .dt-container .dtsb-data,
         .dt-series-container .dtsb-condition,
        #series .dt-container .dtsb-condition,
         .dt-series-container .dtsb-add,
         #series .dt-container .dtsb-add,
         .dt-series-container .dtsb-clearAll,
        #series .dt-container .dtsb-clearAll,
         .dt-series-container .dtsb-delete,
         #series .dt-container .dtsb-clearGroup,
         .dt-series-container .dtsb-left,
        #series .dt-container .dtsb-search,
         .dt-series-container .dtsb-right {
         #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;
           display: none !important;
         }
         }


         .dt-series-container .dtsb-searchBuilder,
         #series .dt-container .dtsb-searchBuilder,
         .dt-series-container .dtsb-group,
         #series .dt-container .dtsb-group,
         .dt-series-container .dtsb-criteria {
         #series .dt-container .dtsb-criteria {
           border: 0 !important;
           border: 0 !important;
           margin: 0 !important;
           margin: 0 !important;
Line 322: Line 336:
         }
         }


         .dt-series-container .dtsb-criteria {
         #series .dt-container .dtsb-criteria {
           display: block !important;
           display: block !important;
         }
         }


         .dt-series-container .dtsb-inputCont {
         #series .dt-container .dtsb-inputCont {
           display: block !important;
           display: block !important;
           width: min(100%, 24rem) !important;
           width: min(100%, 24rem) !important;
         }
         }


         .dt-series-container .dtsb-inputCont > :not(.dtsb-value) {
         #series .dt-container .dtsb-inputCont > :not(.dtsb-value) {
           display: none !important;
           display: none !important;
         }
         }


         .dt-series-container .dtsb-value {
         #series .dt-container .dtsb-value {
           display: block !important;
           display: block !important;
           width: 100% !important;
           width: 100% !important;
         }
         }
 
aside.is-open .sidebar-drawer {
        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;
right: 0;
width: 6px;
left: 284px;
height: 6px;
border: 0;
content: '';
z-index: 3;
border-left: 3px solid rgba(255, 255, 255, .25);
}
border-top: 3px solid rgba(255, 255, 255, .25);
border-right: 3px solid transparent;
aside.dt-filter-panel {
border-bottom: 3px solid transparent;
background-image: url(/w/images/d/de/File_Icon-Guardians-semitransparent.png);
transform: rotate(45deg);
background-position: bottom;
margin-top: -12px;
background-repeat: no-repeat;
        }
}
       `,
       `,
        }).appendTo("head");
    }).appendTo("head");
    }
  }


    $(allSelectors.join(",")).addClass("dt-loading").attr("aria-busy", "true");
  $(allSelectors.join(",")).addClass("dt-loading").attr("aria-busy", "true");


    /* Load JavaScript. */
  /* Load JavaScript. */
    $.when(
  $.when(
        mw.loader.getScript(
    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",
      "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(
  ).then(
        () => {
    () => {
            window.datatablesLoaded = true;
      window.datatablesLoaded = true;
            initDataTable();
      initDataTable();
        },
    },
        (e) => mw.log.error(e.message),
    (e) => mw.log.error(e.message),
    );
  );
}
}


Line 376: Line 463:


function initDataTable() {
function initDataTable() {
    function getDataTableColumnCount(table) {
  function getDataTableColumnCount(table) {
        var headerRow =
    var headerRow =
            table.tHead && table.tHead.rows.length
      table.tHead && table.tHead.rows.length
                ? table.tHead.rows[table.tHead.rows.length - 1]
        ? table.tHead.rows[table.tHead.rows.length - 1]
                : null;
        : 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++;
      }
    });
  }


        if (headerRow) {
  function waitForTableToSettle(table, callback) {
            return headerRow.cells.length;
    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);
    }


        return table.rows.length ? table.rows[0].cells.length : 0;
    if (window.MutationObserver) {
      observer = new MutationObserver(scheduleFinish);
      observer.observe(table.tBodies[0] || table, {
        childList: true,
        subtree: true,
      });
     }
     }


     function normaliseDataTableRows(table, expectedColumnCount) {
     timeoutTimer = setTimeout(finish, 30000);
        $("tbody tr", table).each(function () {
    scheduleFinish();
            var row = this;
  }
            var cellCount = row.cells.length;
 
  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);
  }


            if (cellCount === 1 && row.cells[0].colSpan > 1) {
  function renderPlayerGames(html) {
                return;
    var games = getAcceptsValues(html);
            }


            while (cellCount < expectedColumnCount) {
    if (games.length < 2) {
                row.appendChild(document.createElement("td"));
      return games[0] || "";
                cellCount++;
            }
        });
     }
     }


     function waitForTableToSettle(table, callback) {
     return (
        var settleTimer;
      '<div class="player-games">' +
         var timeoutTimer;
      '<span class="player-games__first">' +
         var observer;
      games[0] +
        var settled = false;
      "</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 finish() {
  function getPlainSearchPaneRender() {
            if (settled) return;
    return {
      display: function (data) {
         return data;
      },
      sp: function (data) {
        return getPlainAcceptsValues(data);
      },
    };
  }


            settled = true;
  function getGameModeText(html) {
            clearTimeout(settleTimer);
    var container = document.createElement("div");
            clearTimeout(timeoutTimer);
    var values = [];


            if (observer) {
    container.innerHTML = html || "";
                observer.disconnect();
    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);
          }
        },
      );
    });


            callback();
    return values.join(" ");
        }
  }


        function scheduleFinish() {
  function getGameModeValue(html) {
            clearTimeout(settleTimer);
    var text = getGameModeText(html).toLowerCase().replace(/[_-]+/g, " ");
            settleTimer = setTimeout(finish, 500);
        }


        if (window.MutationObserver) {
    if (/\b(single\s*player|singleplayer|solo|sp)\b/.test(text)) {
            observer = new MutationObserver(scheduleFinish);
      return "SP";
            observer.observe(table.tBodies[0] || table, {
    }
                childList: true,
                subtree: true,
            });
        }


        timeoutTimer = setTimeout(finish, 30000);
    if (/\b(multi\s*player|multiplayer|mp)\b/.test(text)) {
        scheduleFinish();
      return "MP";
     }
     }


     function getAcceptsValues(html) {
     if (/\b(co\s*op|coop|cooperative)\b/.test(text)) {
        return (html || "")
      return "Coop";
            .split(/<br\s*\/?>/i)
            .map((value) => value.trim())
            .filter(Boolean);
     }
     }


     function getGameModeText(html) {
     return "";
        var container = document.createElement("div");
  }
        var values = [];
 
  function getGameModeValues(html) {
    var selectedModes = new Set(
      getAcceptsValues(html).map(getGameModeValue).filter(Boolean),
    );


        container.innerHTML = html || "";
    return ["SP", "MP", "Coop"].filter(function (mode) {
        values.push(container.textContent || "");
      return selectedModes.has(mode);
    });
  }


        container.querySelectorAll("*").forEach(function (element) {
  function getGameModeDisplayValue(html, mode) {
            ["alt", "title", "aria-label", "class", "src"].forEach(function (attribute) {
    var container = document.createElement("div");
                var value = element.getAttribute(attribute);
    var link;


                if (value) {
    container.innerHTML = html || "";
                    values.push(value);
    link = container.querySelector("a[href]");
                }
            });
        });


        return values.join(" ");
    if (!link) {
      return mode;
     }
     }


     function getGameModeValue(html) {
     link = link.cloneNode(false);
        var text = getGameModeText(html)
    link.textContent = mode;
            .toLowerCase()
            .replace(/[_-]+/g, " ");


         if (/\b(single\s*player|singleplayer|solo|sp)\b/.test(text)) {
    return link.outerHTML;
            return "SP";
  }
 
  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 (/\b(multi\s*player|multiplayer|mp)\b/.test(text)) {
         if (
            return "MP";
          Object.prototype.hasOwnProperty.call(titleIndexes, normalisedValue)
        ) {
          hiddenIndexes.add(titleIndexes[normalisedValue]);
         }
         }
      });
    return hiddenIndexes;
  }


        if (/\b(co\s*op|coop|cooperative)\b/.test(text)) {
  function getPaneColumnIndexes(table, titles, fallbackIndexes) {
            return "Coop";
    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 = [];


        return "";
    if (!configuredAttribute) {
      return fallbackIndexes.slice();
     }
     }


     function getGameModeValues(html) {
     titles.forEach(function (title, index) {
        var selectedModes = new Set(getAcceptsValues(html)
      titleIndexes[normaliseColumnName(title)] = index;
            .map(getGameModeValue)
    });
            .filter(Boolean));


        return ["SP", "MP", "Coop"].filter(function (mode) {
    String(configElement.getAttribute(configuredAttribute) || "")
            return selectedModes.has(mode);
      .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);
        }
      });


     function getGameModeDisplayValue(html, mode) {
     return paneIndexes;
        var container = document.createElement("div");
  }
        var link;


        container.innerHTML = html || "";
  function getTableData(table, columnCount) {
         link = container.querySelector("a[href]");
    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());


         if (!link) {
         while (data.length < columnCount) {
            return mode;
          data.push("");
         }
         }


         link = link.cloneNode(false);
         return data.slice(0, columnCount);
        link.textContent = mode;
      });
  }


        return link.outerHTML;
  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 getGameModeDisplayValues(html) {
     function removeBorderClass() {
         var valuesByMode = {};
      container.querySelectorAll(".dtsp-bordered").forEach(function (element) {
         element.classList.remove("dtsp-bordered");
      });
    }


        getAcceptsValues(html).forEach(function (value) {
    removeBorderClass();
            var mode = getGameModeValue(value);


            if (mode && !valuesByMode[mode]) {
    new MutationObserver(removeBorderClass).observe(container, {
                valuesByMode[mode] = getGameModeDisplayValue(value, mode);
      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";
         });
         });
      });
  }


        return ["SP", "MP", "Coop"]
  function setupFilterToolbar(wrapper, toggle) {
            .filter(function (mode) {
    var toolbar = document.createElement("div");
                return Object.prototype.hasOwnProperty.call(valuesByMode, mode);
    var search = wrapper.querySelector(".dt-search");
            })
    var searchRow = search ? search.closest(".dt-layout-row") : null;
            .map(function (mode) {
 
                return valuesByMode[mode];
    toolbar.className = "dt-filter-toolbar";
            });
 
    if (searchRow && searchRow.parentNode) {
      searchRow.parentNode.insertBefore(toolbar, searchRow);
    } else {
      wrapper.insertBefore(toolbar, wrapper.firstChild);
     }
     }


     function normaliseColumnName(value) {
     toolbar.appendChild(toggle);
        return String(value || "")
 
            .trim()
    if (search) {
            .toLowerCase()
      toolbar.appendChild(search);
            .replace(/[\s_-]+/g, "");
     }
     }


     function getTableConfigElement(table) {
     if (
        return table.closest(
      searchRow &&
            "[data-hidden-columns], [data-hide-columns], [data-dt-hidden-columns], [data-dt-hide-columns], [data-first-column-name], [data-first-column], [data-page-column-name], [data-dt-first-column-name]",
      searchRow.parentNode &&
        ) || table.parentElement;
      !searchRow.querySelector(".dt-search")
    ) {
      searchRow.remove();
     }
     }
  }


    function getFirstColumnName(table) {
  function getCurrentPageName() {
        var configElement = getTableConfigElement(table);
    var pageName = "";
        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";
    if (window.mw && mw.config) {
      pageName = mw.config.get("wgTitle") || mw.config.get("wgPageName") || "";
     }
     }


     function getHiddenColumnIndexes(table, titles) {
     if (!pageName) {
        var configElement = getTableConfigElement(table);
      pageName =
         var rawValue = configElement
         (document.querySelector("#firstHeading") || {}).textContent ||
            ? configElement.getAttribute("data-hidden-columns") ||
        document.title ||
            configElement.getAttribute("data-hide-columns") ||
        "";
            configElement.getAttribute("data-dt-hidden-columns") ||
    }
            configElement.getAttribute("data-dt-hide-columns")
 
            : "";
    return pageName.replace(/_/g, " ").trim();
        var titleIndexes = {};
  }
        var hiddenIndexes = new Set();
 
  function getFilterPanelTitle(titles, hiddenColumnIndexes) {
    var pageName = getCurrentPageName();


        titles.forEach(function (title, index) {
    if (hiddenColumnIndexes && hiddenColumnIndexes.size === 1) {
            titleIndexes[normaliseColumnName(title)] = index;
      return titles[Array.from(hiddenColumnIndexes)[0]] + ": " + pageName;
        });
    }


        String(rawValue || "")
    return pageName;
            .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) {
  function setupFilterOffCanvas(dataTable, table, titles, hiddenColumnIndexes) {
                    hiddenIndexes.add(numberValue);
    var wrapper = table.closest(".dt-container") || table.parentNode;
                    return;
    var panes = wrapper ? wrapper.querySelector(".dtsp-panesContainer") : null;
                }


                if (Object.prototype.hasOwnProperty.call(titleIndexes, normalisedValue)) {
    if (!wrapper || !panes) {
                    hiddenIndexes.add(titleIndexes[normalisedValue]);
      return null;
                }
    }
            });


         return hiddenIndexes;
    if (wrapper.querySelector(".dt-filter-toggle")) {
      return document.getElementById(
         wrapper
          .querySelector(".dt-filter-toggle")
          .getAttribute("aria-controls"),
      );
     }
     }


     function getTableData(table, columnCount) {
     var toggle = document.createElement("button");
        return Array.from(table.tBodies[0] ? table.tBodies[0].rows : [])
    var panel = document.createElement("aside");
            .filter((row) => !(row.cells.length === 1 && row.cells[0].colSpan > 1))
    var backdrop = document.createElement("div");
            .map((row) => {
    var drawer = document.createElement("button");
                var data = Array.from(row.cells, (cell) => cell.innerHTML.trim());
    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;


                while (data.length < columnCount) {
    toggle.type = "button";
                    data.push("");
    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 = "&times;";
 
    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);


                return data.slice(0, columnCount);
    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 buildTableLayout(table, titles) {
     function closePanel() {
        var headerRow = document.createElement("tr");
      panel.classList.remove("is-open");
        var tableHead = document.createElement("thead");
      backdrop.classList.remove("is-open");
      document.body.classList.remove("dt-filter-panel-open");
      panel.setAttribute("aria-hidden", "true");
      toggle.setAttribute("aria-expanded", "false");


        titles.forEach((title) => {
      if (lastFocusedElement && document.contains(lastFocusedElement)) {
            var headerCell = document.createElement("th");
        lastFocusedElement.focus();
            headerCell.textContent = title;
      }
            headerRow.appendChild(headerCell);
        });


        table.replaceChildren(tableHead, document.createElement("tbody"));
      dataTable.columns.adjust();
        tableHead.appendChild(headerRow);
     }
     }


     function suppressSearchPaneBorders(container) {
     toggle.addEventListener("click", openPanel);
        if (!container) {
    close.addEventListener("click", closePanel);
            return;
    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;


        function removeBorderClass() {
      waitForTableToSettle(table, function () {
            container.querySelectorAll(".dtsp-bordered").forEach(function (element) {
        if ($.fn.dataTable.isDataTable(table)) {
                element.classList.remove("dtsp-bordered");
          return;
            });
         }
         }


         removeBorderClass();
         normaliseDataTableRows(table, getDataTableColumnCount(table));


         new MutationObserver(removeBorderClass).observe(container, {
         $(table).DataTable({
             attributes: true,
          language: {
             attributeFilter: ["class"],
             search: "",
             childList: true,
             searchPlaceholder: "Search",
             subtree: true,
             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();


    function openFellowshipLinksInNewTab(dataTable) {
                api.searchBuilder.rebuild(emptySeriesCriterion(), false);
        dataTable
                api.draw(false);
            .column(0)
                 setTimeout(enableAllSeriesOption, 0);
            .nodes()
              },
            .toArray()
              true,
            .forEach(function (cell) {
             );
                 cell.querySelectorAll("a").forEach(function (link) {
                    link.target = "_blank";
                });
             });
    }


    function setupFilterToolbar(wrapper, toggle) {
            setTimeout(enableAllSeriesOption, 0);
        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";
         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();
      }
    });
  });


        if (searchRow && searchRow.parentNode) {
  $(assetSelectors.join(",")).each(function () {
            searchRow.parentNode.insertBefore(toolbar, searchRow);
    var table = this;
        } else {
            wrapper.insertBefore(toolbar, wrapper.firstChild);
        }


        toolbar.appendChild(toggle);
    waitForTableToSettle(table, function () {
      if ($.fn.dataTable.isDataTable(table)) {
        return;
      }


         if (search) {
      var titles = [
            toolbar.appendChild(search);
        "Asset",
         }
        "Type",
         "Subtype",
        "Mode",
        "Developer",
        "Released",
        "Status",
        "Description",
        "Version",
        "Files",
         "Game",
      ];


        if (searchRow && searchRow.parentNode && !searchRow.querySelector(".dt-search")) {
      var paneIndexes = [1, 2, 3, 4, 5, 6, 8];
            searchRow.remove();
      if (mw.config.get("wgPageName") === "Mods") {
        }
        paneIndexes = [10, 1, 2, 3, 4, 5, 6, 8];
    }
      }


    function getCurrentPageName() {
      var hiddenColumns = [2, 3, 5, 6, 7, 8, 10];
        var pageName = "";
      var data = getTableData(table, titles.length);


        if (window.mw && mw.config) {
      buildTableLayout(table, titles);
            pageName = mw.config.get("wgTitle") || mw.config.get("wgPageName") || "";
        }


         if (!pageName) {
      var dataTable = new DataTable(table, {
             pageName = (document.querySelector("#firstHeading") || {}).textContent || document.title || "";
         columns: titles.map(function (title, index) {
        }
          var column = {
             title: title,
            visible: hiddenColumns.indexOf(index) === -1,
            searchPanes: {
              show: paneIndexes.indexOf(index) !== -1,
            },
          };


        return pageName.replace(/_/g, " ").trim();
          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>"
                );
              }


    function getFilterPanelTitle(titles, hiddenColumnIndexes) {
              return cellData;
        var pageName = getCurrentPageName();
            };
          }


        if (hiddenColumnIndexes && hiddenColumnIndexes.size === 1) {
          if ([1, 2, 3, 4, 6, 8, 10].indexOf(index) !== -1) {
             return titles[Array.from(hiddenColumnIndexes)[0]] + ": " + pageName;
             column.render = {
        }
              display: function (cellData) {
                if (index === 4) {
                  return (
                    '<span style="white-space: nowrap">' +
                    getAcceptsValues(cellData).join(", ") +
                    "</span>"
                  );
                }


        return pageName;
                return cellData;
    }
              },
              sp: function (cellData) {
                return getPlainAcceptsValues(cellData);
              },
            };
            column.searchPanes.orthogonal = "sp";
          }


    function setupFilterOffCanvas(dataTable, table, titles, hiddenColumnIndexes) {
          if (index === 9) {
        var wrapper = table.closest(".dt-container") || table.parentNode;
            column.render = function (cellData, type) {
        var panes = wrapper ? wrapper.querySelector(".dtsp-panesContainer") : null;
              if (type !== "display") {
                return cellData;
              }


        if (!wrapper || !panes) {
              var wrapper = document.createElement("div");
            return null;
              wrapper.innerHTML = cellData;
        }


        if (wrapper.querySelector(".dt-filter-toggle")) {
              var links = Array.from(wrapper.querySelectorAll("a"));
            return document.getElementById(wrapper.querySelector(".dt-filter-toggle").getAttribute("aria-controls"));
        }


        var toggle = document.createElement("button");
              if (!links.length) {
        var panel = document.createElement("aside");
                return "";
        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";
              var downloads = document.createElement("div");
        toggle.className = "dt-filter-toggle";
        toggle.textContent = "Filters";
        toggle.setAttribute("aria-controls", panelId);
        toggle.setAttribute("aria-expanded", "false");


        panel.id = panelId;
              links.forEach(function (sourceLink, linkIndex) {
        panel.className = "dt-filter-panel";
                var link = sourceLink.cloneNode(true);
        panel.setAttribute("aria-hidden", "true");
        panel.setAttribute("aria-label", "Data filters");


        backdrop.className = "dt-filter-backdrop";
                link.textContent =
        drawer.className = "sidebar-drawer";
                  links.length === 1
        drawer.setAttribute("type", "button");
                    ? "Download"
        drawer.setAttribute("tabindex", "0");
                    : "Download " + (linkIndex + 1);
                link.target = "_blank";
                link.rel = "noopener noreferrer nofollow";


        header.className = "dt-filter-panel__header";
                downloads.appendChild(link);
        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 = "&times;";


        body.className = "dt-filter-panel__body";
                if (linkIndex < links.length - 1) {
                  downloads.appendChild(document.createElement("br"));
                }
              });


        header.append(title, close);
              return downloads.innerHTML;
        body.appendChild(panes);
            };
        panel.append(drawer, header, body);
          }


         setupFilterToolbar(wrapper, toggle);
          return column;
         document.body.append(backdrop, panel);
        }),
        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,
      });


        function openPanel() {
      var lastGlobalSearch = dataTable.search();
            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() {
      dataTable.on("draw.dt", function () {
            panel.classList.remove("is-open");
        var globalSearch = dataTable.search();
            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)) {
        openFellowshipLinksInNewTab(dataTable);
                lastFocusedElement.focus();
            }


            dataTable.columns.adjust();
        if (globalSearch === lastGlobalSearch) {
          return;
         }
         }


         toggle.addEventListener("click", openPanel);
         lastGlobalSearch = globalSearch;
        close.addEventListener("click", closePanel);
        drawer.addEventListener("click", closePanel);
        backdrop.addEventListener("click", closePanel);


         document.addEventListener("keydown", function (event) {
         paneIndexes.forEach(function (paneIndex) {
            if (event.key === "Escape" && panel.classList.contains("is-open")) {
          dataTable.searchPanes.rebuildPane(paneIndex, true);
                closePanel();
            }
         });
         });
      });


         return panel;
      dataTable.rows.add(data).draw();
    }
      $(table).removeClass("dt-loading").removeAttr("aria-busy").show();
      dataTable.searchPanes.rebuildPane();
 
      var filterPanel = setupFilterOffCanvas(
        dataTable,
        table,
        titles,
         new Set(),
      );


    $(".dataTable")
      suppressSearchPaneBorders(filterPanel);
        .not(allSelectors.join(","))
      dataTable.columns.adjust().draw(false);
        .each(function () {
    });
            var table = this;
  });


            waitForTableToSettle(table, function () {
  $(clanSelectors.join(",")).each(function () {
                if ($.fn.dataTable.isDataTable(table)) {
    var table = this;
                    return;
                }


                normaliseDataTableRows(table, getDataTableColumnCount(table));
    waitForTableToSettle(table, function () {
      if ($.fn.dataTable.isDataTable(table)) {
        return;
      }


                $(table).DataTable({
      var titles = ["Clan", "Games", "Links", "Description", "Status"];
                    dom: "f",
      var paneIndexes = [1, 4];
                    language: {
      var clanColorClasses = Array.from(
                        search: "",
        table.tBodies[0] ? table.tBodies[0].rows : [],
                        searchPlaceholder: "Search",
      )
                        searchPanes: {
        .filter(function (row) {
                            title: {
          return !(row.cells.length === 1 && row.cells[0].colSpan > 1);
                                _: 'Filters: %d',
        })
                                0: ''
        .map(function (row) {
                            }
          return row.cells[0] ? row.cells[0].className : "";
                        }
                    },
                    retrieve: true,
                    order: [[0, "asc"]],
                    pageLength: 1000,
                });
            });
         });
         });
      var data = getTableData(table, titles.length);


    $(seriesSelectors.join(",")).each(function () {
      buildTableLayout(table, titles);
        var table = this;


        waitForTableToSettle(table, function () {
      var dataTable = new DataTable(table, {
            if ($.fn.dataTable.isDataTable(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);
        });
      });


            var titles = ["Series", "Games"];
      $(table).removeClass("dt-loading").removeAttr("aria-busy").show();
            /*
      dataTable.searchPanes.rebuildPane();
            * The source table is emitted as Games, Series. DataTables uses
            * Series, Games so the hidden filter column is always index 0.
            */
            var data = getTableData(table, titles.length).map(function (row) {
                return {
                    series: row[1],
                    games: row[0],
                };
            });


            normaliseDataTableRows(table, titles.length);
      var filterPanel = setupFilterOffCanvas(
            buildTableLayout(table, titles);
        dataTable,
        table,
        titles,
        new Set(),
      );


            try {
      suppressSearchPaneBorders(filterPanel);
                var dataTable = new DataTable(table, {
      dataTable.columns.adjust().draw(false);
                    data: data,
    });
                    columns: [
  });
                        {
                            data: "series",
                            title: "Series",
                            visible: false,
                            searchBuilderTitle: "Series",
                            searchBuilderType: "html",
                        },
                        {
                            data: "games",
                            title: "Games",
                        },
                    ],
                    layout: {
                        top1: {
                            searchBuilder: {},
                        },
                    },
                    language: {
                        searchBuilder: {
                            data: "Series",
                            value: "Select a series",
                        },
                    },
                    order: [[1, "asc"]],
                    paging: false,
                    searchDelay: 400,
                    initComplete: function () {
                        var api = this.api();


                        /*
  $(playerSelectors.join(",")).each(function () {
                        * SearchBuilder initially contains no criterion, so its
    var table = this;
                        * data select does not exist until Add is activated.
                        * Defer until DataTables has attached the layout.
                        */
                        setTimeout(function configureSeriesBuilder(attempt) {
                            var container = $(api.table().container());
                            var addButton = container.find(".dtsb-add").first();
                            var dataSelect;
                            var seriesOption;
                            var conditionSelect;
                            var valueSelect;
                            var halfLifeOption;


                            attempt = Number(attempt) || 0;
    waitForTableToSettle(table, function () {
                            container.addClass("dt-series-container");
      if ($.fn.dataTable.isDataTable(table)) {
        return;
      }


                            if (!addButton.length && !container.find(".dtsb-data").length) {
      var fallbackTitles = [
                                if (attempt < 20) {
        "Player",
                                    setTimeout(function () {
        "Games",
                                        configureSeriesBuilder(attempt + 1);
        "Clans",
                                    }, 50);
        "Roles",
                                } else {
        "Clan (Role)",
                                    mw.log.error("SearchBuilder controls were not attached.");
        "Status",
                                }
      ];
                                return;
      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()
          : "";


                            if (!container.find(".dtsb-data").length) {
        return title || fallbackTitle;
                                addButton.trigger("click");
      });
                            }


                            dataSelect = container.find(".dtsb-data").first();
      var hiddenColumnIndexes = new Set([2, 3, 5]);
                            seriesOption = dataSelect
      var paneIndexes = getPaneColumnIndexes(table, titles, [2, 3, 5]);
                                .find("option")
      var playerColorClasses = Array.from(
                                .filter(function () {
        table.tBodies[0] ? table.tBodies[0].rows : [],
                                    return $(this).text().trim() === "Series";
      )
                                })
        .filter(function (row) {
                                .first();
          return !(row.cells.length === 1 && row.cells[0].colSpan > 1);
        })
        .map(function (row) {
          return row.cells[0] ? row.cells[0].className : "";
        });


                            if (!seriesOption.length) {
      var data = getTableData(table, titles.length);
                                mw.log.error("SearchBuilder could not find the Series column.");
                                return;
                            }


                            dataSelect.val(seriesOption.val()).trigger("change.dtsb");
      buildTableLayout(table, titles);


                            conditionSelect = container.find(".dtsb-condition").first();
      var dataTable = new DataTable(table, {
                            conditionSelect.val("=").trigger("change.dtsb");
        columns: titles.map(function (title, index) {
          var column = {
            title: title,
            visible: !hiddenColumnIndexes.has(index),
            searchPanes: {
              show: paneIndexes.indexOf(index) !== -1,
            },
          };


                            valueSelect = container.find(".dtsb-value").first();
          if (index === 0) {
                            halfLifeOption = valueSelect
            column.createdCell = function (cell, cellData, rowData, rowIndex) {
                                .find("option")
              var colorClasses = playerColorClasses[rowIndex];
                                .filter(function () {
                                    return $(this).text().trim() === "Half-Life";
                                })
                                .first();


                            if (!halfLifeOption.length) {
              if (!colorClasses) {
                                mw.log.error("SearchBuilder could not find the Half-Life series.");
                return;
                                return;
              }
                            }


                            valueSelect.val(halfLifeOption.val()).trigger("change.dtsb");
              colorClasses
                        }, 0);
                .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;
                * Scope the presentation rules to this DataTable. SearchBuilder
            };
                * owns the predefined Series = criterion and its value select.
          } else if (index === 1) {
                */
            column.render = function (cellData, type) {
                 dataTable.columns.adjust().draw(false);
              if (type === "display") {
            } catch (error) {
                 return renderPlayerGames(cellData);
                 mw.log.error("Could not initialise the Series DataTable:", error);
              }
             } finally {
 
                $(table)
              if (type === "sp") {
                    .removeClass("dt-loading")
                 return getPlainAcceptsValues(cellData);
                    .removeAttr("aria-busy")
              }
                    .show();
 
              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 () {
  $(selectors.join(",")).each(function () {
        var table = this;
    var table = this;


        waitForTableToSettle(table, function () {
    waitForTableToSettle(table, function () {
            if ($.fn.dataTable.isDataTable(table)) {
      if ($.fn.dataTable.isDataTable(table)) {
                return;
        return;
            }
      }


            var titles = [
      var titles = [
                getFirstColumnName(table), "Developer", "Publisher", "Genre", "Platform", "Mode", "Released", "Category"
        getFirstColumnName(table),
            ];
        "Developer",
            var hiddenColumnIndexes = getHiddenColumnIndexes(table, titles);
        "Publisher",
            var paneIndexes = [1, 2, 3, 4, 5, 7].filter(function (paneIndex) {
        "Genre",
                return !hiddenColumnIndexes.has(paneIndex);
        "Platform",
            });
        "Mode",
            var data = getTableData(table, titles.length);
        "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);
      buildTableLayout(table, titles);


            var dataTable = new DataTable(table, {
      var dataTable = new DataTable(table, {
                columns: [
        columns: [
                    {
          {
                        title: titles[0],
            title: titles[0],
                        visible: !hiddenColumnIndexes.has(0),
            visible: !hiddenColumnIndexes.has(0),
                        render: function (data, type) {
            render: function (data, type) {
                            if (type === "display") {
              if (type === "display") {
                                return (
                return (
                                    '<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
                  '<span style="display: list-item; margin-left: 1.25rem; white-space: nowrap">' +
                                    data +
                  data +
                                    "</span>"
                  "</span>"
                                );
                );
                            }
              }


                            return data;
              return data;
                        },
            },
                        searchPanes: {
            searchPanes: {
                            show: false,
              show: false,
                        },
            },
                    },
          },
                    {
          {
                        title: "Developer",
            title: "Developer",
                        visible: !hiddenColumnIndexes.has(1),
            visible: !hiddenColumnIndexes.has(1),
                        searchPanes: {
            render: getPlainSearchPaneRender(),
                            show: !hiddenColumnIndexes.has(1),
            searchPanes: {
                        },
              show: !hiddenColumnIndexes.has(1),
                    },
              orthogonal: "sp",
                    {
            },
                        title: "Publisher",
          },
                        visible: !hiddenColumnIndexes.has(2),
          {
                        searchPanes: {
            title: "Publisher",
                            show: !hiddenColumnIndexes.has(2),
            visible: !hiddenColumnIndexes.has(2),
                        },
            render: getPlainSearchPaneRender(),
                    },
            searchPanes: {
                    {
              show: !hiddenColumnIndexes.has(2),
                        title: "Genre",
              orthogonal: "sp",
                        visible: !hiddenColumnIndexes.has(3),
            },
                        searchPanes: {
          },
                            show: !hiddenColumnIndexes.has(3),
          {
                        },
            title: "Genre",
                    },
            visible: !hiddenColumnIndexes.has(3),
                    {
            render: getPlainSearchPaneRender(),
                        title: "Platform",
            searchPanes: {
                        visible: !hiddenColumnIndexes.has(4),
              show: !hiddenColumnIndexes.has(3),
                        searchPanes: {
              orthogonal: "sp",
                            show: !hiddenColumnIndexes.has(4),
            },
                        },
          },
                    },
          {
                    {
            title: "Platform",
                        title: "Mode",
            visible: !hiddenColumnIndexes.has(4),
                        visible: !hiddenColumnIndexes.has(5),
            render: getPlainSearchPaneRender(),
                        render: {
            searchPanes: {
                            display: function (data) {
              show: !hiddenColumnIndexes.has(4),
                                return getGameModeDisplayValues(data).join(", ");
              orthogonal: "sp",
                            },
            },
                            sp: function (data) {
          },
                                return getGameModeValues(data);
          {
                            },
            title: "Mode",
                        },
            visible: !hiddenColumnIndexes.has(5),
                        searchPanes: {
            render: {
                            show: !hiddenColumnIndexes.has(5),
              display: function (data) {
                            orthogonal: "sp",
                return getGameModeDisplayValues(data).join(", ");
                        },
              },
                    },
              sp: function (data) {
                    {
                return getGameModeValues(data);
                        title: "Released",
              },
                        visible: !hiddenColumnIndexes.has(6),
            },
                        searchPanes: {
            searchPanes: {
                            show: !hiddenColumnIndexes.has(6),
              show: !hiddenColumnIndexes.has(5),
                        },
              orthogonal: "sp",
                    },
            },
                    {
          },
                        title: "Category",
          {
                        visible: false,
            title: "Released",
                        render: {
            visible: !hiddenColumnIndexes.has(6),
                            display: function (data) {
            searchPanes: {
                                return data;
              show: !hiddenColumnIndexes.has(6),
                            },
            },
                            sp: function (data) {
          },
                                return getAcceptsValues(data);
          {
                            },
            title: "Category",
                        },
            visible: false,
                        searchPanes: {
            render: getPlainSearchPaneRender(),
                            show: true,
            searchPanes: {
                            orthogonal: "sp",
              show: true,
                        },
              orthogonal: "sp",
                    },
            },
                ],
          },
                layout: {
        ],
                    topStart: null,
        layout: {
                    topEnd: null,
          topStart: null,
                    top2: {
          topEnd: null,
                        searchPanes: {
          top2: {
                            cascadePanes: true,
            searchPanes: {
                            initCollapsed: true,
              cascadePanes: true,
                            layout: "columns-1",
              dtOpts: {
                            columns: paneIndexes,
                scroller: false,
                        },
              },
                    },
              initCollapsed: true,
                    top1: {
              layout: "columns-1",
                        search: {
              columns: paneIndexes,
                            className: "mx-0 w-100",
            },
                        },
          },
                    },
          top1: {
                },
            search: {
                order: [[0, "asc"]],
              className: "mx-0 w-100",
                language: {
            },
                    search: "",
          },
                    searchPlaceholder: "Search",
        },
                    searchPanes: {
        order: [[0, "asc"]],
                        title: {
        language: {
                            _: 'Filters: %d',
          search: "",
                            0: ''
          searchPlaceholder: "Search",
                        }
          searchPanes: {
                    }
            title: {
                },
              _: "Filters: %d",
                paging: false,
              0: "",
                searchDelay: 400,
            },
            });
          },
        },
        paging: false,
        searchDelay: 400,
      });


            var lastGlobalSearch = dataTable.search();
      var lastGlobalSearch = dataTable.search();


            dataTable.on("draw.dt", function () {
      dataTable.on("draw.dt", function () {
                var globalSearch = dataTable.search();
        var globalSearch = dataTable.search();


                openFellowshipLinksInNewTab(dataTable);
        openFellowshipLinksInNewTab(dataTable);


                if (globalSearch === lastGlobalSearch) {
        if (globalSearch === lastGlobalSearch) {
                    return;
          return;
                }
        }


                lastGlobalSearch = globalSearch;
        lastGlobalSearch = globalSearch;


                paneIndexes.forEach(function (paneIndex) {
        paneIndexes.forEach(function (paneIndex) {
                    dataTable.searchPanes.rebuildPane(paneIndex, true);
          dataTable.searchPanes.rebuildPane(paneIndex, true);
                });
        });
            });
      });


            var filterPanel;
      var filterPanel;


            dataTable.rows.add(data).draw();
      dataTable.rows.add(data).draw();
            $(table)
      $(table).removeClass("dt-loading").removeAttr("aria-busy").show();
                .removeClass("dt-loading")
      dataTable.searchPanes.rebuildPane();
                .removeAttr("aria-busy")
      filterPanel = setupFilterOffCanvas(
                .show();
        dataTable,
            dataTable.searchPanes.rebuildPane();
        table,
            filterPanel = setupFilterOffCanvas(dataTable, table, titles, hiddenColumnIndexes);
        titles,
            suppressSearchPaneBorders(filterPanel);
        hiddenColumnIndexes,
            dataTable.columns.adjust().draw(false);
      );
        });
      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 = "&times;";

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