MediaWiki:Citizen.js: Difference between revisions
MediaWiki interface page
More actions
Joeeasterly (talk | contribs) No edit summary |
Joeeasterly (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
/** | /** | ||
* Dashboard Quick-Jump ( | * 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. | ||
* | * Wrapped in strict safety guards to prevent breaking the editor. | ||
*/ | */ | ||
(function() { | $(function() { // Wait for the DOM to be fully ready | ||
// 1. | 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); | |||
} | } | ||
}); | |||
} | |||
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);
}
});