MediaWiki:Common.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
/* -------------------------------------------------------------------------
* Hardware benchmark table search
* ------------------------------------------------------------------------- */
mw.hook('wikipage.content').add(function ($content) {
'use strict';
var action = mw.config.get('wgAction');
/* Do not run inside edit or preview screens */
if (action === 'edit' || action === 'submit') {
return;
}
$content.find('.benchmark-results-table').each(function () {
var table = this;
if (table.dataset.searchInitialised === 'true') {
return;
}
table.dataset.searchInitialised = 'true';
/* Create a wrapper that follows the table's natural width */
var tableBlock = document.createElement('div');
tableBlock.className = 'benchmark-table-block';
table.parentNode.insertBefore(tableBlock, table);
tableBlock.appendChild(table);
var controls = document.createElement('div');
controls.className = 'benchmark-table-controls';
var searchWrapper = document.createElement('div');
searchWrapper.className = 'benchmark-table-search';
var input = document.createElement('input');
input.type = 'search';
input.className = 'benchmark-search-input';
input.placeholder =
'Search processor, system name, RAM, or benchmark result';
input.setAttribute(
'aria-label',
'Search benchmark results'
);
var clearButton = document.createElement('button');
clearButton.type = 'button';
clearButton.className = 'benchmark-search-clear';
clearButton.textContent = 'Clear';
var resultCount = document.createElement('div');
resultCount.className = 'benchmark-result-count';
resultCount.setAttribute('aria-live', 'polite');
searchWrapper.appendChild(input);
searchWrapper.appendChild(clearButton);
controls.appendChild(searchWrapper);
controls.appendChild(resultCount);
tableBlock.insertBefore(controls, table);
var rows = Array.from(
table.querySelectorAll('tr')
).filter(function (row) {
return row.querySelectorAll('td').length > 0;
});
function normaliseText(value) {
return String(value || '')
.toLocaleLowerCase()
.replace(/\s+/g, ' ')
.trim();
}
function filterRows() {
var query = normaliseText(input.value);
var visibleCount = 0;
rows.forEach(function (row) {
var matches =
query === '' ||
normaliseText(row.textContent).includes(query);
row.hidden = !matches;
if (matches) {
visibleCount += 1;
}
});
resultCount.textContent = query
? visibleCount +
' matching result' +
(visibleCount === 1 ? '' : 's')
: rows.length + ' benchmark results';
clearButton.hidden = query === '';
}
input.addEventListener('input', filterRows);
clearButton.addEventListener('click', function () {
input.value = '';
filterRows();
input.focus();
});
filterRows();
});
});
/* -------------------------------------------------------------------------
* Category-aware Example Model Catalogue search
* ------------------------------------------------------------------------- */
mw.hook('wikipage.content').add(function ($content) {
'use strict';
var action = mw.config.get('wgAction');
/* Do not rearrange the table in edit or preview screens */
if (action === 'edit' || action === 'submit') {
return;
}
$content.find('.example-model-results-table').each(function () {
var table = this;
if (table.dataset.exampleSearchInitialised === 'true') {
return;
}
table.dataset.exampleSearchInitialised = 'true';
/*
* Reuse the hardware-search classes so both search bars have
* the same visual appearance.
*/
var tableBlock = document.createElement('div');
tableBlock.className = 'benchmark-table-block';
table.parentNode.insertBefore(tableBlock, table);
tableBlock.appendChild(table);
var controls = document.createElement('div');
controls.className = 'benchmark-table-controls';
var searchWrapper = document.createElement('div');
searchWrapper.className = 'benchmark-table-search';
var input = document.createElement('input');
input.type = 'search';
input.className = 'benchmark-search-input';
input.placeholder =
'Search model category, description, or model name';
input.setAttribute(
'aria-label',
'Search Example Model Catalogue'
);
var clearButton = document.createElement('button');
clearButton.type = 'button';
clearButton.className = 'benchmark-search-clear';
clearButton.textContent = 'Clear';
var resultCount = document.createElement('div');
resultCount.className = 'benchmark-result-count';
resultCount.setAttribute('aria-live', 'polite');
searchWrapper.appendChild(input);
searchWrapper.appendChild(clearButton);
controls.appendChild(searchWrapper);
controls.appendChild(resultCount);
tableBlock.insertBefore(controls, table);
/*
* Catalogue logical columns:
*
* 0: Major category
* 1: Subcategory
* 2: Description
* 3: Model name
*
* Major categories and subcategories may use rowspan.
* Some major categories may span both category columns.
*/
var originalRows = Array.from(
table.querySelectorAll('tr')
).filter(function (row) {
return row.querySelectorAll('td').length > 0;
});
if (!originalRows.length) {
controls.remove();
return;
}
var rowParent = originalRows[0].parentNode;
var activeRowspans = [];
var records = [];
function normaliseText(value) {
return String(value || '')
.toLocaleLowerCase()
.replace(/\s+/g, ' ')
.trim();
}
function getCellText(cell) {
return cell
? normaliseText(cell.textContent)
: '';
}
/*
* Convert each physical HTML row into a logical catalogue record.
* Text from merged category cells is inherited by every row covered
* by that rowspan.
*/
originalRows.forEach(function (row, rowIndex) {
var logicalCells = [];
var physicalCells = Array.from(row.cells);
var columnIndex = 0;
activeRowspans.forEach(function (active, index) {
if (active && active.endRow >= rowIndex) {
logicalCells[index] = active.cell;
}
});
physicalCells.forEach(function (cell) {
while (logicalCells[columnIndex]) {
columnIndex += 1;
}
var colspan = Math.max(cell.colSpan || 1, 1);
var rowspan = Math.max(cell.rowSpan || 1, 1);
for (
var offset = 0;
offset < colspan;
offset += 1
) {
var logicalColumn = columnIndex + offset;
logicalCells[logicalColumn] = cell;
if (rowspan > 1) {
activeRowspans[logicalColumn] = {
cell: cell,
endRow: rowIndex + rowspan - 1
};
}
}
columnIndex += colspan;
});
var categoryCell = logicalCells[0] || null;
var subcategoryCell = logicalCells[1] || null;
var descriptionCell = logicalCells[2] || null;
var modelNameCell = logicalCells[3] || null;
/*
* When one cell spans both category columns, there is no
* separate subcategory.
*/
var categorySpansBoth =
categoryCell &&
subcategoryCell &&
categoryCell === subcategoryCell;
var category = getCellText(categoryCell);
var subcategory = categorySpansBoth
? ''
: getCellText(subcategoryCell);
var description = getCellText(descriptionCell);
var modelName = getCellText(modelNameCell);
records.push({
originalRow: row,
categoryCell: categoryCell,
subcategoryCell: categorySpansBoth
? null
: subcategoryCell,
descriptionCell: descriptionCell,
modelNameCell: modelNameCell,
category: category,
subcategory: subcategory,
categoryKey:
category || '__uncategorised__',
subcategoryKey:
subcategory || '__no_subcategory__',
searchableText: normaliseText(
[
category,
subcategory,
description,
modelName
].join(' ')
)
});
});
function cloneCell(cell) {
var clone;
if (!cell) {
return document.createElement('td');
}
clone = cell.cloneNode(true);
clone.removeAttribute('rowspan');
clone.removeAttribute('colspan');
return clone;
}
function removeDisplayedDataRows() {
Array.from(
table.querySelectorAll('tr')
).forEach(function (row) {
if (row.querySelectorAll('td').length > 0) {
row.remove();
}
});
}
function restoreOriginalRows() {
removeDisplayedDataRows();
records.forEach(function (record) {
rowParent.appendChild(record.originalRow);
});
}
function getRunLength(items, startIndex, keyFunction) {
var startingKey = keyFunction(items[startIndex]);
var count = 1;
var index;
for (
index = startIndex + 1;
index < items.length;
index += 1
) {
if (keyFunction(items[index]) !== startingKey) {
break;
}
count += 1;
}
return count;
}
/*
* Rebuild only the matching rows and calculate new rowspan values
* for the visible category and subcategory groups.
*/
function renderFilteredRows(matches) {
removeDisplayedDataRows();
matches.forEach(function (record, index) {
var previous = index > 0
? matches[index - 1]
: null;
var categoryStarts =
!previous ||
previous.categoryKey !== record.categoryKey;
var subcategoryStarts =
categoryStarts ||
previous.subcategoryKey !==
record.subcategoryKey;
var row = document.createElement('tr');
if (categoryStarts) {
var categoryCell =
cloneCell(record.categoryCell);
var categoryRowspan = getRunLength(
matches,
index,
function (item) {
return item.categoryKey;
}
);
categoryCell.rowSpan = categoryRowspan;
if (!record.subcategory) {
categoryCell.colSpan = 2;
}
row.appendChild(categoryCell);
}
if (
record.subcategory &&
subcategoryStarts
) {
var subcategoryCell =
cloneCell(record.subcategoryCell);
var subcategoryRowspan = getRunLength(
matches,
index,
function (item) {
return (
item.categoryKey +
'::' +
item.subcategoryKey
);
}
);
subcategoryCell.rowSpan =
subcategoryRowspan;
row.appendChild(subcategoryCell);
}
row.appendChild(
cloneCell(record.descriptionCell)
);
row.appendChild(
cloneCell(record.modelNameCell)
);
rowParent.appendChild(row);
});
}
function filterRows() {
var query = normaliseText(input.value);
var matches;
if (!query) {
restoreOriginalRows();
resultCount.textContent =
records.length + ' example models';
clearButton.hidden = true;
return;
}
matches = records.filter(function (record) {
return record.searchableText.includes(query);
});
renderFilteredRows(matches);
resultCount.textContent =
matches.length +
' matching model' +
(matches.length === 1 ? '' : 's');
clearButton.hidden = false;
}
input.addEventListener('input', filterRows);
clearButton.addEventListener('click', function () {
input.value = '';
filterRows();
input.focus();
});
filterRows();
});
});