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 (v3 - The "Shy" Version)
  * Dashboard Quick-Jump (v4 - Bulletproof)
  * Listens for typing on the Dashboard page and jumps to matching cards.
  * Listens for typing on the Dashboard page and jumps to matching cards.
  * Aggressively disables itself if ANY editing interface is detected.
  * Wrapped in strict safety guards to prevent breaking the editor.
  */
  */
(function() {
$(function() { // Wait for the DOM to be fully ready
     // 1. INITIAL LOAD GUARD
     try {
    // If we are already in a non-view action, or the URL indicates an edit intent, stop immediately.
        // 1. EDIT MODE GUARD (URL Check)
    const uri = new URL(window.location.href);
        // If the URL contains 'veaction' (VisualEditor) or 'action=edit', stop immediately.
    if (mw.config.get('wgAction') !== 'view') return;
        // We use simple string checking here for maximum compatibility.
    if (uri.searchParams.has('veaction')) return;
        if (window.location.search.indexOf('veaction') !== -1) return;
    if (uri.searchParams.has('action') && uri.searchParams.get('action') !== 'view') return;
        if (window.location.search.indexOf('action=edit') !== -1) return;
        if (window.location.search.indexOf('action=submit') !== -1) return;


    // 2. CONTEXT GUARD
        // 2. CONTEXT GUARD
    // If there are no dashboard cards, don't even add the listener.
        // If there are no dashboard cards, exit.
    if (!document.querySelector('.dashboard-card')) return;
        if (!document.querySelector('.dashboard-card')) return;


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


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


    document.addEventListener('keydown', function(e) {
        document.addEventListener('keydown', function(e) {
        // 3. DYNAMIC EDITOR DETECTION (The Fix)
            // 3. EDIT INTERFACE GUARD (Dynamic)
        // Check for VisualEditor, NWE, or Source Editor surfaces.
            // Even if the URL looked safe, check if an editor overlay is actually open.
        // If these exist, the user is editing. Do not interfere.
            if (document.documentElement.classList.contains('ve-active') ||
        const isVEActive = document.documentElement.classList.contains('ve-active');
                document.querySelector('.ve-ui-surface') ||
        const hasVESurface = document.querySelector('.ve-ui-surface');
                document.querySelector('.wikiEditor-ui')) {
        const hasWikiEditor = document.querySelector('.wikiEditor-ui');
                return;
       
            }
        if (isVEActive || hasVESurface || hasWikiEditor) {
            return;
        }


        // 4. INPUT GUARD
            // 4. INPUT GUARD
        // Standard check for typing in search bars, forms, etc.
            // Don't intercept typing in search bars or inputs
        const targetTag = e.target.tagName.toLowerCase();
            const target = e.target;
        const isInput = ['input', 'textarea', 'select'].includes(targetTag);
            if (target.tagName === 'INPUT' ||
        const isEditable = e.target.isContentEditable;
                target.tagName === 'TEXTAREA' ||
                target.tagName === 'SELECT' ||
                target.isContentEditable) {
                return;
            }


        if (isInput || isEditable) {
            // 5. MODIFIER GUARD
            return;
            if (e.ctrlKey || e.altKey || e.metaKey) return;
        }


        // 5. MODIFIER GUARD
            // --- Logic ---
        if (e.ctrlKey || e.altKey || e.metaKey) {
           
            return;
            if (e.key === 'Escape') {
        }
                resetSearch();
                return;
            }


        // --- LOGIC STARTS HERE ---
            if (e.key === 'Enter') {
                const active = document.querySelector(`.${CONFIG.classActive} ${CONFIG.selectorLink}`);
                if (active) {
                    e.preventDefault();
                    e.stopPropagation();
                    active.click();
                }
                return;
            }


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


        if (e.key === 'Enter') {
            // Capture single char keys (letters/numbers)
            const active = document.querySelector(`.${CONFIG.classActive} ${CONFIG.selectorLink}`);
            if (e.key.length === 1) {
            if (active) {
                searchBuffer += e.key;
                 // Only prevent default if we actually have a card selected
                updateSelection();
                 e.preventDefault();
                  
                 e.stopPropagation();
                 clearTimeout(clearTimer);
                active.click();
                 clearTimer = setTimeout(resetSearch, CONFIG.timeout);
             }
             }
            return;
         });
         }
 
        if (e.key === 'Backspace') {
            searchBuffer = searchBuffer.slice(0, -1);
            if (searchBuffer.length === 0) resetSearch();
            else updateSelection();
            return;
        }
 
        // Capture typing (Single character keys only)
        if (e.key.length === 1) {
            searchBuffer += e.key;
            updateSelection();
 
            // Reset the "clear buffer" timer
            clearTimeout(clearTimer);
            clearTimer = setTimeout(resetSearch, CONFIG.timeout);
        }
    });


    function updateSelection() {
        function updateSelection() {
        // Clean up previous highlights
            // Clear previous highlights
        document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
            document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));


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


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


        for (const card of cards) {
            for (const card of cards) {
            const labelEl = card.querySelector(CONFIG.selectorLabel);
                const label = card.querySelector(CONFIG.selectorLabel)?.innerText.trim() || '';
            const descEl = card.querySelector(CONFIG.selectorDesc);
                const desc = card.querySelector(CONFIG.selectorDesc)?.innerText.trim() || '';
           
            const labelText = labelEl ? labelEl.innerText.trim() : '';
            const descText = descEl ? descEl.innerText.trim() : '';


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


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


    function resetSearch() {
        function resetSearch() {
        searchBuffer = '';
            searchBuffer = '';
        document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
            document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
        }
   
    } catch (err) {
        console.error('Hallyu Dashboard Script Error:', err);
     }
     }
 
});
})();

Revision as of 00:20, 2 February 2026

/**
 * Dashboard Quick-Jump (v4 - Bulletproof)
 * Listens for typing on the Dashboard page and jumps to matching cards.
 * Wrapped in strict safety guards to prevent breaking the editor.
 */
$(function() { // Wait for the DOM to be fully ready
    try {
        // 1. EDIT MODE GUARD (URL Check)
        // If the URL contains 'veaction' (VisualEditor) or 'action=edit', stop immediately.
        // We use simple string checking here for maximum compatibility.
        if (window.location.search.indexOf('veaction') !== -1) return;
        if (window.location.search.indexOf('action=edit') !== -1) return;
        if (window.location.search.indexOf('action=submit') !== -1) return;

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

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

        let searchBuffer = '';
        let clearTimer = null;

        document.addEventListener('keydown', function(e) {
            // 3. EDIT INTERFACE GUARD (Dynamic)
            // Even if the URL looked safe, check if an editor overlay is actually open.
            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
            const target = e.target;
            if (target.tagName === 'INPUT' || 
                target.tagName === 'TEXTAREA' || 
                target.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') {
                const 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
            document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));

            if (!searchBuffer) return;

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

            for (const card of cards) {
                const label = card.querySelector(CONFIG.selectorLabel)?.innerText.trim() || '';
                const desc = card.querySelector(CONFIG.selectorDesc)?.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 = '';
            document.querySelectorAll(`.${CONFIG.classActive}`).forEach(el => el.classList.remove(CONFIG.classActive));
        }
    
    } catch (err) {
        console.error('Hallyu Dashboard Script Error:', err);
    }
});