MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| (25 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
/** | |||
* Night Mode Toggle for Minerva Neue | |||
* Theme is applied early via LocalSettings.php hook to prevent FOUC | |||
* This script just adds the toggle button | |||
*/ | |||
(function () { | |||
'use strict'; | |||
var STORAGE_KEY = 'minerva-night-mode'; | |||
// SVG icons (20x20) | |||
var MOON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>'; | |||
var SUN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>'; | |||
/** | |||
* Detect OS color scheme preference | |||
*/ | |||
function getOSPreference() { | |||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | |||
return 'night'; | |||
} | |||
return 'day'; | |||
} | |||
/** | |||
* Get saved theme, or detect from OS on first visit | |||
*/ | |||
function getSavedTheme() { | |||
try { | |||
var saved = localStorage.getItem(STORAGE_KEY); | |||
if (saved) { | |||
return saved; | |||
} | |||
// First visit: detect OS preference and save it | |||
var osPreference = getOSPreference(); | |||
localStorage.setItem(STORAGE_KEY, osPreference); | |||
return osPreference; | |||
} catch (e) { | |||
return getOSPreference(); | |||
} | |||
} | |||
function saveTheme(theme) { | |||
try { | |||
localStorage.setItem(STORAGE_KEY, theme); | |||
} catch (e) {} | |||
} | |||
function applyTheme(theme) { | |||
var html = document.documentElement; | |||
html.classList.remove('skin-theme-clientpref-day', 'skin-theme-clientpref-night'); | |||
html.classList.add('skin-theme-clientpref-' + theme); | |||
} | |||
function updateButtonState(button, theme) { | |||
var icon = button.querySelector('.nightmode-icon'); | |||
if (theme === 'night') { | |||
icon.innerHTML = SUN_SVG; | |||
button.setAttribute('title', 'Switch to light mode'); | |||
} else { | |||
icon.innerHTML = MOON_SVG; | |||
button.setAttribute('title', 'Switch to dark mode'); | |||
} | |||
} | |||
function createToggleButton() { | |||
var currentTheme = getSavedTheme(); | |||
var li = document.createElement('li'); | |||
var button = document.createElement('a'); | |||
button.setAttribute('role', 'button'); | |||
button.setAttribute('href', '#'); | |||
button.id = 'ca-nightmode'; | |||
button.className = 'cdx-button cdx-button--size-large cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet'; | |||
button.style.cursor = 'pointer'; | |||
var icon = document.createElement('span'); | |||
icon.className = 'nightmode-icon'; | |||
icon.style.cssText = 'display:flex;align-items:center;justify-content:center;width:20px;height:20px;'; | |||
var label = document.createElement('span'); | |||
label.className = 'sr-only'; | |||
label.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0;'; | |||
label.textContent = currentTheme === 'night' ? 'Light mode' : 'Dark mode'; | |||
button.appendChild(icon); | |||
button.appendChild(label); | |||
li.appendChild(button); | |||
updateButtonState(button, currentTheme); | |||
button.addEventListener('click', function (e) { | |||
e.preventDefault(); | |||
var newTheme = getSavedTheme() === 'night' ? 'day' : 'night'; | |||
saveTheme(newTheme); | |||
applyTheme(newTheme); | |||
updateButtonState(button, newTheme); | |||
}); | |||
return li; | |||
} | |||
function insertButton() { | |||
var notificationsList = document.querySelector('.minerva-notifications ul'); | |||
if (notificationsList) { | |||
notificationsList.appendChild(createToggleButton()); | |||
} else { | |||
var userNav = document.querySelector('.minerva-user-navigation'); | |||
if (userNav) { | |||
var container = document.createElement('div'); | |||
container.className = 'minerva-notifications'; | |||
var ul = document.createElement('ul'); | |||
ul.appendChild(createToggleButton()); | |||
container.appendChild(ul); | |||
userNav.insertBefore(container, userNav.firstChild); | |||
} | |||
} | |||
} | |||
// Insert button when DOM is ready | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', insertButton); | |||
} else { | |||
insertButton(); | |||
} | |||
})(); | |||
/** | |||
* Source Editor Warning Banner | |||
* Shows a styled warning above the editor when users open the standard | |||
* source editor on CRT model or AV device pages, directing them to the | |||
* Page Forms editor instead. | |||
* | |||
*/ | |||
(function () { | |||
'use strict'; | |||
// Only run on action=edit in the main namespace (ns 0) | |||
if (mw.config.get('wgAction') !== 'edit' || mw.config.get('wgNamespaceNumber') !== 0) { | |||
return; | |||
} | |||
mw.hook('wikipage.editform').add(function () { | |||
// Don't add the banner twice | |||
if (document.getElementById('ecrt-source-edit-warning')) return; | |||
var textarea = document.getElementById('wpTextbox1'); | |||
if (!textarea) return; | |||
var content = textarea.value; | |||
var formName = null; | |||
if (/\{\{CRT model/i.test(content)) { | |||
formName = 'CRT model'; | |||
} else if (/\{\{AV device/i.test(content)) { | |||
formName = 'AV device'; | |||
} | |||
if (!formName) return; | |||
var pageName = mw.config.get('wgPageName'); | |||
var formEditUrl = mw.util.getUrl('Special:FormEdit/' + formName + '/' + pageName); | |||
var typeName = formName === 'CRT model' ? 'CRT model' : 'AV device'; | |||
var banner = document.createElement('div'); | |||
banner.id = 'ecrt-source-edit-warning'; | |||
banner.style.cssText = [ | |||
'background-color: #fef2f2', | |||
'border: 1px solid #fca5a5', | |||
'border-left: 4px solid #ef4444', | |||
'border-radius: 8px', | |||
'padding: 16px 20px', | |||
'margin-bottom: 16px', | |||
'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', | |||
'line-height: 1.5' | |||
].join(';'); | |||
banner.innerHTML = | |||
'<div style="display:flex;align-items:flex-start;gap:12px;">' + | |||
'<span style="color:#dc2626;font-size:20px;line-height:1;flex-shrink:0;">⚠️</span>' + | |||
'<div>' + | |||
'<div style="color:#991b1b;font-weight:600;font-size:15px;margin-bottom:4px;">' + | |||
'You are using the source editor' + | |||
'</div>' + | |||
'<div style="color:#7f1d1d;font-size:13px;line-height:1.6;">' + | |||
'This page is a <strong>' + typeName + '</strong>. ' + | |||
'To avoid formatting issues and keep data consistent, please use the ' + | |||
'<strong>structured form editor</strong> instead.' + | |||
'<br>' + | |||
'<a href="' + mw.html.escape(formEditUrl) + '" ' + | |||
'style="display:inline-block;margin-top:10px;padding:8px 16px;' + | |||
'background-color:#dc2626;color:#fff;font-weight:600;font-size:13px;' + | |||
'border-radius:6px;text-decoration:none;">' + | |||
'✎ Edit with form editor' + | |||
'</a>' + | |||
'<span style="display:inline-block;margin-top:10px;margin-left:12px;' + | |||
'color:#991b1b;font-size:12px;font-style:italic;">' + | |||
'Only use the source editor if you know what you\'re doing.' + | |||
'</span>' + | |||
'<div style="margin-top:10px;color:#991b1b;font-size:12px;">' + | |||
'<strong>Tip:</strong> To go directly to the form editor next time, ' + | |||
'click the <strong>"Edit this data"</strong> button on the page instead of "Edit".' + | |||
'</div>' + | |||
'</div>' + | |||
'</div>' + | |||
'</div>'; | |||
// Insert above the edit form | |||
var editForm = document.getElementById('editform'); | |||
if (editForm) { | |||
editForm.parentNode.insertBefore(banner, editForm); | |||
} | |||
}); | |||
})(); | |||
/** | /** | ||
* | * 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 () { | ||
'use strict'; | 'use strict'; | ||
function | if (mw.config.get('wgPageName') !== 'Main_Page') return; | ||
var | |||
// ── Shared config ─────────────────────────────────────────────── | |||
var API_URL = mw.util.wikiScript('api'); | |||
var DEBOUNCE_MS = 250; | |||
var MIN_CHARS = 2; | |||
// ── Helpers ───────────────────────────────────────────────────── | |||
function navigate(title) { | |||
title = (title || '').trim(); | |||
if (title) window.location.href = mw.util.getUrl(title); | |||
} | |||
// ── Styles (injected once) ────────────────────────────────────── | |||
function injectStyles() { | |||
if (document.getElementById('ecrt-qs-styles')) return; | |||
var style = document.createElement('style'); | |||
style.id = 'ecrt-qs-styles'; | |||
style.textContent = [ | |||
/* ── Base (blue) container & label ── */ | |||
'.ecrt-qs-container {', | |||
' background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);', | |||
' border: 2px solid #7dd3fc;', | |||
' border-radius: 0.75rem;', | |||
' padding: 1rem 1.25rem;', | |||
' margin-bottom: 1.25rem;', | |||
' box-shadow: 0 2px 4px rgba(0,0,0,0.05);', | |||
'}', | |||
'.ecrt-qs-label {', | |||
' font-weight: 600;', | |||
' color: #0369a1;', | |||
' margin-bottom: 0.5rem;', | |||
' 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 {', | |||
' position: relative;', | |||
'}', | |||
'.ecrt-qs-row {', | |||
' display: flex;', | |||
' gap: 0.5rem;', | |||
'}', | |||
'.ecrt-qs-input {', | |||
' flex: 1;', | |||
' padding: 0.625rem 0.875rem;', | |||
' font-size: 0.95rem;', | |||
' border: 1.5px solid #94a3b8;', | |||
' border-radius: 0.5rem;', | |||
' outline: none;', | |||
' background: #fff;', | |||
' color: #1e293b;', | |||
' transition: border-color 0.15s, box-shadow 0.15s;', | |||
' min-width: 0;', | |||
'}', | |||
'.ecrt-qs-input:focus {', | |||
' border-color: #0284c7;', | |||
' box-shadow: 0 0 0 3px rgba(2,132,199,0.15);', | |||
'}', | |||
'.ecrt-qs-input::placeholder { color: #94a3b8; }', | |||
'.ecrt-qs-btn {', | |||
' padding: 0.625rem 1.25rem;', | |||
' font-size: 0.95rem;', | |||
' font-weight: 600;', | |||
' color: #fff;', | |||
' background: #0284c7;', | |||
' border: none;', | |||
' border-radius: 0.5rem;', | |||
' cursor: pointer;', | |||
' transition: background 0.15s;', | |||
' white-space: nowrap;', | |||
'}', | |||
'.ecrt-qs-btn:hover { background: #0369a1; }', | |||
'.ecrt-qs-btn:active { background: #075985; }', | |||
/* Dropdown */ | |||
'.ecrt-qs-dropdown {', | |||
' position: absolute;', | |||
' top: 100%;', | |||
' left: 0;', | |||
' right: 0;', | |||
' margin-top: 0.25rem;', | |||
' background: #fff;', | |||
' border: 1.5px solid #cbd5e1;', | |||
' border-radius: 0.5rem;', | |||
' box-shadow: 0 10px 25px -5px rgba(0,0,0,0.12), 0 4px 6px -2px rgba(0,0,0,0.06);', | |||
' max-height: 20rem;', | |||
' overflow-y: auto;', | |||
' z-index: 1000;', | |||
' display: none;', | |||
'}', | |||
'.ecrt-qs-dropdown.is-open { display: block; }', | |||
'.ecrt-qs-item {', | |||
' padding: 0.5rem 0.875rem;', | |||
' cursor: pointer;', | |||
' font-size: 0.925rem;', | |||
' color: #334155;', | |||
' transition: background 0.1s;', | |||
'}', | |||
'.ecrt-qs-item:hover,', | |||
'.ecrt-qs-item.is-active {', | |||
' background: #e0f2fe;', | |||
' color: #0c4a6e;', | |||
'}', | |||
'.ecrt-qs-item + .ecrt-qs-item {', | |||
' border-top: 1px solid #f1f5f9;', | |||
'}', | |||
'.ecrt-qs-status {', | |||
' padding: 0.625rem 0.875rem;', | |||
' font-size: 0.85rem;', | |||
' color: #94a3b8;', | |||
' font-style: italic;', | |||
'}', | |||
/* ── Night-mode overrides (base / blue) ── */ | |||
'.skin-theme-clientpref-night .ecrt-qs-container {', | |||
' background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);', | |||
' border-color: #334155;', | |||
'}', | |||
'.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 {', | |||
' background: #1e293b;', | |||
' border-color: #475569;', | |||
' color: #e2e8f0;', | |||
'}', | |||
'.skin-theme-clientpref-night .ecrt-qs-input:focus {', | |||
' border-color: #38bdf8;', | |||
' box-shadow: 0 0 0 3px rgba(56,189,248,0.2);', | |||
'}', | |||
'.skin-theme-clientpref-night .ecrt-qs-input::placeholder { color: #64748b; }', | |||
'.skin-theme-clientpref-night .ecrt-qs-btn { background: #0369a1; }', | |||
'.skin-theme-clientpref-night .ecrt-qs-btn:hover { background: #0284c7; }', | |||
'.skin-theme-clientpref-night .ecrt-qs-dropdown {', | |||
' background: #1e293b;', | |||
' border-color: #475569;', | |||
'}', | |||
'.skin-theme-clientpref-night .ecrt-qs-item { color: #cbd5e1; }', | |||
'.skin-theme-clientpref-night .ecrt-qs-item:hover,', | |||
'.skin-theme-clientpref-night .ecrt-qs-item.is-active {', | |||
' background: #0c4a6e;', | |||
' color: #e0f2fe;', | |||
'}', | |||
'.skin-theme-clientpref-night .ecrt-qs-item + .ecrt-qs-item {', | |||
' border-top-color: #334155;', | |||
'}', | |||
'.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'); | |||
document.head.appendChild(style); | |||
} | |||
// ── Generic widget initializer ────────────────────────────────── | |||
// 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'); | |||
row.className = 'ecrt-qs-row'; | |||
var input = document.createElement('input'); | |||
input.type = 'text'; | |||
input.className = 'ecrt-qs-input'; | |||
input.placeholder = cfg.placeholder; | |||
input.setAttribute('autocomplete', 'off'); | |||
var btn = document.createElement('button'); | |||
btn.type = 'button'; | |||
btn.className = 'ecrt-qs-btn'; | |||
btn.textContent = 'Go'; | |||
row.appendChild(input); | |||
row.appendChild(btn); | |||
var dropdown = document.createElement('div'); | |||
dropdown.className = 'ecrt-qs-dropdown'; | |||
// Replace placeholder with real controls | |||
wrap.innerHTML = ''; | |||
wrap.appendChild(row); | |||
wrap.appendChild(dropdown); | |||
// ── State ─────────────────────────────────────────────────── | |||
var items = []; | |||
var activeIdx = -1; | |||
var timer = null; | |||
function openDropdown() { dropdown.classList.add('is-open'); } | |||
function closeDropdown() { | |||
dropdown.classList.remove('is-open'); | |||
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' }); | |||
} | |||
} | |||
function renderItems(results) { | |||
dropdown.innerHTML = ''; | |||
items = []; | |||
activeIdx = -1; | |||
if (!results.length) { | |||
var msg = document.createElement('div'); | |||
msg.className = 'ecrt-qs-status'; | |||
msg.textContent = 'No results found'; | |||
dropdown.appendChild(msg); | |||
openDropdown(); | |||
return; | |||
} | |||
results.forEach(function (r, i) { | |||
var el = document.createElement('div'); | |||
el.className = 'ecrt-qs-item'; | |||
el.textContent = r.title; | |||
el.addEventListener('mousedown', function (e) { | |||
e.preventDefault(); | |||
navigate(r.title); | |||
}); | |||
el.addEventListener('mouseenter', function () { | |||
setActive(i); | |||
}); | |||
dropdown.appendChild(el); | |||
items.push(r.title); | |||
}); | |||
openDropdown(); | |||
} | |||
function renderLoading() { | |||
dropdown.innerHTML = ''; | |||
var msg = document.createElement('div'); | |||
msg.className = 'ecrt-qs-status'; | |||
msg.textContent = 'Searching\u2026'; | |||
dropdown.appendChild(msg); | |||
openDropdown(); | |||
} | |||
// ── API fetch ─────────────────────────────────────────────── | |||
function fetchSuggestions(substr) { | |||
renderLoading(); | |||
var url = API_URL + '?action=pfautocomplete&format=json' | |||
+ '&substr=' + encodeURIComponent(substr) | |||
+ '&category=' + encodeURIComponent(cfg.category); | |||
fetch(url) | |||
.then(function (r) { return r.json(); }) | |||
.then(function (data) { | |||
if (input.value.trim().toLowerCase() === substr.toLowerCase()) { | |||
renderItems((data && data.pfautocomplete) || []); | |||
} | |||
}) | |||
.catch(function () { | |||
dropdown.innerHTML = ''; | |||
closeDropdown(); | |||
}); | |||
} | |||
// ── Event handlers ────────────────────────────────────────── | |||
input.addEventListener('input', function () { | |||
clearTimeout(timer); | |||
var val = input.value.trim(); | |||
if (val.length < MIN_CHARS) { | |||
closeDropdown(); | |||
return; | |||
} | |||
timer = setTimeout(function () { | |||
fetchSuggestions(val); | |||
}, DEBOUNCE_MS); | |||
}); | }); | ||
input.addEventListener('keydown', function (e) { | |||
if (!dropdown.classList.contains('is-open') || !items.length) { | |||
if (e.key === 'Enter') { | |||
e.preventDefault(); | |||
navigate(input.value); | |||
} | |||
return; | |||
} | |||
switch (e.key) { | |||
case 'ArrowDown': | |||
e.preventDefault(); | |||
setActive(activeIdx < items.length - 1 ? activeIdx + 1 : 0); | |||
break; | |||
case 'ArrowUp': | |||
e.preventDefault(); | |||
setActive(activeIdx > 0 ? activeIdx - 1 : items.length - 1); | |||
break; | |||
case 'Enter': | |||
e.preventDefault(); | |||
if (activeIdx >= 0 && activeIdx < items.length) { | |||
navigate(items[activeIdx]); | |||
} else { | |||
navigate(input.value); | |||
} | |||
break; | |||
case 'Escape': | |||
closeDropdown(); | |||
break; | |||
} | |||
}); | |||
btn.addEventListener('click', function () { | |||
navigate(input.value); | |||
}); | |||
document.addEventListener('click', function (e) { | |||
if (!container.contains(e.target)) { | |||
closeDropdown(); | |||
} | |||
}); | |||
input.addEventListener('focus', function () { | |||
if (items.length && input.value.trim().length >= MIN_CHARS) { | |||
openDropdown(); | |||
} | |||
}); | |||
} | |||
// ── Init all widgets ──────────────────────────────────────────── | |||
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') { | |||
document.addEventListener('DOMContentLoaded', init); | |||
} else { | |||
init(); | |||
} | } | ||
})(); | })(); | ||
// | |||
/** | |||
* 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(); | |||
} | |||
})(); | })(); | ||
// Load CRT Search module on the CRT search page | |||
if (mw.config.get('wgPageName') === 'CRT_Search') { | |||
mw.loader.load('/wiki/MediaWiki:CRT_Search.js?action=raw&ctype=text/javascript'); | |||
} | |||
// Load AV Device Search module on the AV Device Search page | |||
if (mw.config.get('wgPageName') === 'AV_Device_Search') { | |||
mw.loader.load('/wiki/MediaWiki:AV_Device_Search.js?action=raw&ctype=text/javascript'); | |||
} | |||