Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Citizen.js: Difference between revisions

MediaWiki interface page
No edit summary
No edit summary
Line 1: Line 1:
/**
/**
  * Dashboard Quick-Jump (v4 - Bulletproof)
  * Dashboard Quick-Jump (v5 - ES5 Compatible)
  * Listens for typing on the Dashboard page and jumps to matching cards.
  * Listens for typing on the Dashboard page and jumps to matching cards.
  * Wrapped in strict safety guards to prevent breaking the editor.
  * Replaced modern syntax to appease the MediaWiki ResourceLoader gods.
  */
  */
$(function() { // Wait for the DOM to be fully ready
$(function() {  
     try {
     try {
         // 1. EDIT MODE GUARD (URL Check)
         // 1. EDIT MODE GUARD (URL Check)
         // If the URL contains 'veaction' (VisualEditor) or 'action=edit', stop immediately.
         // Check if we are trying to edit.
         // We use simple string checking here for maximum compatibility.
         var search = window.location.search;
         if (window.location.search.indexOf('veaction') !== -1) return;
         if (search.indexOf('veaction') !== -1) return;
         if (window.location.search.indexOf('action=edit') !== -1) return;
         if (search.indexOf('action=edit') !== -1) return;
         if (window.location.search.indexOf('action=submit') !== -1) return;
         if (search.indexOf('action=submit') !== -1) return;


         // 2. CONTEXT GUARD
         // 2. CONTEXT GUARD
         // If there are no dashboard cards, exit.
         // If there are no dashboard cards, exit.
         if (!document.querySelector('.dashboard-card')) return;
         var cards = document.querySelectorAll('.dashboard-card');
        if (cards.length === 0) return;


         const CONFIG = {
         var CONFIG = {
             selectorCard: '.dashboard-card',
             selectorCard: '.dashboard-card',
             selectorLabel: '.dashboard-label',
             selectorLabel: '.dashboard-label',
Line 26: Line 27:
         };
         };


         let searchBuffer = '';
         var searchBuffer = '';
         let clearTimer = null;
         var clearTimer = null;


         document.addEventListener('keydown', function(e) {
         document.addEventListener('keydown', function(e) {
             // 3. EDIT INTERFACE GUARD (Dynamic)
             // 3. EDIT INTERFACE GUARD (Dynamic)
             // Even if the URL looked safe, check if an editor overlay is actually open.
             // Check for active editor classes or elements
             if (document.documentElement.classList.contains('ve-active') ||  
             if (document.documentElement.classList.contains('ve-active') ||  
                 document.querySelector('.ve-ui-surface') ||  
                 document.querySelector('.ve-ui-surface') ||  
Line 40: Line 41:
             // 4. INPUT GUARD
             // 4. INPUT GUARD
             // Don't intercept typing in search bars or inputs
             // Don't intercept typing in search bars or inputs
             const target = e.target;
             var target = e.target;
             if (target.tagName === 'INPUT' ||  
            var tagName = target.tagName;
                 target.tagName === 'TEXTAREA' ||  
             if (tagName === 'INPUT' ||  
                 target.tagName === 'SELECT' ||  
                 tagName === 'TEXTAREA' ||  
                 tagName === 'SELECT' ||  
                 target.isContentEditable) {
                 target.isContentEditable) {
                 return;
                 return;
Line 59: Line 61:


             if (e.key === 'Enter') {
             if (e.key === 'Enter') {
                 const active = document.querySelector(`.${CONFIG.classActive} ${CONFIG.selectorLink}`);
                 var active = document.querySelector('.' + CONFIG.classActive + ' ' + CONFIG.selectorLink);
                 if (active) {
                 if (active) {
                     e.preventDefault();
                     e.preventDefault();
Line 87: Line 89:
         function updateSelection() {
         function updateSelection() {
             // Clear previous highlights
             // Clear previous highlights
             document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
             var actives = document.querySelectorAll('.' + CONFIG.classActive);
            for (var i = 0; i < actives.length; i++) {
                actives[i].classList.remove(CONFIG.classActive);
            }


             if (!searchBuffer) return;
             if (!searchBuffer) return;


            const cards = document.querySelectorAll(CONFIG.selectorCard);
             // Escape special regex chars
             // Escape special regex chars to avoid crashes
             var safeBuffer = searchBuffer.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
             const safeBuffer = searchBuffer.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
             var regex = new RegExp('^' + safeBuffer, 'i');  
             const regex = new RegExp(`^${safeBuffer}`, 'i');  


             for (const card of cards) {
             for (var j = 0; j < cards.length; j++) {
                 const label = card.querySelector(CONFIG.selectorLabel)?.innerText.trim() || '';
                 var card = cards[j];
                 const desc = card.querySelector(CONFIG.selectorDesc)?.innerText.trim() || '';
                var labelEl = card.querySelector(CONFIG.selectorLabel);
                var descEl = card.querySelector(CONFIG.selectorDesc);
               
                // Old school null checks (No optional chaining ?.)
                var label = labelEl ? labelEl.innerText.trim() : '';
                 var desc = descEl ? descEl.innerText.trim() : '';


                 // Priority 1: Label match
                 // Priority 1: Label match
Line 120: Line 129:
         function resetSearch() {
         function resetSearch() {
             searchBuffer = '';
             searchBuffer = '';
             document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
             var actives = document.querySelectorAll('.' + CONFIG.classActive);
            for (var i = 0; i < actives.length; i++) {
                actives[i].classList.remove(CONFIG.classActive);
            }
         }
         }
      
      

Revision as of 00:22, 2 February 2026

/**
 * Dashboard Quick-Jump (v5 - ES5 Compatible)
 * Listens for typing on the Dashboard page and jumps to matching cards.
 * Replaced modern syntax to appease the MediaWiki ResourceLoader gods.
 */
$(function() { 
    try {
        // 1. EDIT MODE GUARD (URL Check)
        // Check if we are trying to edit.
        var search = window.location.search;
        if (search.indexOf('veaction') !== -1) return;
        if (search.indexOf('action=edit') !== -1) return;
        if (search.indexOf('action=submit') !== -1) return;

        // 2. CONTEXT GUARD
        // If there are no dashboard cards, exit.
        var cards = document.querySelectorAll('.dashboard-card');
        if (cards.length === 0) return;

        var CONFIG = {
            selectorCard: '.dashboard-card',
            selectorLabel: '.dashboard-label',
            selectorDesc: '.dashboard-desc',
            selectorLink: 'a',
            classActive: 'dashboard-jump-active',
            timeout: 1500
        };

        var searchBuffer = '';
        var clearTimer = null;

        document.addEventListener('keydown', function(e) {
            // 3. EDIT INTERFACE GUARD (Dynamic)
            // Check for active editor classes or elements
            if (document.documentElement.classList.contains('ve-active') || 
                document.querySelector('.ve-ui-surface') || 
                document.querySelector('.wikiEditor-ui')) {
                return;
            }

            // 4. INPUT GUARD
            // Don't intercept typing in search bars or inputs
            var target = e.target;
            var tagName = target.tagName;
            if (tagName === 'INPUT' || 
                tagName === 'TEXTAREA' || 
                tagName === 'SELECT' || 
                target.isContentEditable) {
                return;
            }

            // 5. MODIFIER GUARD
            if (e.ctrlKey || e.altKey || e.metaKey) return;

            // --- Logic ---
            
            if (e.key === 'Escape') {
                resetSearch();
                return;
            }

            if (e.key === 'Enter') {
                var active = document.querySelector('.' + CONFIG.classActive + ' ' + CONFIG.selectorLink);
                if (active) {
                    e.preventDefault();
                    e.stopPropagation();
                    active.click();
                }
                return;
            }

            if (e.key === 'Backspace') {
                searchBuffer = searchBuffer.slice(0, -1);
                if (searchBuffer.length === 0) resetSearch();
                else updateSelection();
                return;
            }

            // Capture single char keys (letters/numbers)
            if (e.key.length === 1) {
                searchBuffer += e.key;
                updateSelection();
                
                clearTimeout(clearTimer);
                clearTimer = setTimeout(resetSearch, CONFIG.timeout);
            }
        });

        function updateSelection() {
            // Clear previous highlights
            var actives = document.querySelectorAll('.' + CONFIG.classActive);
            for (var i = 0; i < actives.length; i++) {
                actives[i].classList.remove(CONFIG.classActive);
            }

            if (!searchBuffer) return;

            // Escape special regex chars
            var safeBuffer = searchBuffer.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            var regex = new RegExp('^' + safeBuffer, 'i'); 

            for (var j = 0; j < cards.length; j++) {
                var card = cards[j];
                var labelEl = card.querySelector(CONFIG.selectorLabel);
                var descEl = card.querySelector(CONFIG.selectorDesc);
                
                // Old school null checks (No optional chaining ?.)
                var label = labelEl ? labelEl.innerText.trim() : '';
                var desc = descEl ? descEl.innerText.trim() : '';

                // Priority 1: Label match
                if (label && regex.test(label)) {
                    activateCard(card);
                    return;
                }
                // Priority 2: Description match
                if (desc && regex.test(desc)) {
                    activateCard(card);
                    return;
                }
            }
        }

        function activateCard(card) {
            card.classList.add(CONFIG.classActive);
            card.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }

        function resetSearch() {
            searchBuffer = '';
            var actives = document.querySelectorAll('.' + CONFIG.classActive);
            for (var i = 0; i < actives.length; i++) {
                actives[i].classList.remove(CONFIG.classActive);
            }
        }
    
    } catch (err) {
        console.error('Hallyu Dashboard Script Error:', err);
    }
});