MediaWiki:Common.js: Difference between revisions
Fix: Skip [num] template containers, track initialized variant index to handle cloned elements (via update-page on MediaWiki MCP Server) |
Add Edit Item page enhancements: sprite preview and dynamic Add/Edit button (via update-page on MediaWiki MCP Server) |
||
| Line 356: | Line 356: | ||
observer.observe(document.body, { childList: true, subtree: true }); | observer.observe(document.body, { childList: true, subtree: true }); | ||
} | |||
/** | |||
* Edit Item page enhancements | |||
* Shows sprite preview and changes button text based on page existence | |||
*/ | |||
function initEditItemPage() { | |||
// Find the forminput elements | |||
var formInput = document.querySelector('.pfFormInput'); | |||
if (!formInput) return; | |||
var textInput = formInput.querySelector('input[type="text"]'); | |||
var submitButton = formInput.querySelector('input[type="submit"], button[type="submit"]'); | |||
if (!textInput || !submitButton) return; | |||
console.log('[Apogea] Edit Item page detected, initializing enhancements'); | |||
// Create preview container | |||
var previewContainer = document.createElement('div'); | |||
previewContainer.className = 'edit-item-preview'; | |||
previewContainer.style.cssText = 'margin: 15px 0; min-height: 64px;'; | |||
formInput.parentNode.insertBefore(previewContainer, formInput.nextSibling); | |||
// Create status container | |||
var statusContainer = document.createElement('div'); | |||
statusContainer.className = 'edit-item-status'; | |||
statusContainer.style.cssText = 'margin: 10px 0; font-size: 14px;'; | |||
formInput.parentNode.insertBefore(statusContainer, previewContainer); | |||
var originalButtonText = submitButton.value || submitButton.textContent; | |||
function updatePreview() { | |||
var itemName = textInput.value.trim(); | |||
if (!itemName) { | |||
previewContainer.innerHTML = ''; | |||
statusContainer.innerHTML = ''; | |||
if (submitButton.tagName === 'INPUT') { | |||
submitButton.value = originalButtonText; | |||
} else { | |||
submitButton.textContent = originalButtonText; | |||
} | |||
return; | |||
} | |||
// Check both page existence and sprite existence in parallel | |||
var pageExists = null; | |||
var spriteData = null; | |||
var checksComplete = 0; | |||
function onCheckComplete() { | |||
checksComplete++; | |||
if (checksComplete < 2) return; | |||
// Update button text | |||
var buttonText = pageExists ? 'Edit Item' : 'Add Item'; | |||
if (submitButton.tagName === 'INPUT') { | |||
submitButton.value = buttonText; | |||
} else { | |||
submitButton.textContent = buttonText; | |||
} | |||
// Update status | |||
var statusHtml = ''; | |||
if (pageExists) { | |||
statusHtml = '<span style="color: var(--color-success, #7a9a5a);">✓ Page exists</span>'; | |||
} else { | |||
statusHtml = '<span style="color: var(--gold, #d4a84b);">⚠ New item (page does not exist)</span>'; | |||
} | |||
statusContainer.innerHTML = statusHtml; | |||
// Update sprite preview | |||
if (spriteData && spriteData.url) { | |||
var scaledWidth = (spriteData.width || 32) * 2; | |||
var scaledHeight = (spriteData.height || 32) * 2; | |||
previewContainer.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="image-rendering: pixelated; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">'; | |||
} else { | |||
previewContainer.innerHTML = '<span style="color: var(--text-muted, #888);">No sprite found for "' + itemName + '"</span>'; | |||
} | |||
} | |||
// Check if page exists | |||
$.ajax({ | |||
url: mw.util.wikiScript('api'), | |||
data: { | |||
action: 'query', | |||
titles: itemName, | |||
format: 'json' | |||
}, | |||
dataType: 'json', | |||
success: function(data) { | |||
pageExists = false; | |||
if (data.query && data.query.pages) { | |||
for (var id in data.query.pages) { | |||
if (id !== '-1' && !data.query.pages[id].missing) { | |||
pageExists = true; | |||
break; | |||
} | |||
} | |||
} | |||
onCheckComplete(); | |||
}, | |||
error: function() { | |||
pageExists = false; | |||
onCheckComplete(); | |||
} | |||
}); | |||
// Check if sprite exists | |||
var fileName = itemName.replace(/_/g, ' ') + '.png'; | |||
$.ajax({ | |||
url: mw.util.wikiScript('api'), | |||
data: { | |||
action: 'query', | |||
titles: 'File:' + fileName, | |||
prop: 'imageinfo', | |||
iiprop: 'url|size', | |||
format: 'json' | |||
}, | |||
dataType: 'json', | |||
success: function(data) { | |||
spriteData = null; | |||
if (data.query && data.query.pages) { | |||
for (var id in data.query.pages) { | |||
if (data.query.pages[id].imageinfo && data.query.pages[id].imageinfo[0]) { | |||
spriteData = { | |||
url: data.query.pages[id].imageinfo[0].url, | |||
width: data.query.pages[id].imageinfo[0].width, | |||
height: data.query.pages[id].imageinfo[0].height | |||
}; | |||
break; | |||
} | |||
} | |||
} | |||
onCheckComplete(); | |||
}, | |||
error: function() { | |||
spriteData = null; | |||
onCheckComplete(); | |||
} | |||
}); | |||
} | |||
// Debounced input handler | |||
var timeout; | |||
textInput.addEventListener('input', function() { | |||
clearTimeout(timeout); | |||
timeout = setTimeout(updatePreview, 400); | |||
}); | |||
// Also trigger on autocomplete selection | |||
textInput.addEventListener('change', function() { | |||
clearTimeout(timeout); | |||
timeout = setTimeout(updatePreview, 100); | |||
}); | |||
// Initial check if there's already a value | |||
if (textInput.value.trim()) { | |||
updatePreview(); | |||
} | |||
} | } | ||
| Line 369: | Line 530: | ||
console.log('[Apogea] Item form page detected'); | console.log('[Apogea] Item form page detected'); | ||
initItemForm(); | initItemForm(); | ||
} | |||
// Edit Item page enhancements | |||
if (pageName === 'Apogea_Wiki:Edit_Item') { | |||
console.log('[Apogea] Edit Item page detected'); | |||
initEditItemPage(); | |||
} | } | ||
} | } | ||
Revision as of 20:30, 29 January 2026
/**
* Apogea Wiki Common JavaScript
*/
(function() {
'use strict';
/**
* Sprite preview for Page Forms
*/
function initSpritePreview() {
var inputs = document.querySelectorAll('input');
var found = false;
inputs.forEach(function(input) {
var name = input.getAttribute('name') || '';
// Match sprite fields
if (name.indexOf('sprite]') === -1) return;
// Skip template fields (Page Forms uses [num] as placeholder)
if (name.indexOf('[num]') !== -1) return;
found = true;
console.log('[Apogea] Found sprite field:', name);
// Skip if already has preview
if (input.dataset.hasPreview) return;
input.dataset.hasPreview = 'true';
// Extract variant index from field name (e.g., ItemEntry[1b][sprite] -> 1b)
var indexMatch = name.match(/\[(\d+[a-z]?)\]/);
var variantIndex = indexMatch ? indexMatch[1] : null;
// Create preview container
var preview = document.createElement('div');
preview.className = 'sprite-preview';
preview.style.cssText = 'margin-top: 5px; min-height: 48px;';
input.parentNode.appendChild(preview);
// Find corresponding name field with same variant index
var nameInput = null;
if (variantIndex) {
nameInput = document.querySelector('input[name*="[' + variantIndex + '][name]"]');
} else {
// Fallback for non-indexed fields
var container = input.closest('tr') || input.closest('fieldset') || input.parentNode.parentNode;
if (container) {
var allInputs = container.parentNode.querySelectorAll('input');
allInputs.forEach(function(inp) {
var n = inp.getAttribute('name') || '';
if (n.indexOf('[name]') !== -1) {
nameInput = inp;
}
});
}
}
function updatePreview() {
var spriteName = input.value.trim();
// Fall back to name field if sprite is empty
if (!spriteName && nameInput) {
spriteName = nameInput.value.trim();
}
if (!spriteName) {
preview.innerHTML = '<span style="color: #888;">No preview</span>';
return;
}
// Replace underscores with spaces for the filename
var fileName = spriteName.replace(/_/g, ' ') + '.png';
// Use MediaWiki API to get the actual file URL and dimensions
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url|size',
format: 'json'
},
dataType: 'json',
success: function(data) {
var pages = data.query.pages;
var url = null;
var width = null;
var height = null;
for (var id in pages) {
if (pages[id].imageinfo && pages[id].imageinfo[0]) {
url = pages[id].imageinfo[0].url;
width = pages[id].imageinfo[0].width;
height = pages[id].imageinfo[0].height;
break;
}
}
if (url) {
// Scale up 2x
var scaledWidth = (width || 32) * 2;
var scaledHeight = (height || 32) * 2;
preview.innerHTML = '<img src="' + url + '" alt="' + spriteName + '" class="pixel-sprite" style="image-rendering: pixelated; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
} else {
preview.innerHTML = '<span style="color: #c33;">Sprite not found: ' + fileName + '</span>';
}
},
error: function() {
preview.innerHTML = '<span style="color: #888;">Preview unavailable</span>';
}
});
}
// Update on input change (debounced)
var timeout;
input.addEventListener('input', function() {
clearTimeout(timeout);
timeout = setTimeout(updatePreview, 500);
});
// Also update when name field changes
if (nameInput) {
nameInput.addEventListener('input', function() {
if (!input.value.trim()) {
clearTimeout(timeout);
timeout = setTimeout(updatePreview, 500);
}
});
}
// Initial preview
updatePreview();
});
return found;
}
/**
* Live infobox preview for item forms
*/
function initInfoboxPreview() {
var containers = document.querySelectorAll('.item-form-container');
console.log('[Apogea] initInfoboxPreview called, found containers:', containers.length);
var found = false;
containers.forEach(function(container, idx) {
var previewDiv = container.querySelector('.infobox-preview');
console.log('[Apogea] Container', idx, '- has previewDiv:', !!previewDiv);
if (!previewDiv) return;
// Find variant index from any field in this container
var anyField = container.querySelector('input[name*="["], select[name*="["]');
console.log('[Apogea] Container', idx, '- anyField:', anyField ? anyField.getAttribute('name') : 'none');
// Skip template containers (Page Forms uses [num] as placeholder)
if (anyField && anyField.getAttribute('name').indexOf('[num]') !== -1) {
console.log('[Apogea] Container', idx, '- skipping template container');
return;
}
var variantIndex = null;
if (anyField) {
var indexMatch = anyField.getAttribute('name').match(/\[(\d+[a-z]?)\]/);
variantIndex = indexMatch ? indexMatch[1] : null;
console.log('[Apogea] Container', idx, '- indexMatch:', indexMatch, '- variantIndex:', variantIndex);
}
// Check if already initialized for THIS variant (handles cloned elements from template)
var initializedVariant = previewDiv.dataset.initializedVariant;
console.log('[Apogea] Container', idx, '- initializedVariant:', initializedVariant, '- current variant:', variantIndex);
if (initializedVariant === variantIndex) {
console.log('[Apogea] Container', idx, '- already initialized for this variant, skipping');
return;
}
// Mark as initialized with the variant index
previewDiv.dataset.initializedVariant = variantIndex;
found = true;
console.log('[Apogea] Initializing infobox preview for variant:', variantIndex || 'default');
// Get all form fields in this container
var fields = container.querySelectorAll('input, select, textarea');
console.log('[Apogea] Container', idx, '- field count:', fields.length);
function getFieldValue(fieldName) {
var selector;
if (variantIndex) {
// Scoped to this variant's index
selector = '[name*="[' + variantIndex + '][' + fieldName + ']"]';
} else {
selector = '[name*="[' + fieldName + ']"]';
}
var field = container.querySelector(selector);
return field ? field.value.trim() : '';
}
function updateInfoboxPreview() {
console.log('[Apogea] updateInfoboxPreview called for variant:', variantIndex);
var name = getFieldValue('name') || mw.config.get('wgTitle') || 'Item';
var sprite = getFieldValue('sprite') || name;
var rarity = getFieldValue('rarity') || 'common';
var description = getFieldValue('description');
var damage = getFieldValue('damage');
var armor = getFieldValue('armor');
var defense = getFieldValue('defense');
var health = getFieldValue('health');
var mana = getFieldValue('mana');
var rng = getFieldValue('rng');
var attackSpeed = getFieldValue('attack_speed');
var moveSpeed = getFieldValue('move_speed');
var ability = getFieldValue('ability');
var magic = getFieldValue('magic');
var healthRegen = getFieldValue('health_regen');
var manaRegen = getFieldValue('mana_regen');
var weight = getFieldValue('weight');
var size = getFieldValue('size');
var slots = getFieldValue('container');
var type = getFieldValue('type');
var slot = getFieldValue('slot');
console.log('[Apogea] Variant', variantIndex, '- name:', name, 'sprite:', sprite);
// Build wikitext for preview with all params
var params = [
'preview=true',
'name=' + name,
'sprite=' + sprite,
'rarity=' + rarity,
'float=none'
];
if (description) params.push('description=' + description);
if (damage) params.push('damage=' + damage);
if (armor) params.push('armor=' + armor);
if (defense) params.push('defense=' + defense);
if (health) params.push('health=' + health);
if (mana) params.push('mana=' + mana);
if (rng) params.push('rng=' + rng);
if (attackSpeed) params.push('attack_speed=' + attackSpeed);
if (moveSpeed) params.push('move_speed=' + moveSpeed);
if (ability) params.push('ability=' + ability);
if (magic) params.push('magic=' + magic);
if (healthRegen) params.push('health_regen=' + healthRegen);
if (manaRegen) params.push('mana_regen=' + manaRegen);
if (weight) params.push('weight=' + weight);
if (size) params.push('size=' + size);
if (slots) params.push('container=' + slots);
if (type) params.push('type=' + type);
if (slot) params.push('slot=' + slot);
var wikitext = '{{Infobox Item|' + params.join('|') + '}}';
// Use API to parse the wikitext
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'parse',
text: wikitext,
contentmodel: 'wikitext',
prop: 'text',
format: 'json',
disablelimitreport: true
},
dataType: 'json',
success: function(data) {
if (data.parse && data.parse.text) {
previewDiv.innerHTML = data.parse.text['*'];
} else {
previewDiv.innerHTML = '<em style="color: #888;">Preview unavailable</em>';
}
},
error: function() {
previewDiv.innerHTML = '<em style="color: #888;">Preview unavailable</em>';
}
});
}
// Debounced update
var timeout;
console.log('[Apogea] Attaching listeners to', fields.length, 'fields for variant', variantIndex);
fields.forEach(function(field) {
field.addEventListener('input', function() {
console.log('[Apogea] Input event on field:', field.getAttribute('name'), 'for variant:', variantIndex);
clearTimeout(timeout);
timeout = setTimeout(updateInfoboxPreview, 800);
});
field.addEventListener('change', function() {
console.log('[Apogea] Change event on field:', field.getAttribute('name'), 'for variant:', variantIndex);
clearTimeout(timeout);
timeout = setTimeout(updateInfoboxPreview, 800);
});
});
// Initial preview immediately
updateInfoboxPreview();
});
return found;
}
// Poll for form elements until found
function waitForForm(callback, maxAttempts) {
var attempts = 0;
maxAttempts = maxAttempts || 20;
function check() {
attempts++;
var container = document.querySelector('.item-form-container');
if (container) {
console.log('[Apogea] Form found after ' + attempts + ' attempts');
callback();
} else if (attempts < maxAttempts) {
setTimeout(check, 250);
}
}
check();
}
/**
* Initialize Item Form features (only on FormEdit/Item pages)
*/
function initItemForm() {
// Try immediately
var spriteFound = initSpritePreview();
var infoboxFound = initInfoboxPreview();
// If not found, poll for form to load
if (!spriteFound || !infoboxFound) {
waitForForm(function() {
initSpritePreview();
initInfoboxPreview();
});
}
// Watch for dynamically added fields (e.g., "Add another" in Page Forms)
// Debounced to allow Page Forms to finish rendering new elements
var observerTimeout;
var observer = new MutationObserver(function(mutations) {
console.log('[Apogea] MutationObserver triggered, mutations:', mutations.length);
clearTimeout(observerTimeout);
observerTimeout = setTimeout(function() {
console.log('[Apogea] MutationObserver debounce fired, re-initializing...');
initSpritePreview();
initInfoboxPreview();
}, 300);
});
observer.observe(document.body, { childList: true, subtree: true });
}
/**
* Edit Item page enhancements
* Shows sprite preview and changes button text based on page existence
*/
function initEditItemPage() {
// Find the forminput elements
var formInput = document.querySelector('.pfFormInput');
if (!formInput) return;
var textInput = formInput.querySelector('input[type="text"]');
var submitButton = formInput.querySelector('input[type="submit"], button[type="submit"]');
if (!textInput || !submitButton) return;
console.log('[Apogea] Edit Item page detected, initializing enhancements');
// Create preview container
var previewContainer = document.createElement('div');
previewContainer.className = 'edit-item-preview';
previewContainer.style.cssText = 'margin: 15px 0; min-height: 64px;';
formInput.parentNode.insertBefore(previewContainer, formInput.nextSibling);
// Create status container
var statusContainer = document.createElement('div');
statusContainer.className = 'edit-item-status';
statusContainer.style.cssText = 'margin: 10px 0; font-size: 14px;';
formInput.parentNode.insertBefore(statusContainer, previewContainer);
var originalButtonText = submitButton.value || submitButton.textContent;
function updatePreview() {
var itemName = textInput.value.trim();
if (!itemName) {
previewContainer.innerHTML = '';
statusContainer.innerHTML = '';
if (submitButton.tagName === 'INPUT') {
submitButton.value = originalButtonText;
} else {
submitButton.textContent = originalButtonText;
}
return;
}
// Check both page existence and sprite existence in parallel
var pageExists = null;
var spriteData = null;
var checksComplete = 0;
function onCheckComplete() {
checksComplete++;
if (checksComplete < 2) return;
// Update button text
var buttonText = pageExists ? 'Edit Item' : 'Add Item';
if (submitButton.tagName === 'INPUT') {
submitButton.value = buttonText;
} else {
submitButton.textContent = buttonText;
}
// Update status
var statusHtml = '';
if (pageExists) {
statusHtml = '<span style="color: var(--color-success, #7a9a5a);">✓ Page exists</span>';
} else {
statusHtml = '<span style="color: var(--gold, #d4a84b);">⚠ New item (page does not exist)</span>';
}
statusContainer.innerHTML = statusHtml;
// Update sprite preview
if (spriteData && spriteData.url) {
var scaledWidth = (spriteData.width || 32) * 2;
var scaledHeight = (spriteData.height || 32) * 2;
previewContainer.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="image-rendering: pixelated; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
} else {
previewContainer.innerHTML = '<span style="color: var(--text-muted, #888);">No sprite found for "' + itemName + '"</span>';
}
}
// Check if page exists
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
titles: itemName,
format: 'json'
},
dataType: 'json',
success: function(data) {
pageExists = false;
if (data.query && data.query.pages) {
for (var id in data.query.pages) {
if (id !== '-1' && !data.query.pages[id].missing) {
pageExists = true;
break;
}
}
}
onCheckComplete();
},
error: function() {
pageExists = false;
onCheckComplete();
}
});
// Check if sprite exists
var fileName = itemName.replace(/_/g, ' ') + '.png';
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url|size',
format: 'json'
},
dataType: 'json',
success: function(data) {
spriteData = null;
if (data.query && data.query.pages) {
for (var id in data.query.pages) {
if (data.query.pages[id].imageinfo && data.query.pages[id].imageinfo[0]) {
spriteData = {
url: data.query.pages[id].imageinfo[0].url,
width: data.query.pages[id].imageinfo[0].width,
height: data.query.pages[id].imageinfo[0].height
};
break;
}
}
}
onCheckComplete();
},
error: function() {
spriteData = null;
onCheckComplete();
}
});
}
// Debounced input handler
var timeout;
textInput.addEventListener('input', function() {
clearTimeout(timeout);
timeout = setTimeout(updatePreview, 400);
});
// Also trigger on autocomplete selection
textInput.addEventListener('change', function() {
clearTimeout(timeout);
timeout = setTimeout(updatePreview, 100);
});
// Initial check if there's already a value
if (textInput.value.trim()) {
updatePreview();
}
}
// Main initialization
function init() {
console.log('[Apogea] Common.js loaded');
var pageName = mw.config.get('wgPageName');
var canonicalSpecialPage = mw.config.get('wgCanonicalSpecialPageName');
// Item form features - only on FormEdit pages for Item form
if (canonicalSpecialPage === 'FormEdit' && pageName.indexOf('Item') !== -1) {
console.log('[Apogea] Item form page detected');
initItemForm();
}
// Edit Item page enhancements
if (pageName === 'Apogea_Wiki:Edit_Item') {
console.log('[Apogea] Edit Item page detected');
initEditItemPage();
}
}
// Wait for DOM and jQuery
$(document).ready(init);
})();