MediaWiki:Common.js
Appearance
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.
/* MediaWiki:Common.js is loaded for every page in the wiki. Its functions are FURTHER AUGMENTED BY GADGETS defined in MediaWiki:Gadgets-definition. In which files the code is located DOES MATTER, as Gadgets run at the earlier ResourceLoader stage, while code on this file runs later, and should be used for less time-sensitive operations. */
// Load jQuery UI using mw.loader
mw.loader.using(['jquery.ui'], function() {
console.log("jQuery UI loaded");
// Deprecated
$(function() {;
});
// ANCHOR: Attach classes to system links for CSS customization
mw.hook('wikipage.content').add(function($content) {
$content.find('a[title="Special:Undelete"]').addClass('undelete-link');
});
// ANCHOR: Page ID Display
$(function() {
// Only show page ID on actual content pages (not special pages, edit pages, etc.)
var pageId = mw.config.get('wgArticleId');
var namespace = mw.config.get('wgNamespaceNumber');
var action = mw.config.get('wgAction');
// Show on main namespace (0) and other content namespaces, but not on edit/special pages
if (pageId && namespace >= 0 && action === 'view') {
// Try to find the footer element (works for both Vector and Minerva skins)
var $footer = $('#footer.mw-footer, .mw-footer');
if ($footer.length > 0) {
// Insert at the beginning of the footer
$('<div id="icw-page-id">PageID: ' + pageId + '</div>')
.prependTo($footer);
} else {
// Fallback: append to body if no footer found
$('<div id="icw-page-id">PageID: ' + pageId + '</div>')
.appendTo('body');
}
}
});
// ANCHOR: Set "File" Categories intelligently based on file type - Incompatible with Gadgets for now
mw.loader.using(['mediawiki.util'], function () {
(function () {
// Only run on upload pages (classic / MultipleUpload). We do NOT touch UploadWizard here.
var page = mw.config.get('wgCanonicalSpecialPageName');
if (window.FileCategoryLoaded || !(/Upload|MultipleUpload/g.test(page))) return;
window.FileCategoryLoaded = true;
// Localized Category namespace label (NS 14)
var CAT_NS = (mw.config.get('wgFormattedNamespaces') || {})[14] || 'Category';
// Define file type mapping
var categoryMapping = {
// Images
'jpg': 'Images',
'jpeg': 'Images',
'png': 'Images',
'gif': 'Images',
'svg': 'Images',
'webp': 'Images',
'bmp': 'Images',
'tiff': 'Images',
// Documents
'pdf': 'Documents',
'doc': 'Documents',
'docx': 'Documents',
'ppt': 'Documents',
'pptx': 'Documents',
'xls': 'Documents',
'xlsx': 'Documents',
'txt': 'Documents',
'rtf': 'Documents',
'odt': 'Documents',
'ods': 'Documents',
'odp': 'Documents',
// Archives
'zip': 'Archives',
'rar': 'Archives',
'tar': 'Archives',
'gz': 'Archives',
'7z': 'Archives',
// Audio
'mp3': 'Audio',
'wav': 'Audio',
'ogg': 'Audio',
'flac': 'Audio',
// Video
'mp4': 'Video',
'webm': 'Video',
'avi': 'Video',
'mov': 'Video',
'mkv': 'Video',
// Default fallback
'default': 'Files'
};
function escRx(s) {
return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Extract clean extension from filename
function getFileExtension(filename) {
if (!filename) return '';
var cleanName = filename.split('\\').pop().split('/').pop(); // strip path
var i = cleanName.lastIndexOf('.');
if (i <= 0 || i === cleanName.length - 1) return '';
return cleanName.slice(i + 1).toLowerCase();
}
// Apply category based on file extension (writes ONLY to #wpUploadDescription)
function applyCategoryFromExtension(filename) {
var $descField = $('#wpUploadDescription');
if (!$descField.length || !filename) return;
var ext = getFileExtension(filename);
if (!ext) return;
var category = categoryMapping[ext] || categoryMapping['default'];
var add = '[[' + CAT_NS + ':' + category + ']]';
// Current description
var current = $descField.val() || '';
// If already present, do nothing (prevents duplicates on repeated change events)
var already = new RegExp('\\[\\[\\s*' + escRx(CAT_NS) + '\\s*:\\s*' + escRx(category) + '(?:\\s*\\|[^\\]]*)?\\s*\\]\\]', 'i');
if (already.test(current)) return;
// Remove any mapped categories we may have added before (handles sort keys and spaces)
var cats = Array.from(new Set(Object.values(categoryMapping)));
cats.forEach(function (catName) {
if (!catName) return;
var rx = new RegExp('\\[\\[\\s*' + escRx(CAT_NS) + '\\s*:\\s*' + escRx(catName) + '(?:\\s*\\|[^\\]]*)?\\s*\\]\\]', 'gi');
current = current.replace(rx, '');
});
current = current.trim();
var next = current ? (current + '\n\n' + add) : add;
$descField.val(next);
// console.log('FileCategory: Applied ' + add + ' for .' + ext);
}
// Set up event listeners
function setupEventListeners() {
// Classic uses #wpUploadFile; MultipleUpload often has several inputs (wpUploadFile1, wpUploadFile2, …)
var $fileInputs = $('#wpUploadFile, [id^="wpUploadFile"]');
if (!$fileInputs.length) return;
// Listen for file selection changes on all inputs
$fileInputs.on('change', function () {
var filename = $(this).val();
if (filename) applyCategoryFromExtension(filename);
});
// Hook into MediaWiki's upload events if available
if (typeof mw.hook !== 'undefined') {
mw.hook('uploadform.fileSelected').add(function (data) {
if (data && data.filename) applyCategoryFromExtension(data.filename);
});
}
// Observe value attribute changes (some browsers/flows update programmatically)
$fileInputs.each(function () {
var el = this;
if (!window.MutationObserver) return;
try {
var ob = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var m = mutations[i];
if (m.type === 'attributes' && m.attributeName === 'value') {
var filename = $(el).val();
if (filename) applyCategoryFromExtension(filename);
}
}
});
ob.observe(el, { attributes: true, attributeFilter: ['value'] });
} catch (e) { /* no-op */ }
});
}
// Initialize when DOM is ready
$(setupEventListeners);
})();
});
// ANCHOR: Template debug monitor
$(function() {
// Initialize the template debug monitor
window.templateDebugMonitor = {
// Cache to track already seen errors to prevent duplicates
seenErrors: {},
// Scan for debug information in invisible divs with more efficient error reporting
scan: function() {
const html = document.documentElement.innerHTML;
let found = false;
// Scan for status messages
const templateStatusDivs = document.querySelectorAll('div[data-template-status]');
if (templateStatusDivs.length > 0) {
templateStatusDivs.forEach(div => {
const statusCount = parseInt(div.getAttribute('data-status-count') || '0');
if (statusCount === 0) return;
let statusFingerprint = "";
for (let i = 1; i <= statusCount; i++) {
const source = div.getAttribute(`data-status-${i}-source`) || '';
const msg = div.getAttribute(`data-status-${i}-msg`) || '';
statusFingerprint += `${source}:${msg};`;
}
if (this.seenErrors[statusFingerprint]) {
return;
}
this.seenErrors[statusFingerprint] = true;
found = true;
console.group(`Template Status`);
console.info(`Found ${statusCount} status message(s)`);
for (let i = 1; i <= statusCount; i++) {
const source = div.getAttribute(`data-status-${i}-source`);
const msg = div.getAttribute(`data-status-${i}-msg`);
const details = div.getAttribute(`data-status-${i}-details`);
if (source && msg) {
console.info(`Status from ${source}: ${msg}`);
if (details) {
console.log('Details:', details);
}
}
}
console.groupEnd();
});
}
// Only look for the data attribute format - our unified approach
const templateErrorDivs = document.querySelectorAll('div[data-template-error]');
if (templateErrorDivs.length > 0) {
templateErrorDivs.forEach(div => {
// Create a unique ID for this error set
const errorCount = parseInt(div.getAttribute('data-error-count') || '0');
if (errorCount === 0) return;
// Create a fingerprint of the errors to avoid duplicates
let errorFingerprint = "";
for (let i = 1; i <= errorCount; i++) {
const source = div.getAttribute(`data-error-${i}-source`) || '';
const msg = div.getAttribute(`data-error-${i}-msg`) || '';
errorFingerprint += `${source}:${msg};`;
}
// Skip if we've already seen this exact set of errors
if (this.seenErrors[errorFingerprint]) {
return;
}
// Mark as seen
this.seenErrors[errorFingerprint] = true;
found = true;
// Log to console
console.group(`Template Error`);
console.warn(`Found ${errorCount} error(s)`);
// Look for individual error attributes with our simplified naming
for (let i = 1; i <= errorCount; i++) {
const source = div.getAttribute(`data-error-${i}-source`);
const msg = div.getAttribute(`data-error-${i}-msg`);
const details = div.getAttribute(`data-error-${i}-details`);
if (source && msg) {
console.error(`Error in ${source}: ${msg}`);
if (details) {
console.info('Details:', details);
}
}
}
console.groupEnd();
});
}
// Structure errors with minimal format
const structureErrorDivs = document.querySelectorAll('div[data-structure-error]');
if (structureErrorDivs.length > 0) {
structureErrorDivs.forEach(div => {
const errorCount = parseInt(div.getAttribute('data-error-count') || '0');
if (errorCount === 0) return;
// Create fingerprint for deduplication
let errorFingerprint = "structure:";
Array.from(div.attributes)
.filter(attr => attr.name.startsWith('data-error-block-'))
.forEach(attr => {
errorFingerprint += `${attr.name}:${attr.value};`;
});
// Skip if already seen
if (this.seenErrors[errorFingerprint]) {
return;
}
// Mark as seen
this.seenErrors[errorFingerprint] = true;
found = true;
console.group(`Template Structure Error`);
console.warn(`Found ${errorCount} block error(s)`);
// Scan all attributes
Array.from(div.attributes)
.filter(attr => attr.name.startsWith('data-error-block-'))
.forEach(attr => {
const blockNum = attr.name.replace('data-error-block-', '');
console.error(`Error in Block ${blockNum}: ${attr.value}`);
});
console.groupEnd();
});
}
// Look for emergency outputs (minimal attributes and format)
const emergencyDivs = document.querySelectorAll('div[data-critical="1"]');
if (emergencyDivs.length > 0) {
emergencyDivs.forEach(div => {
const source = div.getAttribute('data-error-1-source') || 'unknown';
const msg = div.getAttribute('data-error-1-msg') || 'Critical error';
// Create fingerprint for deduplication
const errorFingerprint = `critical:${source}:${msg}`;
// Skip if already seen
if (this.seenErrors[errorFingerprint]) {
return;
}
// Mark as seen
this.seenErrors[errorFingerprint] = true;
found = true;
console.group(`Critical Template Error`);
console.error(`Template crashed in ${source}: ${msg}`);
console.groupEnd();
});
}
return found;
},
// Initialize the monitor - with throttling to prevent excessive scanning
init: function() {
let scanTimeout = null;
// Define a throttled scan function
const throttledScan = () => {
if (scanTimeout) {
clearTimeout(scanTimeout);
}
scanTimeout = setTimeout(() => {
this.scan();
scanTimeout = null;
}, 300); // Throttle to once every 300ms
};
// Run initial scan after page load
throttledScan();
// Run when content changes
mw.hook('wikipage.content').add(throttledScan);
console.log('Template Debug Monitor initialized');
}
};
// Initialize the debug monitor
templateDebugMonitor.init();
// Debug function for achievements
window.debugAchievements = function() {
// Get page ID
var pageId = mw.config.get('wgArticleId');
console.log("Page ID: " + pageId);
// Check for achievement header
var headerElement = document.querySelector('.achievement-header');
if (headerElement) {
console.log("Achievement Header Found:");
console.log(" Class: " + headerElement.className);
console.log(" Data ID: " + headerElement.getAttribute('data-achievement-id'));
console.log(" Data Name: " + headerElement.getAttribute('data-achievement-name'));
console.log(" Text: " + headerElement.textContent.trim());
} else {
console.log("No achievement header found");
}
// Check for achievement badges
var badgeElements = document.querySelectorAll('.achievement-badge');
if (badgeElements.length > 0) {
console.log("Achievement Badges Found: " + badgeElements.length);
badgeElements.forEach(function(badge, index) {
console.log("Badge " + (index + 1) + ":");
console.log(" Class: " + badge.className);
console.log(" Data ID: " + badge.getAttribute('data-achievement-id'));
console.log(" Data Name: " + badge.getAttribute('data-achievement-name'));
});
} else {
console.log("No achievement badges found");
}
};
// Run the debug function when the page loads
setTimeout(function() {
if (typeof window.debugAchievements === 'function') {
window.debugAchievements();
}
}, 1000);
});
});