MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
 
(3 intermediate revisions by the same user not shown)
Line 228: Line 228:


/**
/**
  * Quick Search Widget Custom autocomplete for CRT model lookup
  * Quick Search Widgets Reusable autocomplete for CRT & AV device lookup
  * Replaces Page Forms #forminput with a fully custom implementation.
  * Each widget is configured via a simple object (container ID, controls ID,
  * Fetches suggestions from the PF autocomplete API, navigates directly
* category, placeholder text). The Main Page wikitext contains the styled
  * to the wiki page on selection.
  * containers with "Loading search…" placeholders; this script replaces them
  * with the actual input, button, and dropdown, then wires up event handlers.
  */
  */
(function () {
(function () {
Line 238: Line 239:
     if (mw.config.get('wgPageName') !== 'Main_Page') return;
     if (mw.config.get('wgPageName') !== 'Main_Page') return;


     // ── Config ──────────────────────────────────────────────────────
     // ── Shared config ───────────────────────────────────────────────
     var API_URL      = mw.util.wikiScript('api');
     var API_URL      = mw.util.wikiScript('api');
    var CATEGORY    = 'CRT models';
     var DEBOUNCE_MS  = 250;
     var DEBOUNCE_MS  = 250;
     var MIN_CHARS    = 2;
     var MIN_CHARS    = 2;


     // ── Helpers ─────────────────────────────────────────────────────
     // ── Helpers ─────────────────────────────────────────────────────
    function getPageUrl(title) {
        return mw.util.getUrl(title);
    }
     function navigate(title) {
     function navigate(title) {
         title = (title || '').trim();
         title = (title || '').trim();
         if (title) window.location.href = getPageUrl(title);
         if (title) window.location.href = mw.util.getUrl(title);
     }
     }


Line 260: Line 256:
         style.id = 'ecrt-qs-styles';
         style.id = 'ecrt-qs-styles';
         style.textContent = [
         style.textContent = [
             '#crt-quick-search {',
            /* ── Base (blue) container & label ── */
             '.ecrt-qs-container {',
             '  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);',
             '  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);',
             '  border: 2px solid #7dd3fc;',
             '  border: 2px solid #7dd3fc;',
Line 274: Line 271:
             '  font-size: 0.9rem;',
             '  font-size: 0.9rem;',
             '}',
             '}',
            /* ── Green variant (AV devices) ── */
            '.ecrt-qs-container--av {',
            '  background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);',
            '  border-color: #86efac;',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-label {',
            '  color: #15803d;',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-input:focus {',
            '  border-color: #16a34a;',
            '  box-shadow: 0 0 0 3px rgba(22,163,74,0.15);',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-btn {',
            '  background: #16a34a;',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-btn:hover {',
            '  background: #15803d;',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-btn:active {',
            '  background: #166534;',
            '}',
            '.ecrt-qs-container--av .ecrt-qs-item:hover,',
            '.ecrt-qs-container--av .ecrt-qs-item.is-active {',
            '  background: #dcfce7;',
            '  color: #14532d;',
            '}',
            /* Placeholder shown before JS loads */
            '.ecrt-qs-placeholder {',
            '  padding: 0.625rem 0.875rem;',
            '  font-size: 0.95rem;',
            '  border: 1.5px solid #cbd5e1;',
            '  border-radius: 0.5rem;',
            '  background: #f1f5f9;',
            '  color: #94a3b8;',
            '  font-style: italic;',
            '}',
            /* Interactive controls */
             '.ecrt-qs-wrap {',
             '.ecrt-qs-wrap {',
             '  position: relative;',
             '  position: relative;',
Line 297: Line 334:
             '  box-shadow: 0 0 0 3px rgba(2,132,199,0.15);',
             '  box-shadow: 0 0 0 3px rgba(2,132,199,0.15);',
             '}',
             '}',
             '.ecrt-qs-input::placeholder {',
             '.ecrt-qs-input::placeholder { color: #94a3b8; }',
            '  color: #94a3b8;',
            '}',
             '.ecrt-qs-btn {',
             '.ecrt-qs-btn {',
             '  padding: 0.625rem 1.25rem;',
             '  padding: 0.625rem 1.25rem;',
Line 354: Line 389:
             '}',
             '}',


             /* Night‑mode overrides */
             /* ── Night-mode overrides (base / blue) ── */
             '.skin-theme-clientpref-night #crt-quick-search {',
             '.skin-theme-clientpref-night .ecrt-qs-container {',
             '  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);',
             '  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);',
             '  border-color: #334155;',
             '  border-color: #334155;',
             '}',
             '}',
             '.skin-theme-clientpref-night .ecrt-qs-label { color: #7dd3fc; }',
             '.skin-theme-clientpref-night .ecrt-qs-label { color: #7dd3fc; }',
            '.skin-theme-clientpref-night .ecrt-qs-placeholder {',
            '  background: #0f172a;',
            '  border-color: #334155;',
            '  color: #475569;',
            '}',
             '.skin-theme-clientpref-night .ecrt-qs-input {',
             '.skin-theme-clientpref-night .ecrt-qs-input {',
             '  background: #1e293b;',
             '  background: #1e293b;',
Line 385: Line 425:
             '  border-top-color: #334155;',
             '  border-top-color: #334155;',
             '}',
             '}',
             '.skin-theme-clientpref-night .ecrt-qs-status { color: #64748b; }'
             '.skin-theme-clientpref-night .ecrt-qs-status { color: #64748b; }',
 
            /* ── Night-mode overrides (green / AV) ── */
            '.skin-theme-clientpref-night .ecrt-qs-container--av {',
            '  background: linear-gradient(135deg, #0a1f0f 0%, #14291a 100%);',
            '  border-color: #1e4d2b;',
            '}',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-label {',
            '  color: #86efac;',
            '}',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-input:focus {',
            '  border-color: #4ade80;',
            '  box-shadow: 0 0 0 3px rgba(74,222,128,0.2);',
            '}',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-btn {',
            '  background: #15803d;',
            '}',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-btn:hover {',
            '  background: #16a34a;',
            '}',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-item:hover,',
            '.skin-theme-clientpref-night .ecrt-qs-container--av .ecrt-qs-item.is-active {',
            '  background: #14532d;',
            '  color: #bbf7d0;',
            '}'
         ].join('\n');
         ].join('\n');
         document.head.appendChild(style);
         document.head.appendChild(style);
     }
     }


     // ── Build the widget ────────────────────────────────────────────
     // ── Generic widget initializer ──────────────────────────────────
     function build(container) {
    // cfg = { containerId, controlsId, category, placeholder }
         injectStyles();
     function initWidget(cfg) {
        var container = document.getElementById(cfg.containerId);
        var wrap      = document.getElementById(cfg.controlsId);
         if (!container || !wrap) return;


         // Label
         // ── Build interactive controls ──────────────────────────────
        var label = document.createElement('div');
        label.className = 'ecrt-qs-label';
        label.textContent = '\uD83D\uDD0D Quick Search';
 
        // Wrapper (for positioning dropdown)
        var wrap = document.createElement('div');
        wrap.className = 'ecrt-qs-wrap';
 
        // Row: input + button
         var row = document.createElement('div');
         var row = document.createElement('div');
         row.className = 'ecrt-qs-row';
         row.className = 'ecrt-qs-row';
Line 410: Line 468:
         input.type = 'text';
         input.type = 'text';
         input.className = 'ecrt-qs-input';
         input.className = 'ecrt-qs-input';
         input.placeholder = 'Type a CRT model name (e.g. Sony KV-27S42)';
         input.placeholder = cfg.placeholder;
         input.setAttribute('autocomplete', 'off');
         input.setAttribute('autocomplete', 'off');


Line 421: Line 479:
         row.appendChild(btn);
         row.appendChild(btn);


        // Dropdown
         var dropdown = document.createElement('div');
         var dropdown = document.createElement('div');
         dropdown.className = 'ecrt-qs-dropdown';
         dropdown.className = 'ecrt-qs-dropdown';


        // Replace placeholder with real controls
        wrap.innerHTML = '';
         wrap.appendChild(row);
         wrap.appendChild(row);
         wrap.appendChild(dropdown);
         wrap.appendChild(dropdown);
        container.appendChild(label);
        container.appendChild(wrap);


         // ── State ───────────────────────────────────────────────────
         // ── State ───────────────────────────────────────────────────
         var items = [];       // current suggestion titles
         var items     = [];
         var activeIdx = -1;   // keyboard-highlighted index (-1 = none)
         var activeIdx = -1;
         var timer = null;
         var timer     = null;


         function openDropdown()  { dropdown.classList.add('is-open'); }
         function openDropdown()  { dropdown.classList.add('is-open'); }
Line 439: Line 496:
             dropdown.classList.remove('is-open');
             dropdown.classList.remove('is-open');
             activeIdx = -1;
             activeIdx = -1;
        }
        function setActive(idx) {
            var els = dropdown.querySelectorAll('.ecrt-qs-item');
            els.forEach(function (el) { el.classList.remove('is-active'); });
            activeIdx = idx;
            if (idx >= 0 && idx < els.length) {
                els[idx].classList.add('is-active');
                els[idx].scrollIntoView({ block: 'nearest' });
            }
         }
         }


Line 459: Line 526:
                 el.className = 'ecrt-qs-item';
                 el.className = 'ecrt-qs-item';
                 el.textContent = r.title;
                 el.textContent = r.title;
                el.setAttribute('data-index', i);
                 el.addEventListener('mousedown', function (e) {
                 el.addEventListener('mousedown', function (e) {
                    // mousedown instead of click so it fires before blur
                     e.preventDefault();
                     e.preventDefault();
                     navigate(r.title);
                     navigate(r.title);
Line 482: Line 547:
             dropdown.appendChild(msg);
             dropdown.appendChild(msg);
             openDropdown();
             openDropdown();
        }
        function setActive(idx) {
            var els = dropdown.querySelectorAll('.ecrt-qs-item');
            els.forEach(function (el) { el.classList.remove('is-active'); });
            activeIdx = idx;
            if (idx >= 0 && idx < els.length) {
                els[idx].classList.add('is-active');
                // Scroll into view if needed
                els[idx].scrollIntoView({ block: 'nearest' });
            }
         }
         }


Line 500: Line 554:
             var url = API_URL + '?action=pfautocomplete&format=json'
             var url = API_URL + '?action=pfautocomplete&format=json'
                 + '&substr=' + encodeURIComponent(substr)
                 + '&substr=' + encodeURIComponent(substr)
                 + '&category=' + encodeURIComponent(CATEGORY);
                 + '&category=' + encodeURIComponent(cfg.category);


             fetch(url)
             fetch(url)
                 .then(function (r) { return r.json(); })
                 .then(function (r) { return r.json(); })
                 .then(function (data) {
                 .then(function (data) {
                    // Only render if input still matches (avoid stale results)
                     if (input.value.trim().toLowerCase() === substr.toLowerCase()) {
                     if (input.value.trim().toLowerCase() === substr.toLowerCase()) {
                         renderItems((data && data.pfautocomplete) || []);
                         renderItems((data && data.pfautocomplete) || []);
Line 531: Line 584:
         input.addEventListener('keydown', function (e) {
         input.addEventListener('keydown', function (e) {
             if (!dropdown.classList.contains('is-open') || !items.length) {
             if (!dropdown.classList.contains('is-open') || !items.length) {
                // Enter with no dropdown → navigate to whatever is typed
                 if (e.key === 'Enter') {
                 if (e.key === 'Enter') {
                     e.preventDefault();
                     e.preventDefault();
Line 566: Line 618:
         });
         });


        // Close dropdown when clicking outside
         document.addEventListener('click', function (e) {
         document.addEventListener('click', function (e) {
             if (!wrap.contains(e.target)) {
             if (!container.contains(e.target)) {
                 closeDropdown();
                 closeDropdown();
             }
             }
         });
         });


        // Re-open on focus if there's already a query worth showing
         input.addEventListener('focus', function () {
         input.addEventListener('focus', function () {
             if (items.length && input.value.trim().length >= MIN_CHARS) {
             if (items.length && input.value.trim().length >= MIN_CHARS) {
Line 581: Line 631:
     }
     }


     // ── Init ────────────────────────────────────────────────────────
     // ── Init all widgets ────────────────────────────────────────────
     function init() {
     function init() {
         var container = document.getElementById('crt-quick-search');
         injectStyles();
         if (!container) return;
 
         build(container);
        // CRT Quick Search
        initWidget({
            containerId: 'crt-quick-search',
            controlsId:  'ecrt-qs-controls-crt',
            category:    'CRT models',
            placeholder: 'Type a CRT model name (e.g. Sony KV-27S42)'
        });
 
        // AV Device Quick Search
         initWidget({
            containerId: 'av-quick-search',
            controlsId:  'ecrt-qs-controls-av',
            category:    'AV devices',
            placeholder: 'Type an AV device name (e.g. JVC SR-V10U)'
         });
     }
     }


    // ── Run ─────────────────────────────────────────────────────────
     if (document.readyState === 'loading') {
     if (document.readyState === 'loading') {
         document.addEventListener('DOMContentLoaded', init);
         document.addEventListener('DOMContentLoaded', init);
Line 596: Line 661:




/**
* Footer Credit — "An Arclight Automata project"
* Appends inline credit text with link after the site name
* in the Minerva Neue footer (.minerva-footer-logo).
*/
(function () {
    'use strict';
    function addFooterCredit() {
        var logo = document.querySelector('.minerva-footer-logo');
        if (!logo || logo.querySelector('.ecrt-footer-credit')) return;
        var span = document.createElement('span');
        span.className = 'ecrt-footer-credit';
        span.innerHTML = ' \u00b7 An <a href="https://arclight.run" '
            + 'style="color:inherit;text-decoration:underline;text-decoration-color:rgba(128,128,128,0.4);'
            + 'text-underline-offset:2px;" '
            + 'target="_blank" rel="noopener">Arclight Automata</a> project';
        span.style.cssText = 'font-weight:normal;font-size:1em;opacity:0.7;';
        logo.appendChild(span);
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addFooterCredit);
    } else {
        addFooterCredit();
    }
})();




MediaWiki Appliance - Powered by TurnKey Linux