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");
// Initialize and set up accordion handling when DOM is ready
$(function() {
setupAccordionMenus();
});
// 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: Main Page accordion menus
var menuState = {
initialized: false,
hasLoggedSetupUnnecessary: false
};
// Main setup function for accordion menu behavior
function setupAccordionMenus() {
var $topRow = $('.icannwiki-top-row');
// Only proceed if we have the top row
if ($topRow.length === 0) {
if (!menuState.hasLoggedSetupUnnecessary) {
console.log("Menu setup unnecessary - no top row found");
menuState.hasLoggedSetupUnnecessary = true;
}
return;
}
// Skip if already initialized
if (menuState.initialized) {
return;
}
// Set up accordion layout immediately
setupAccordionLayout($topRow);
menuState.initialized = true;
}
// Set up accordion layout
function setupAccordionLayout($topRow) {
// If accordions already exist, just mark as ready
if ($topRow.find('.icannwiki-accordion-section').length > 0) {
$topRow.addClass('mobile-ready');
console.log("Accordions already exist - marked as ready");
return;
}
console.log("Setting up accordion layout");
// Capture original menu content
var $primaryMenu = $topRow.find('.icannwiki-menu-container.primary').clone(true, true);
var $secondaryMenu = $topRow.find('.icannwiki-menu-container.secondary').clone(true, true);
// Build accordion structure using MainPage.css classes
var accordionHTML =
'<div class="icannwiki-accordion-section">' +
'<button type="button" class="icannwiki-accordion-header primary" aria-expanded="false">ICANN</button>' +
'<div class="icannwiki-accordion-content" aria-hidden="true"></div>' +
'</div>' +
'<div class="icannwiki-accordion-section">' +
'<button type="button" class="icannwiki-accordion-header secondary" aria-expanded="false">Internet Governance</button>' +
'<div class="icannwiki-accordion-content" aria-hidden="true"></div>' +
'</div>';
// Replace content with accordion structure
$topRow.empty().append(accordionHTML);
// Insert original menu content into accordions
$topRow.find('.icannwiki-accordion-section:eq(0) .icannwiki-accordion-content').append($primaryMenu);
$topRow.find('.icannwiki-accordion-section:eq(1) .icannwiki-accordion-content').append($secondaryMenu);
// Add click handlers
$topRow.find('.icannwiki-accordion-header').on('click', function() {
var $header = $(this);
var $content = $header.next('.icannwiki-accordion-content');
var isOpen = $header.hasClass('active');
// Toggle active state
$header.toggleClass('active');
$content.toggleClass('active');
// Update ARIA attributes
$header.attr('aria-expanded', !isOpen);
$content.attr('aria-hidden', isOpen);
});
// Mark as ready immediately - this triggers MainPage.css to show content
$topRow.addClass('mobile-ready');
console.log("Accordion setup complete - marked as ready");
}
// Initialize on page load
setupAccordionMenus();
});
// 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);
});
// ANCHOR: Page Creator
// SHARED CODEBASE
function fetchTemplateList(callback, errorCallback) {
var api = new mw.Api();
api.parse('{{#invoke:TemplateStarter|listTemplates}}')
.done(function(html) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var templateListText = tempDiv.textContent || tempDiv.innerText || '';
var availableTemplates = templateListText.split(',').map(function(t) {
return t.trim().split('\n')[0].trim();
}).filter(function(t) {
return t.length > 0 && t.match(/^[a-zA-Z][a-zA-Z0-9_\-\s\(\)]*$/);
});
callback(availableTemplates);
})
.fail(function(error) {
if (errorCallback) errorCallback(error);
});
}
function fetchTemplateContent(templateType, callback, errorCallback) {
var api = new mw.Api();
api.parse('{{#invoke:TemplateStarter|preload|' + templateType + '}}')
.done(function(html) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var templateContent = tempDiv.textContent || tempDiv.innerText || '';
callback(templateContent);
})
.fail(function(error) {
if (errorCallback) errorCallback(error);
});
}
function cleanupUnfilledPlaceholders(templateContent, pageName, formData) {
var processedContent = templateContent;
if (!pageName || pageName.trim() === '') {
throw new Error('Page name is required and cannot be empty');
}
processedContent = processedContent.replace(/\$PAGE_NAME\$/g, pageName);
for (var key in formData) {
if (key !== 'PAGE_NAME' && formData[key] && formData[key].trim() !== '') {
var placeholder = '$' + key + '$';
var escapedPlaceholder = placeholder.replace(/\$/g, '\\$');
processedContent = processedContent.replace(new RegExp(escapedPlaceholder, 'g'), formData[key]);
}
}
processedContent = processedContent.replace(/\$[A-Z_]+\$/g, '');
processedContent = processedContent.replace(/\s+/g, ' ');
processedContent = processedContent.replace(/is a\s+based/g, 'is based');
processedContent = processedContent.replace(/is a\s+in/g, 'is in');
processedContent = processedContent.replace(/\s+\./g, '.');
processedContent = processedContent.trim();
return processedContent;
}
// HERO PAGE CREATOR WIDGET
mw.loader.using(['mediawiki.api', 'mediawiki.util'], function() {
$(function() {
// Only run if the hero page creator container exists
if ($('#hero-page-creator').length === 0) return;
console.log('Initializing page creator widget');
// Build the simple HTML form
var formHtml =
'<div class="icannwiki-search-unifier">' +
'<div class="mw-inputbox-element">' +
'<input type="text" id="hero-page-name" class="searchboxInput" placeholder="Page name">' +
'<select id="hero-template-type" class="searchboxInput" disabled>' +
'<option value="">Loading...</option>' +
'</select>' +
'<button id="hero-create-btn" class="mw-ui-button mw-ui-progressive" disabled>Create</button>' +
'</div>' +
'</div>';
$('#hero-page-creator').html(formHtml);
// Fetch available templates using shared function
fetchTemplateList(
function(availableTemplates) {
var $select = $('#hero-template-type');
$select.empty().append('<option value="">Select a subject (template)</option>');
availableTemplates.forEach(function(template) {
$select.append('<option value="' + template + '">' + template + '</option>');
});
$select.prop('disabled', false);
},
function() {
$('#hero-template-type').empty().append('<option value="">Error</option>');
}
);
// Enable/disable create button
function updateHeroCreateButton() {
var pageName = $('#hero-page-name').val().trim();
var templateType = $('#hero-template-type').val();
$('#hero-create-btn').prop('disabled', !pageName || !templateType);
}
$('#hero-page-name, #hero-template-type').on('input change', updateHeroCreateButton);
// Handle create button click
$('#hero-create-btn').on('click', function() {
var pageName = $('#hero-page-name').val().trim();
var templateType = $('#hero-template-type').val();
if (!pageName || !templateType) return;
$(this).prop('disabled', true).text('Creating...');
fetchTemplateContent(templateType, function(templateContent) {
try {
var formData = {};
templateContent = cleanupUnfilledPlaceholders(templateContent, pageName, formData);
} catch (error) {
alert('Error: ' + error.message);
$('#hero-create-btn').prop('disabled', false).text('Create');
return;
}
var api = new mw.Api();
api.create(pageName, { summary: 'Creating new ' + templateType + ' page' }, templateContent)
.done(function() {
window.location.href = mw.util.getUrl(pageName, { veaction: 'edit' });
})
.fail(function(code) {
if (code === 'articleexists') {
if (confirm('Page already exists. Do you want to edit it?')) {
window.location.href = mw.util.getUrl(pageName, { action: 'edit' });
}
} else {
alert('Error creating page: ' + code);
}
$('#hero-create-btn').prop('disabled', false).text('Create');
});
}, function() {
alert('Error loading template content.');
$('#hero-create-btn').prop('disabled', false).text('Create');
});
});
});
});
// CREATE A PAGE METAPAGE
mw.loader.using(['mediawiki.api', 'mediawiki.util'], function() {
$(function() {
// Only run on pages that have the template creator container
if ($('#template-creator-container').length === 0) return;
console.log('Initializing TemplateStarter');
// Build the initial HTML with loading state
var formHtml =
'<div class="template-creator-main-layout">' +
'<div class="template-creator-form-container">' +
'<h3>Create New Page</h3>' +
'<div class="form-group">' +
'<label for="template-type">Template Type:</label>' +
'<select id="template-type" class="form-control" disabled>' +
'<option value="">Loading templates...</option>' +
'</select>' +
'</div>' +
'<div class="form-group">' +
'<label for="page-name">Page Name:</label>' +
'<input type="text" id="page-name" class="form-control" placeholder="Enter page name...">' +
'</div>' +
'<div id="dynamic-fields-container"></div>' +
'<button id="create-page-btn" class="mw-ui-button mw-ui-progressive" disabled>Create Page</button>' +
'</div>' +
'<div id="template-preview" class="template-creator-preview-container" style="display:none;">' +
'<h4>Preview:</h4>' +
'<pre id="template-preview-content"></pre>' +
'</div>' +
'</div>';
// Insert the form
$('#template-creator-container').html(formHtml);
// Fetch available templates using shared function
fetchTemplateList(
function(availableTemplates) {
var $select = $('#template-type');
$select.empty().append('<option value="">Select a template...</option>');
availableTemplates.forEach(function(template) {
$select.append('<option value="' + template + '">' + template + '</option>');
});
$select.prop('disabled', false);
console.log('Loaded templates dynamically:', availableTemplates);
},
function(error) {
console.error('Failed to load templates dynamically:', error);
$('#template-type').empty().append('<option value="">Error loading templates</option>').prop('disabled', true);
}
);
// Enable/disable create button based on form completion
function updateCreateButton() {
var templateType = $('#template-type').val();
var pageName = $('#page-name').val().trim();
$('#create-page-btn').prop('disabled', !templateType || !pageName);
}
// Update button state when inputs change
$('#template-type, #page-name').on('change input', updateCreateButton);
// Preview template when type is selected
$('#template-type').on('change', function() {
var templateType = $(this).val();
if (!templateType) {
$('#template-preview').hide();
$('#dynamic-fields-container').empty();
return;
}
// Load dynamic fields for this template type
loadDynamicFields(templateType);
updatePreview();
});
// Update preview when page name changes
$('#page-name').on('input', function() {
var templateType = $('#template-type').val();
if (templateType) {
updatePreview();
}
});
// Function to update the preview with processed placeholders
function updatePreview() {
var templateType = $('#template-type').val();
var pageName = $('#page-name').val().trim();
if (!templateType) return;
// Call the Lua module to get template structure
var api = new mw.Api();
api.parse('{{#invoke:TemplateStarter|preload|' + templateType + '}}')
.done(function(html) {
// Extract text content from the parsed HTML
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var templateContent = tempDiv.textContent || tempDiv.innerText || '';
// Collect form data and process placeholders
var formData = collectFormData();
templateContent = processPlaceholders(templateContent, pageName, formData);
$('#template-preview-content').text(templateContent);
$('#template-preview').show();
})
.fail(function(error) {
console.error('Failed to preview template:', error);
$('#template-preview-content').text('Error loading template preview');
$('#template-preview').show();
});
}
// Function to load dynamic fields for a template type
function loadDynamicFields(templateType) {
console.log('Loading dynamic fields for template:', templateType);
var api = new mw.Api();
api.parse('{{#invoke:TemplateStarter|getCreatorFieldDefinitionsJSON|' + templateType + '}}')
.done(function(html) {
// Extract JSON content from the parsed HTML
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var jsonText = tempDiv.textContent || tempDiv.innerText || '';
try {
var fieldDefinitions = JSON.parse(jsonText);
console.log('Field definitions loaded:', fieldDefinitions);
// Generate form fields
generateDynamicFields(fieldDefinitions);
} catch (e) {
console.error('Failed to parse field definitions JSON:', e, jsonText);
$('#dynamic-fields-container').html('<p style="color: red;">Error loading field definitions</p>');
}
})
.fail(function(error) {
console.error('Failed to load field definitions:', error);
$('#dynamic-fields-container').html('<p style="color: red;">Error loading field definitions</p>');
});
}
// Function to generate dynamic form fields
function generateDynamicFields(fieldDefinitions) {
var $container = $('#dynamic-fields-container');
$container.empty();
// Skip PAGE_NAME as it's handled separately
for (var fieldKey in fieldDefinitions) {
if (fieldKey === 'PAGE_NAME') continue;
var fieldDef = fieldDefinitions[fieldKey];
var $formGroup = $('<div class="form-group"></div>');
// Create label
var $label = $('<label></label>')
.attr('for', 'dynamic-field-' + fieldKey)
.text(fieldDef.label);
// Create input
var $input = $('<input type="text" class="form-control dynamic-field">')
.attr('id', 'dynamic-field-' + fieldKey)
.attr('data-field-key', fieldKey)
.attr('placeholder', fieldDef.placeholder);
// Mark required fields
if (fieldDef.required) {
$label.append(' <span style="color: red;">*</span>');
$input.attr('required', true);
}
$formGroup.append($label).append($input);
$container.append($formGroup);
// Add event listener for preview updates
$input.on('input', function() {
var templateType = $('#template-type').val();
if (templateType) {
updatePreview();
}
});
}
// Update create button state when dynamic fields change
$container.on('input', '.dynamic-field', updateCreateButton);
console.log('Dynamic fields generated');
}
// Function to collect form data from dynamic fields
function collectFormData() {
var formData = {};
// Only collect dynamic field values (PAGE_NAME is handled separately)
$('.dynamic-field').each(function() {
var $field = $(this);
var fieldKey = $field.data('field-key');
var value = $field.val().trim();
if (value) {
formData[fieldKey] = value;
}
});
return formData;
}
// Function to process $VARIABLE$ placeholders
function processPlaceholders(templateContent, pageName, formData) {
var processedContent = templateContent;
// Replace $PAGE_NAME$ with the actual page name
if (pageName) {
processedContent = processedContent.replace(/\$PAGE_NAME\$/g, pageName);
}
// Replace other placeholders with form data
for (var key in formData) {
if (key !== 'PAGE_NAME' && formData[key]) {
var placeholder = '$' + key + '$';
// Properly escape the $ characters for regex
var escapedPlaceholder = placeholder.replace(/\$/g, '\\$');
processedContent = processedContent.replace(new RegExp(escapedPlaceholder, 'g'), formData[key]);
}
}
return processedContent;
}
// Handle create button click
$('#create-page-btn').on('click', function() {
var templateType = $('#template-type').val();
var pageName = $('#page-name').val().trim();
if (!templateType || !pageName) {
alert('Please select a template type and enter a page name');
return;
}
// Disable button to prevent double-clicks
$(this).prop('disabled', true).text('Creating...');
fetchTemplateContent(templateType, function(templateContent) {
var formData = collectFormData();
try {
var processedContent = cleanupUnfilledPlaceholders(templateContent, pageName, formData);
} catch (error) {
alert('Error: ' + error.message);
$('#create-page-btn').prop('disabled', false).text('Create Page');
return;
}
var api = new mw.Api();
api.create(pageName, { summary: 'Creating new ' + templateType + ' page' }, processedContent)
.done(function() {
window.location.href = mw.util.getUrl(pageName, { veaction: 'edit' });
})
.fail(function(code, error) {
if (code === 'articleexists') {
if (confirm('Page already exists. Do you want to edit it and add the template?')) {
window.location.href = mw.util.getUrl(pageName, { veaction: 'edit' });
}
} else {
console.error('Failed to create page:', code, error);
alert('Error creating page: ' + (error && error.error ? error.error.info : code));
}
$('#create-page-btn').prop('disabled', false).text('Create Page');
});
}, function(error) {
console.error('Failed to get template:', error);
alert('Error loading template. Please try again.');
$('#create-page-btn').prop('disabled', false).text('Create Page');
});
});
console.log('TemplateStarter initialized');
});
});