MediaWiki:Common.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
/** | |||
* MediaWiki:Common.js | |||
* Once Human Guide | |||
*/ | |||
mw.loader.using(['mediawiki.util']).then(function () { | mw.loader.using(['mediawiki.util']).then(function () { | ||
$(function () { | $(function () { | ||
/* ============================================================ | |||
* TAB SYSTEM | |||
* Wires up .mw-tab-buttons groups so each .mw-tab-btn shows its | |||
* matching .mw-tab-content sibling. | |||
* ============================================================ */ | |||
function initTabs() { | |||
document.querySelectorAll('.mw-tab-buttons').forEach(group => { | |||
const buttons = group.querySelectorAll('.mw-tab-btn'); | |||
buttons.forEach(btn => { | |||
btn.addEventListener('click', function () { | |||
const tabId = this.getAttribute('data-tab'); | |||
// Deactivate buttons in this group only | |||
buttons.forEach(b => b.classList.remove('active')); | |||
this.classList.add('active'); | |||
// Hide only the direct-child tab panels | |||
const container = group.parentElement; | |||
container.querySelectorAll(':scope > .mw-tab-content').forEach(c => { | |||
c.classList.remove('active'); | |||
}); | |||
// Show the target panel and init any dropdowns inside it | |||
const target = container.querySelector('#' + tabId); | |||
if (target) { | |||
target.classList.add('active'); | |||
initDropdowns(target); | |||
} | |||
}); | }); | ||
}); | }); | ||
}); | }); | ||
// Auto-click the first tab in each group on load | |||
document.querySelectorAll('.mw-tab-buttons').forEach(group => { | |||
const first = group.querySelector('.mw-tab-btn'); | |||
if (first) first.click(); | |||
}); | |||
} | |||
/* ============================================================ | |||
* JSON-DRIVEN DROPDOWN UI | |||
* Renders .mw-dropdown-ui[data-options] containers as a | |||
* label button, sorted list, and detail output panel. | |||
* Supports both the new "lines" array format and the old | |||
* semicolon-separated "content" string format. | |||
* ============================================================ */ | |||
function initDropdowns(scope) { | function initDropdowns(scope) { | ||
scope.querySelectorAll('.mw-dropdown-ui[data-options]').forEach(container => { | scope.querySelectorAll('.mw-dropdown-ui[data-options]').forEach(container => { | ||
if (container.dataset.rendered === 'true') return; | |||
if (container.dataset.rendered === | |||
let data; | let data; | ||
| Line 52: | Line 61: | ||
data = JSON.parse(container.dataset.options); | data = JSON.parse(container.dataset.options); | ||
} catch (e) { | } catch (e) { | ||
console.error( | console.error('Bad dropdown JSON:', e); | ||
return; | return; | ||
} | } | ||
| Line 58: | Line 67: | ||
container.innerHTML = ''; | container.innerHTML = ''; | ||
const btn = document.createElement('div'); | const btn = document.createElement('div'); | ||
btn.className = 'mod-dropdown-btn'; | btn.className = 'mod-dropdown-btn'; | ||
btn.textContent = container.dataset.label || 'Select One'; | btn.textContent = container.dataset.label || 'Select One'; | ||
const list = document.createElement('div'); | const list = document.createElement('div'); | ||
list.className = 'mod-dropdown-list'; | list.className = 'mod-dropdown-list'; | ||
const output = document.createElement('div'); | const output = document.createElement('div'); | ||
output.className = 'mw-dropdown-output'; | output.className = 'mw-dropdown-output'; | ||
output.innerHTML = '<div class="mw-ui-placeholder">Select something to see details</div>'; | output.innerHTML = '<div class="mw-ui-placeholder">Select something to see details</div>'; | ||
Object.values(data) | Object.values(data) | ||
.sort((a, b) => a.label.localeCompare(b.label)) | .sort((a, b) => a.label.localeCompare(b.label)) | ||
.forEach(item => { | .forEach(item => { | ||
const option = document.createElement('div'); | const option = document.createElement('div'); | ||
option.className = 'mod-dropdown-item'; | option.className = 'mod-dropdown-item'; | ||
option.textContent = item.label; | option.textContent = item.label; | ||
option.onclick = () => { | option.onclick = () => { | ||
btn.textContent = item.label; | btn.textContent = item.label; | ||
list.style.display = 'none'; | list.style.display = 'none'; | ||
let lines = []; | let lines = []; | ||
if (item.lines && Array.isArray(item.lines)) { | if (item.lines && Array.isArray(item.lines)) { | ||
lines = item.lines; | lines = item.lines; | ||
} else if (item.content) { | |||
lines = item.content.split(';').map(s => s.trim()).filter(Boolean); | |||
} | } | ||
output.innerHTML = | output.innerHTML = | ||
`<div class="mw-ui-title">${item.label}</div>` + | `<div class="mw-ui-title">${item.label}</div>` + | ||
lines.map(line => | lines.map(line => `<div class="mw-ui-line">${line}</div>`).join(''); | ||
}; | }; | ||
list.appendChild(option); | list.appendChild(option); | ||
}); | }); | ||
// | // Toggle list open/closed | ||
btn.onclick = (e) => { | btn.onclick = (e) => { | ||
e.stopPropagation(); | e.stopPropagation(); | ||
| Line 118: | Line 108: | ||
}; | }; | ||
// | // Close when clicking outside | ||
document.addEventListener('click', () => { | document.addEventListener('click', () => { | ||
list.style.display = 'none'; | list.style.display = 'none'; | ||
}); | }); | ||
container.appendChild(btn); | container.appendChild(btn); | ||
container.appendChild(list); | container.appendChild(list); | ||
container.appendChild(output); | container.appendChild(output); | ||
container.dataset.rendered = 'true'; | |||
}); | |||
} | |||
/* ============================================================ | |||
* EXCLUSIVE COLLAPSIBLE DROPDOWNS (Loot Crate pages) | |||
* When one item's image is opened, all others in the group | |||
* are forced to collapse so only one is visible at a time. | |||
* ============================================================ */ | |||
function initExclusiveCollapsibles() { | |||
const crateKeys = [ | |||
'bloomingvows', 'celestialreverie', 'crimsonascent', | |||
'luminoadventure', 'thunderoverlord', 'wishland', 'zerooverture' | |||
]; | |||
const selector = crateKeys.map(k => '.mw-customtoggle-' + k).join(', '); | |||
$(selector).on('click', function () { | |||
const match = this.className.match(/mw-customtoggle-(\S+)/); | |||
if (!match) return; | |||
const clickedKey = match[1]; | |||
crateKeys.forEach(k => { | |||
if (k === clickedKey) return; | |||
const el = document.getElementById('mw-customcollapsible-' + k); | |||
if (el) $(el).addClass('mw-collapsed'); | |||
}); | |||
}); | }); | ||
} | |||
/* ============================================================ | |||
* DISCORD TAB OVERRIDE | |||
* Repurposes the page's "Discussion" tab as a Discord invite. | |||
* ============================================================ */ | |||
function initDiscordTab() { | |||
const discussionTab = document.querySelector('#ca-talk a'); | |||
if (discussionTab) { | |||
discussionTab.href = 'https://discord.com/invite/FZtkXeGeUA'; | |||
discussionTab.target = '_blank'; | |||
discussionTab.textContent = 'Discord'; | |||
} | |||
} | } | ||
/* ============================================================ | |||
/* ================= | * BOOT | ||
* ============================================================ */ | |||
initTabs(); | |||
initExclusiveCollapsibles(); | |||
initDiscordTab(); | |||
}); | }); | ||
}); | }); | ||
Revision as of 15:31, 26 May 2026
/**
* MediaWiki:Common.js
* Once Human Guide
*/
mw.loader.using(['mediawiki.util']).then(function () {
$(function () {
/* ============================================================
* TAB SYSTEM
* Wires up .mw-tab-buttons groups so each .mw-tab-btn shows its
* matching .mw-tab-content sibling.
* ============================================================ */
function initTabs() {
document.querySelectorAll('.mw-tab-buttons').forEach(group => {
const buttons = group.querySelectorAll('.mw-tab-btn');
buttons.forEach(btn => {
btn.addEventListener('click', function () {
const tabId = this.getAttribute('data-tab');
// Deactivate buttons in this group only
buttons.forEach(b => b.classList.remove('active'));
this.classList.add('active');
// Hide only the direct-child tab panels
const container = group.parentElement;
container.querySelectorAll(':scope > .mw-tab-content').forEach(c => {
c.classList.remove('active');
});
// Show the target panel and init any dropdowns inside it
const target = container.querySelector('#' + tabId);
if (target) {
target.classList.add('active');
initDropdowns(target);
}
});
});
});
// Auto-click the first tab in each group on load
document.querySelectorAll('.mw-tab-buttons').forEach(group => {
const first = group.querySelector('.mw-tab-btn');
if (first) first.click();
});
}
/* ============================================================
* JSON-DRIVEN DROPDOWN UI
* Renders .mw-dropdown-ui[data-options] containers as a
* label button, sorted list, and detail output panel.
* Supports both the new "lines" array format and the old
* semicolon-separated "content" string format.
* ============================================================ */
function initDropdowns(scope) {
scope.querySelectorAll('.mw-dropdown-ui[data-options]').forEach(container => {
if (container.dataset.rendered === 'true') return;
let data;
try {
data = JSON.parse(container.dataset.options);
} catch (e) {
console.error('Bad dropdown JSON:', e);
return;
}
container.innerHTML = '';
const btn = document.createElement('div');
btn.className = 'mod-dropdown-btn';
btn.textContent = container.dataset.label || 'Select One';
const list = document.createElement('div');
list.className = 'mod-dropdown-list';
const output = document.createElement('div');
output.className = 'mw-dropdown-output';
output.innerHTML = '<div class="mw-ui-placeholder">Select something to see details</div>';
Object.values(data)
.sort((a, b) => a.label.localeCompare(b.label))
.forEach(item => {
const option = document.createElement('div');
option.className = 'mod-dropdown-item';
option.textContent = item.label;
option.onclick = () => {
btn.textContent = item.label;
list.style.display = 'none';
let lines = [];
if (item.lines && Array.isArray(item.lines)) {
lines = item.lines;
} else if (item.content) {
lines = item.content.split(';').map(s => s.trim()).filter(Boolean);
}
output.innerHTML =
`<div class="mw-ui-title">${item.label}</div>` +
lines.map(line => `<div class="mw-ui-line">${line}</div>`).join('');
};
list.appendChild(option);
});
// Toggle list open/closed
btn.onclick = (e) => {
e.stopPropagation();
list.style.display = list.style.display === 'block' ? 'none' : 'block';
};
// Close when clicking outside
document.addEventListener('click', () => {
list.style.display = 'none';
});
container.appendChild(btn);
container.appendChild(list);
container.appendChild(output);
container.dataset.rendered = 'true';
});
}
/* ============================================================
* EXCLUSIVE COLLAPSIBLE DROPDOWNS (Loot Crate pages)
* When one item's image is opened, all others in the group
* are forced to collapse so only one is visible at a time.
* ============================================================ */
function initExclusiveCollapsibles() {
const crateKeys = [
'bloomingvows', 'celestialreverie', 'crimsonascent',
'luminoadventure', 'thunderoverlord', 'wishland', 'zerooverture'
];
const selector = crateKeys.map(k => '.mw-customtoggle-' + k).join(', ');
$(selector).on('click', function () {
const match = this.className.match(/mw-customtoggle-(\S+)/);
if (!match) return;
const clickedKey = match[1];
crateKeys.forEach(k => {
if (k === clickedKey) return;
const el = document.getElementById('mw-customcollapsible-' + k);
if (el) $(el).addClass('mw-collapsed');
});
});
}
/* ============================================================
* DISCORD TAB OVERRIDE
* Repurposes the page's "Discussion" tab as a Discord invite.
* ============================================================ */
function initDiscordTab() {
const discussionTab = document.querySelector('#ca-talk a');
if (discussionTab) {
discussionTab.href = 'https://discord.com/invite/FZtkXeGeUA';
discussionTab.target = '_blank';
discussionTab.textContent = 'Discord';
}
}
/* ============================================================
* BOOT
* ============================================================ */
initTabs();
initExclusiveCollapsibles();
initDiscordTab();
});
});