Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
This user script seems to have a documentation page at User:Daniel Quinlan/Scripts/AnyMessage.
'use strict';
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui']).then(() => {
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Allmessages') return;
const prefixInput = document.querySelector('input[name="prefix"]');
const groupContainer = prefixInput?.closest('.oo-ui-fieldsetLayout-group');
if (!groupContainer) return;
// inject interface
const prefixWidth = prefixInput.offsetWidth;
const columnWidth = prefixWidth ? `${prefixWidth}px` : '50em';
mw.util.addCSS(`
.anymessage-grid {
display: grid;
grid-template-columns: minmax(max-content, ${columnWidth}) max-content;
column-gap: 2em;
}
.anymessage-grid > .oo-ui-fieldLayout { grid-column: 1; }
.anymessage-sidebar {
grid-column: 2;
grid-row: 1 / -1;
}
.anymessage-inline .oo-ui-checkboxMultioptionWidget {
display: inline-block;
margin-right: 1em;
}
#mw-content-text.anymessage .TablePager_nav,
#mw-content-text.anymessage .mw-htmlform-field-HTMLSelectLimitField {
display: none;
}
`);
const api = new mw.Api();
const typeSelect = new OO.ui.ButtonSelectWidget({
items: [
new OO.ui.ButtonOptionWidget({ data: 'prefix', label: 'Prefix', selected: true }),
new OO.ui.ButtonOptionWidget({ data: 'string', label: 'String' }),
new OO.ui.ButtonOptionWidget({ data: 'regex', label: 'Regex' })
]
});
const caseCheckbox = new OO.ui.CheckboxInputWidget({ selected: true });
const searchOptions = new OO.ui.CheckboxMultiselectWidget({
items: [
new OO.ui.CheckboxMultioptionWidget({ data: 'name', label: 'Name', selected: true }),
new OO.ui.CheckboxMultioptionWidget({ data: 'text', label: 'Text', selected: true })
],
classes: ['anymessage-inline']
});
const sidebarFieldset = new OO.ui.FieldsetLayout({ classes: ['anymessage-sidebar'] });
sidebarFieldset.addItems([
new OO.ui.FieldLayout(typeSelect, { label: 'Match type:', align: 'top' }),
new OO.ui.FieldLayout(caseCheckbox, { label: 'Case insensitive', align: 'inline' }),
new OO.ui.FieldLayout(searchOptions, { label: 'Search:', align: 'inline' }),
]);
groupContainer.classList.add('anymessage-grid');
groupContainer.append(sidebarFieldset.$element[0]);
const labelElement = groupContainer.querySelector('.oo-ui-labelElement-label');
if (labelElement) labelElement.textContent = 'Search:';
// interface updates
const updatePage = () => {
const mode = typeSelect.findSelectedItem()?.getData();
const isPrefix = mode === 'prefix';
caseCheckbox.setDisabled(isPrefix);
searchOptions.setDisabled(isPrefix);
document.querySelector('#mw-content-text')?.classList.toggle('anymessage', !isPrefix);
};
typeSelect.on('select', updatePage);
updatePage();
// intercept submit
prefixInput.closest('form')?.addEventListener('submit', async event => {
if (typeSelect.findSelectedItem()?.getData() === 'prefix') return;
event.preventDefault();
const query = prefixInput.value.trim();
const filter = document.querySelector('#mw-input-filter input[type="radio"]:checked')?.value || 'all';
const language = document.querySelector('select[name="lang"]')?.value || mw.config.get('wgContentLanguage');
await runSearch(query, filter, language);
});
async function runSearch(query, filter, language) {
const table = document.getElementById('mw-allmessagestable');
if (!table) return;
const matchFn = buildMatcher(query);
if (!matchFn) {
showStatus(table, 'Invalid regular expression');
return;
}
showStatus(table, 'Searching');
try {
const result = await api.get({
action: 'query',
meta: 'allmessages',
amcustomised: filter,
amlang: language,
amprop: 'default'
});
const messages = result.query?.allmessages || [];
const selectedData = searchOptions.findSelectedItemsData();
const inName = selectedData.includes('name');
const inText = selectedData.includes('text');
const filtered = messages.filter(message => {
if (!query) return true;
if (inName && matchFn(message.name)) return true;
if (inText) {
if (matchFn(message['*'] || '')) return true;
if ('default' in message && matchFn(message.default || '')) return true;
}
return false;
});
renderResults(table, filtered, language);
} catch (error) {
showStatus(table, `Search failed: ${error}`);
}
}
function buildMatcher(query) {
if (!query) return () => true;
const mode = typeSelect.findSelectedItem()?.getData();
const ignoreCase = caseCheckbox.isSelected();
if (mode === 'regex') {
try {
const re = new RegExp(query, ignoreCase ? 'iu' : 'u');
return string => re.test(string);
} catch { return null; }
}
if (ignoreCase) {
const lower = query.toLowerCase();
return string => string.toLowerCase().includes(lower);
}
return string => string.includes(query);
}
function showStatus(table, text) {
table.querySelectorAll('tbody').forEach(tb => tb.remove());
const td = document.createElement('td');
td.colSpan = 2;
td.textContent = text;
const tr = document.createElement('tr');
tr.append(td);
const tbody = document.createElement('tbody');
tbody.append(tr);
table.append(tbody);
}
function renderResults(table, messages, language) {
table.querySelectorAll('tbody').forEach(tb => tb.remove());
if (!messages.length) {
showStatus(table, 'No results');
return;
}
for (const message of messages) {
table.append(renderMessage(message, language));
}
resolveTalkLinks(table);
}
function renderMessage(message, language) {
const isModified = 'default' in message;
const nameCell = buildNameCell(message, language, isModified ? 2 : 1);
const cell1 = document.createElement('td');
cell1.lang = language;
cell1.dir = 'auto';
cell1.textContent = isModified ? (message.default || '') : (message['*'] || '');
if (isModified) cell1.classList.add('am_default');
const tr1 = document.createElement('tr');
tr1.append(nameCell, cell1);
const tbody = document.createElement('tbody');
tbody.append(tr1);
if (isModified) {
const cell2 = document.createElement('td');
cell2.lang = language;
cell2.dir = 'auto';
cell2.textContent = message['*'] || '';
cell2.classList.add('am_actual');
const tr2 = document.createElement('tr');
tr2.append(cell2);
tbody.append(tr2);
}
return tbody;
}
function buildNameCell(message, language, rowspan) {
const td = document.createElement('td');
if (rowspan > 1) td.rowSpan = rowspan;
const name = message.name;
const isModified = 'default' in message;
const editLink = document.createElement('a');
editLink.textContent = name;
editLink.href = mw.util.getUrl(`MediaWiki:${name}`, isModified ? {} : { action: 'edit', redlink: 1 });
if (!isModified) editLink.className = 'new';
const talkTitle = mw.Title.newFromText(name, 9)?.getPrefixedText() ?? null;
const baseContent = (isModified ? message.default : message['*']) || '';
let queryText = baseContent.trim();
if (queryText.length > 1024) {
const last = queryText.lastIndexOf(' ', 1024);
queryText = last > 0 ? queryText.substring(0, last) : queryText.substring(0, 1024);
}
const twLink = document.createElement('a');
twLink.className = 'external';
twLink.rel = 'nofollow';
twLink.textContent = 'Translate';
twLink.href = `https://translatewiki.net/w/i.php?${new URLSearchParams({
title: 'Special:SearchTranslations',
group: 'mediawiki',
grouppath: 'mediawiki',
language: language,
query: `${name} ${queryText}`.trim()
})}`;
if (talkTitle) {
const talkLink = document.createElement('a');
talkLink.className = 'new';
talkLink.textContent = 'talk';
talkLink.href = mw.util.getUrl(talkTitle, { action: 'edit', redlink: 1 });
talkLink.dataset.anymessageTitle = talkTitle;
td.append(editLink, ' (', talkLink, ') (', twLink, ')');
} else {
td.append(editLink, ' (', twLink, ')');
}
return td;
}
async function resolveTalkLinks(container) {
const links = Array.from(container.querySelectorAll('a[data-anymessage-title]'));
for (let i = 0; i < links.length; i += 50) {
const batch = links.slice(i, i + 50);
const result = await api.get({
action: 'query',
titles: batch.map(l => l.dataset.anymessageTitle),
formatversion: 2
});
if (!result.query?.pages) continue;
for (const page of result.query.pages) {
if (!page.missing) {
const link = batch.find(l => l.dataset.anymessageTitle === page.title);
if (!link) continue;
link.classList.remove('new');
link.href = mw.util.getUrl(page.title);
delete link.dataset.anymessageTitle;
}
}
}
}
});