Jump to content

MediaWiki:Common.js: Difference between revisions

From Apogea Wiki
Dane (talk | contribs)
No edit summary
Dane (talk | contribs)
Sprite preview: no upscale for NPC, cap at 256x256, remove redundant image-rendering (via update-page on MediaWiki MCP Server)
 
Line 179: Line 179:
                 fetchSprite(spriteName, function(spriteData) {
                 fetchSprite(spriteName, function(spriteData) {
                     if (spriteData) {
                     if (spriteData) {
                         var scaledWidth = (spriteData.width || 32) * 2;
                         var scaledWidth = spriteData.width || 32;
                         var scaledHeight = (spriteData.height || 32) * 2;
                         var scaledHeight = spriteData.height || 32;
                         preview.innerHTML = '<img src="' + spriteData.url + '" alt="' + spriteName + '" class="pixel-sprite" style="image-rendering: pixelated; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
 
                        // Cap at 256x256
                        var maxSize = 256;
                        if (scaledWidth > maxSize || scaledHeight > maxSize) {
                            var scale = maxSize / Math.max(scaledWidth, scaledHeight);
                            scaledWidth = Math.floor(scaledWidth * scale);
                            scaledHeight = Math.floor(scaledHeight * scale);
                        }
 
                         preview.innerHTML = '<img src="' + spriteData.url + '" alt="' + spriteName + '" class="pixel-sprite" style="width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                     } else {
                     } else {
                         preview.innerHTML = '<span style="color: #c33;">Sprite not found: ' + spriteName + '.png</span>';
                         preview.innerHTML = '<span style="color: #c33;">Sprite not found: ' + spriteName + '.png</span>';
Line 250: Line 259:
                         var scaledWidth = (spriteData.width || 32) * 2;
                         var scaledWidth = (spriteData.width || 32) * 2;
                         var scaledHeight = (spriteData.height || 32) * 2;
                         var scaledHeight = (spriteData.height || 32) * 2;
                         previewDiv.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="image-rendering: pixelated; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                         previewDiv.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                     } else {
                     } else {
                         previewDiv.innerHTML = '<span style="color: var(--text-muted, #888); font-size: 12px;">No sprite</span>';
                         previewDiv.innerHTML = '<span style="color: var(--text-muted, #888); font-size: 12px;">No sprite</span>';
Line 470: Line 479:
     function initNPCForm() {
     function initNPCForm() {
         // Try immediately
         // Try immediately
         var found = initShopItemPreview();
         var spriteFound = initSpritePreview();
        var shopFound = initShopItemPreview();
          
          
         // If not found, wait for page to settle and try again
         // If not found, wait for page to settle and try again
         if (!found) {
         if (!spriteFound || !shopFound) {
             onPageSettled(function() {
             onPageSettled(function() {
                initSpritePreview();
                 initShopItemPreview();
                 initShopItemPreview();
             });
             });
Line 484: Line 495:
             clearTimeout(observerTimeout);
             clearTimeout(observerTimeout);
             observerTimeout = setTimeout(function() {
             observerTimeout = setTimeout(function() {
                initSpritePreview();
                 initShopItemPreview();
                 initShopItemPreview();
             }, 300);
             }, 300);
Line 579: Line 591:
                     var scaledWidth = (spriteData.width || 32) * 2;
                     var scaledWidth = (spriteData.width || 32) * 2;
                     var scaledHeight = (spriteData.height || 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;">';
                     previewContainer.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                 } else {
                 } else {
                     previewContainer.innerHTML = '<span style="color: var(--text-muted, #888);">No sprite found for "' + itemName + '"</span>';
                     previewContainer.innerHTML = '<span style="color: var(--text-muted, #888);">No sprite found for "' + itemName + '"</span>';

Latest revision as of 22:08, 31 January 2026

/**
 * Apogea Wiki Common JavaScript
 */

(function() {
    'use strict';

    /**
     * Wait for page to "settle" - fires callback after DOM stops changing
     * Useful for waiting for async widgets (OOUI, etc.) to finish loading
     */
    function onPageSettled(callback, options) {
        options = options || {};
        var debounceMs = options.debounce || 300;
        var maxWaitMs = options.maxWait || 30000;
        var startTime = Date.now();
        var timeout;
        var settled = false;
        
        function done() {
            if (settled) return;
            settled = true;
            observer.disconnect();
            callback();
        }
        
        var observer = new MutationObserver(function() {
            clearTimeout(timeout);
            
            // If we've waited too long, just fire anyway
            if (Date.now() - startTime > maxWaitMs) {
                done();
                return;
            }
            
            timeout = setTimeout(done, debounceMs);
        });
        
        observer.observe(document.body, { childList: true, subtree: true });
        
        // Start the timer - if nothing changes, fire after debounce period
        timeout = setTimeout(done, debounceMs);
    }

    /**
     * Watch an input for value changes (handles programmatic changes from OOUI)
     * Calls callback when value changes, with debouncing
     */
    function watchInputValue(input, callback, debounceMs) {
        debounceMs = debounceMs || 300;
        var lastValue = input.value;
        var timeout;
        
        function checkValue() {
            if (input.value !== lastValue) {
                lastValue = input.value;
                clearTimeout(timeout);
                timeout = setTimeout(callback, debounceMs);
            }
        }
        
        // Poll for value changes (catches OOUI programmatic updates)
        setInterval(checkValue, 150);
        
        // Also listen to native events for immediate response to user typing
        input.addEventListener('input', function() {
            lastValue = input.value; // Sync to avoid double-firing
            clearTimeout(timeout);
            timeout = setTimeout(callback, debounceMs);
        });
    }

    /**
     * Fetch and display a sprite image by name
     * Sprite files should be named "{Name}.png" matching the item/entity name
     */
    function fetchSprite(spriteName, callback) {
        if (!spriteName) {
            callback(null);
            return;
        }
        
        var fileName = spriteName.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) {
                var pages = data.query.pages;
                for (var id in pages) {
                    if (pages[id].imageinfo && pages[id].imageinfo[0]) {
                        callback({
                            url: pages[id].imageinfo[0].url,
                            width: pages[id].imageinfo[0].width,
                            height: pages[id].imageinfo[0].height
                        });
                        return;
                    }
                }
                callback(null);
            },
            error: function() {
                callback(null);
            }
        });
    }

    /**
     * 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;
                }
                
                fetchSprite(spriteName, function(spriteData) {
                    if (spriteData) {
                        var scaledWidth = spriteData.width || 32;
                        var scaledHeight = spriteData.height || 32;

                        // Cap at 256x256
                        var maxSize = 256;
                        if (scaledWidth > maxSize || scaledHeight > maxSize) {
                            var scale = maxSize / Math.max(scaledWidth, scaledHeight);
                            scaledWidth = Math.floor(scaledWidth * scale);
                            scaledHeight = Math.floor(scaledHeight * scale);
                        }

                        preview.innerHTML = '<img src="' + spriteData.url + '" alt="' + spriteName + '" class="pixel-sprite" style="width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                    } else {
                        preview.innerHTML = '<span style="color: #c33;">Sprite not found: ' + spriteName + '.png</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;
    }

    /**
     * Shop item preview for NPC forms
     */
    function initShopItemPreview() {
        var containers = document.querySelectorAll('.shop-entry-container');
        var found = false;
        
        containers.forEach(function(container) {
            var previewDiv = container.querySelector('.shop-item-preview');
            if (!previewDiv) return;
            
            // Find the item input field within this container
            var itemInput = container.querySelector('input[name*="[item]"]');
            if (!itemInput) return;
            
            // Skip template containers
            if (itemInput.getAttribute('name').indexOf('[num]') !== -1) return;
            
            // Skip if already initialized
            if (previewDiv.dataset.initialized) return;
            previewDiv.dataset.initialized = 'true';
            
            found = true;
            console.log('[Apogea] Initializing shop item preview for:', itemInput.getAttribute('name'));
            
            function updatePreview() {
                var itemName = itemInput.value.trim();
                
                if (!itemName) {
                    previewDiv.innerHTML = '';
                    return;
                }
                
                // Sprite name matches item name
                fetchSprite(itemName, function(spriteData) {
                    if (spriteData) {
                        var scaledWidth = (spriteData.width || 32) * 2;
                        var scaledHeight = (spriteData.height || 32) * 2;
                        previewDiv.innerHTML = '<img src="' + spriteData.url + '" alt="' + itemName + '" style="width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;">';
                    } else {
                        previewDiv.innerHTML = '<span style="color: var(--text-muted, #888); font-size: 12px;">No sprite</span>';
                    }
                });
            }
            
            // Watch for value changes (handles OOUI combobox)
            watchInputValue(itemInput, updatePreview, 300);
            
            // 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;
    }

    /**
     * Initialize Item Form features (only on FormEdit/Item pages)
     */
    function initItemForm() {
        // Try immediately
        var spriteFound = initSpritePreview();
        var infoboxFound = initInfoboxPreview();
        
        // If not found, wait for page to settle and try again
        if (!spriteFound || !infoboxFound) {
            onPageSettled(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) {
            clearTimeout(observerTimeout);
            observerTimeout = setTimeout(function() {
                initSpritePreview();
                initInfoboxPreview();
            }, 300);
        });
        
        observer.observe(document.body, { childList: true, subtree: true });
    }

    /**
     * Initialize NPC Form features
     */
    function initNPCForm() {
        // Try immediately
        var spriteFound = initSpritePreview();
        var shopFound = initShopItemPreview();
        
        // If not found, wait for page to settle and try again
        if (!spriteFound || !shopFound) {
            onPageSettled(function() {
                initSpritePreview();
                initShopItemPreview();
            });
        }
        
        // Watch for dynamically added shop entries
        var observerTimeout;
        var observer = new MutationObserver(function(mutations) {
            clearTimeout(observerTimeout);
            observerTimeout = setTimeout(function() {
                initSpritePreview();
                initShopItemPreview();
            }, 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() {
        console.log('[Apogea] initEditItemPage called');
        
        // Find the text input - it's inside .pfPageNameWithoutNamespace with name="page_name"
        var textInput = document.querySelector('.pfPageNameWithoutNamespace input[name="page_name"]');
        
        // Find the submit button and its label
        var submitButton = document.querySelector('.pfCreateOrEditButton button[type="submit"]');
        var buttonLabel = document.querySelector('.pfCreateOrEditButton .oo-ui-labelElement-label');
        
        console.log('[Apogea] Text input found:', !!textInput);
        console.log('[Apogea] Submit button found:', !!submitButton);
        
        if (!textInput || !submitButton) {
            console.log('[Apogea] Required elements not found yet');
            return false;
        }
        
        console.log('[Apogea] Edit Item page enhancements initializing');
        
        // Find the fieldset to insert after
        var fieldset = textInput.closest('fieldset');
        
        // Create status container
        var statusContainer = document.createElement('div');
        statusContainer.className = 'edit-item-status';
        statusContainer.style.cssText = 'margin: 10px 0; font-size: 14px;';
        
        // Create preview container
        var previewContainer = document.createElement('div');
        previewContainer.className = 'edit-item-preview';
        previewContainer.style.cssText = 'margin: 15px 0; min-height: 64px;';
        
        // Insert after the fieldset
        if (fieldset && fieldset.parentNode) {
            fieldset.parentNode.insertBefore(statusContainer, fieldset.nextSibling);
            fieldset.parentNode.insertBefore(previewContainer, statusContainer.nextSibling);
        }
        
        var originalButtonText = buttonLabel ? buttonLabel.textContent : 'Edit Item';
        
        function updatePreview() {
            var itemName = textInput.value.trim();
            console.log('[Apogea] updatePreview called, itemName:', itemName);
            
            if (!itemName) {
                previewContainer.innerHTML = '';
                statusContainer.innerHTML = '';
                if (buttonLabel) {
                    buttonLabel.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;
                
                console.log('[Apogea] Both checks complete - pageExists:', pageExists, 'spriteData:', spriteData);
                
                // Update button text
                var buttonText = pageExists ? 'Edit Item' : 'Add Item';
                if (buttonLabel) {
                    buttonLabel.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="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();
                }
            });
            
            // Sprite name matches item name
            fetchSprite(itemName, function(data) {
                spriteData = data;
                onCheckComplete();
            });
        }
        
        // Watch for value changes (handles OOUI autocomplete selection)
        watchInputValue(textInput, updatePreview, 300);
        
        // Initial check if there's already a value
        if (textInput.value.trim()) {
            updatePreview();
        }
        
        return true;
    }

    // Main initialization
    function init() {
        console.log('[Apogea] Common.js loaded');
        
        var pageName = mw.config.get('wgPageName');
        var canonicalSpecialPage = mw.config.get('wgCanonicalSpecialPageName');
        var action = mw.config.get('wgAction');
        
        console.log('[Apogea] pageName:', pageName, 'canonicalSpecialPage:', canonicalSpecialPage, 'action:', action);
        
		onPageSettled(function() {
			if (pageName == 'Apogea_Wiki:Edit_Item') {
				initEditItemPage();
			} else if (document.getElementsByClassName('item-form-fields')) {
				console.log('[Apogea] Item Edit form detected');
				initItemForm();
			} else if (pageName == 'Apogea_Wiki:Edit_NPC') {
				console.log('[Apogea] NPC form detected');
				initNPCForm();
			}
		}, { debounce: 500 });
    }

    // Wait for DOM and jQuery
    $(document).ready(init);

})();