MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 228: | Line 228: | ||
/** | /** | ||
* Quick Search — Navigate directly to wiki page | * Quick Search — Navigate directly to wiki page | ||
* Intercepts the Page Forms #forminput inside #crt-quick-search so that | * Intercepts the Page Forms #forminput inside #crt-quick-search so that | ||
* clicking "Go" (or pressing Enter, or selecting an autocomplete suggestion) | * clicking "Go" (or pressing Enter, or selecting an autocomplete suggestion) | ||
* navigates to the wiki article instead of opening the form editor. | * navigates to the wiki article instead of opening the form editor. | ||
* | |||
* Note: Page Forms creates the actual <input> element dynamically inside a | |||
* .pfFormInputWrapper div, so we must wait for it to appear before attaching | |||
* listeners. | |||
*/ | */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
if (mw.config.get('wgPageName') !== 'Main_Page') return; | |||
function goToPage(input) { | |||
var value = input.value.trim(); | |||
if (value) { | |||
window.location.href = mw.util.getUrl(value); | |||
} | |||
} | } | ||
function | function setup(container, form) { | ||
// The actual text input created by Page Forms inside .pfFormInputWrapper | |||
var input = container.querySelector('.pfFormInputWrapper input[type="text"], .pfFormInputWrapper input:not([type="hidden"])'); | |||
if (!input) return false; | |||
// Intercept form submission (Enter key / Go button) | |||
form.addEventListener('submit', function (e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
goToPage(input); | |||
}); | |||
// Navigate immediately when an autocomplete suggestion is selected | |||
$(input).on('pfautocomplete', function () { | |||
setTimeout(function () { goToPage(input); }, 50); | |||
}); | |||
return true; | |||
} | |||
function init() { | |||
var container = document.getElementById('crt-quick-search'); | var container = document.getElementById('crt-quick-search'); | ||
if (!container) return; | if (!container) return; | ||
var form = container.querySelector('form'); | var form = container.querySelector('form'); | ||
if (!form) return; | |||
if (!form) | |||
// Try immediately — if PF has already initialized, we're done | |||
if (setup(container, form)) return; | |||
if ( | |||
// Otherwise watch for PF to inject the <input> dynamically | |||
var observer = new MutationObserver(function (mutations, obs) { | |||
if (setup(container, form)) { | |||
obs.disconnect(); | |||
} | } | ||
}); | }); | ||
observer.observe(container, { childList: true, subtree: true }); | |||
// Safety timeout: stop observing after 10s | |||
setTimeout(function () { observer.disconnect(); }, 10000); | |||
} | } | ||
if (document.readyState === 'loading') { | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', init); | |||
document.addEventListener('DOMContentLoaded', | |||
} else { | } else { | ||
init(); | |||
} | } | ||
})(); | })(); | ||
Revision as of 22:13, 18 February 2026
/* 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 — Navigate directly to wiki page
* Intercepts the Page Forms #forminput inside #crt-quick-search so that
* clicking "Go" (or pressing Enter, or selecting an autocomplete suggestion)
* navigates to the wiki article instead of opening the form editor.
*
* Note: Page Forms creates the actual <input> element dynamically inside a
* .pfFormInputWrapper div, so we must wait for it to appear before attaching
* listeners.
*/
(function () {
'use strict';
if (mw.config.get('wgPageName') !== 'Main_Page') return;
function goToPage(input) {
var value = input.value.trim();
if (value) {
window.location.href = mw.util.getUrl(value);
}
}
function setup(container, form) {
// The actual text input created by Page Forms inside .pfFormInputWrapper
var input = container.querySelector('.pfFormInputWrapper input[type="text"], .pfFormInputWrapper input:not([type="hidden"])');
if (!input) return false;
// Intercept form submission (Enter key / Go button)
form.addEventListener('submit', function (e) {
e.preventDefault();
e.stopPropagation();
goToPage(input);
});
// Navigate immediately when an autocomplete suggestion is selected
$(input).on('pfautocomplete', function () {
setTimeout(function () { goToPage(input); }, 50);
});
return true;
}
function init() {
var container = document.getElementById('crt-quick-search');
if (!container) return;
var form = container.querySelector('form');
if (!form) return;
// Try immediately — if PF has already initialized, we're done
if (setup(container, form)) return;
// Otherwise watch for PF to inject the <input> dynamically
var observer = new MutationObserver(function (mutations, obs) {
if (setup(container, form)) {
obs.disconnect();
}
});
observer.observe(container, { childList: true, subtree: true });
// Safety timeout: stop observing after 10s
setTimeout(function () { observer.disconnect(); }, 10000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// 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');
}