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 | * Quick Search Widgets — Reusable autocomplete for CRT & AV device lookup | ||
* | * Each widget is configured via a simple object (container ID, controls ID, | ||
* | * category, placeholder text). The Main Page wikitext contains the styled | ||
* | * 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; | ||
// ── | // ── Shared config ─────────────────────────────────────────────── | ||
var API_URL = mw.util.wikiScript('api'); | var API_URL = mw.util.wikiScript('api'); | ||
var DEBOUNCE_MS = 250; | var DEBOUNCE_MS = 250; | ||
var MIN_CHARS = 2; | var MIN_CHARS = 2; | ||
// ── Helpers ───────────────────────────────────────────────────── | // ── Helpers ───────────────────────────────────────────────────── | ||
function navigate(title) { | function navigate(title) { | ||
title = (title || '').trim(); | title = (title || '').trim(); | ||
if (title) window.location.href = | 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 = [ | ||
' | /* ── 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; }', | ||
'.ecrt-qs-btn {', | '.ecrt-qs-btn {', | ||
' padding: 0.625rem 1.25rem;', | ' padding: 0.625rem 1.25rem;', | ||
| Line 354: | Line 389: | ||
'}', | '}', | ||
/* | /* ── Night-mode overrides (base / blue) ── */ | ||
'.skin-theme-clientpref-night | '.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); | ||
} | } | ||
// ── | // ── Generic widget initializer ────────────────────────────────── | ||
function | // cfg = { containerId, controlsId, category, placeholder } | ||
function initWidget(cfg) { | |||
var container = document.getElementById(cfg.containerId); | |||
var wrap = document.getElementById(cfg.controlsId); | |||
if (!container || !wrap) return; | |||
// | // ── Build interactive controls ────────────────────────────── | ||
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 = | input.placeholder = cfg.placeholder; | ||
input.setAttribute('autocomplete', 'off'); | input.setAttribute('autocomplete', 'off'); | ||
| Line 421: | Line 479: | ||
row.appendChild(btn); | row.appendChild(btn); | ||
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); | ||
// ── State ─────────────────────────────────────────────────── | // ── State ─────────────────────────────────────────────────── | ||
var items = []; | var items = []; | ||
var activeIdx = -1; | 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.addEventListener('mousedown', function (e) { | el.addEventListener('mousedown', function (e) { | ||
e.preventDefault(); | e.preventDefault(); | ||
navigate(r.title); | navigate(r.title); | ||
| Line 482: | Line 547: | ||
dropdown.appendChild(msg); | dropdown.appendChild(msg); | ||
openDropdown(); | openDropdown(); | ||
} | } | ||
| 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=' + 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) { | ||
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) { | ||
if (e.key === 'Enter') { | if (e.key === 'Enter') { | ||
e.preventDefault(); | e.preventDefault(); | ||
| Line 566: | Line 618: | ||
}); | }); | ||
document.addEventListener('click', function (e) { | document.addEventListener('click', function (e) { | ||
if (! | if (!container.contains(e.target)) { | ||
closeDropdown(); | closeDropdown(); | ||
} | } | ||
}); | }); | ||
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() { | ||
injectStyles(); | |||
// 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(); | |||
} | |||
})(); | |||