MediaWiki:Common.js: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
/* Das folgende JavaScript wird für alle Benutzer geladen. */ | /* Das folgende JavaScript wird für alle Benutzer geladen. */ | ||
/* ==== Sidebar: klickbare Kopfzeile + Gruppen mit Auf/Zu (Vector-2022, auch im Drawer) ==== */ | /* ==== Sidebar: klickbare Kopfzeile + Gruppen mit Auf/Zu + Persistenz (Vector-2022, auch im Drawer) ==== */ | ||
(function () { | (function () { | ||
if (mw.config.get('skin') !== 'vector-2022') return; | if (mw.config.get('skin') !== 'vector-2022') return; | ||
// ---------- | // ---------- Persistenz ---------- | ||
var STORAGE_KEY = 'kr-sb-state:v3:' + (mw.config.get('wgUserName') || 'anon'); | |||
var STATE = loadState(); | |||
function loadState() { | |||
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch (e) { return {}; } | |||
} | |||
function saveState() { | |||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(STATE)); } catch (e) {} | |||
} | |||
// ---------- Helpers ---------- | |||
function extractDirective(a) { | function extractDirective(a) { | ||
if (!a) return null; | if (!a) return null; | ||
| Zeile 10: | Zeile 21: | ||
try { href = new URL(href, location.href).hash || ''; } catch (e) {} | try { href = new URL(href, location.href).hash || ''; } catch (e) {} | ||
var s = (href || '').trim().toLowerCase(); | var s = (href || '').trim().toLowerCase(); | ||
if (s. | if (s.includes('#group:')) return { kind: 'group', value: decodeURIComponent(s.split('#group:').pop()) }; | ||
if (s. | if (s.includes('#link:')) return { kind: 'link', value: decodeURIComponent(s.split('#link:').pop()) }; | ||
return null; | return null; | ||
} | } | ||
function stripDirectiveHash(a){ | function stripDirectiveHash(a){ | ||
if (!a) return; | if (!a) return; | ||
a.setAttribute('href', (a.getAttribute('href') || '').replace(/#(?:link|group):[^#]*$/i,'')); | |||
} | } | ||
function hasRealHref(a){ | function hasRealHref(a){ | ||
if (!a) return false; | if (!a) return false; | ||
var h = a.getAttribute('href') || ''; | var h = a.getAttribute('href') || ''; | ||
// | return !/^#/.test(h); | ||
} | |||
function normId(s){ | |||
return String(s || '').trim().toLowerCase().replace(/\s+/g,'-').replace(/[^\w\-:.|]/g,''); | |||
} | |||
function toggleSet(li, arrow, expanded){ | |||
if (expanded) { | |||
li.classList.remove('is-collapsed'); | |||
arrow.setAttribute('aria-expanded','true'); | |||
} else { | |||
li.classList.add('is-collapsed'); | |||
arrow.setAttribute('aria-expanded','false'); | |||
} | |||
} | } | ||
function makeHead(li, keepLink) { | // Head bauen und ID vergeben (für Persistenz) | ||
function makeHead(li, keepLink, containerUL, labelOverride) { | |||
var a = li.querySelector(':scope > a'); | var a = li.querySelector(':scope > a'); | ||
var head = document.createElement('div'); | var head = document.createElement('div'); | ||
head.className = 'kr-head'; | head.className = 'kr-head'; | ||
var btn = document.createElement('button'); | var btn = document.createElement('button'); | ||
btn.type = 'button'; | btn.type = 'button'; | ||
btn.className = 'kr-arrow'; | btn.className = 'kr-arrow'; | ||
btn.setAttribute('aria-expanded', 'false'); | btn.setAttribute('aria-expanded', 'false'); | ||
head.appendChild(btn); | head.appendChild(btn); | ||
| Zeile 41: | Zeile 63: | ||
var span = document.createElement('span'); | var span = document.createElement('span'); | ||
span.className = 'kr-title'; | span.className = 'kr-title'; | ||
span.textContent = (a && a.textContent) || ''; | span.textContent = labelOverride || (a && a.textContent) || ''; | ||
head.appendChild(span); | head.appendChild(span); | ||
if (a) a.remove(); | if (a) a.remove(); | ||
| Zeile 47: | Zeile 69: | ||
li.insertBefore(head, li.firstChild); | li.insertBefore(head, li.firstChild); | ||
li. | |||
// stabile ID für Persistenz: portletId + label | |||
var portlet = containerUL.closest('.vector-menu'); | |||
var pid = (portlet && (portlet.id || portlet.getAttribute('id'))) || 'portlet'; | |||
var labelText = (labelOverride || (head.querySelector('a, .kr-title') || {}).textContent || '').trim(); | |||
li.dataset.krId = normId(pid + '::' + labelText); | |||
// Default: zu, dann Zustand aus STATE anwenden | |||
toggleSet(li, btn, /*expanded*/ false); | |||
var st = STATE[li.dataset.krId]; | |||
if (st === 1) toggleSet(li, btn, true); | |||
return head; | return head; | ||
} | } | ||
| Zeile 54: | Zeile 87: | ||
if (containerUL.dataset.krToggleAttached) return; | if (containerUL.dataset.krToggleAttached) return; | ||
containerUL.dataset.krToggleAttached = '1'; | containerUL.dataset.krToggleAttached = '1'; | ||
containerUL.addEventListener('click', function (e) { | containerUL.addEventListener('click', function (e) { | ||
// robust: von Ziel nach oben zum Button laufen | |||
var li = | var arrow = e.target.closest('.kr-arrow'); | ||
if (!arrow || !containerUL.contains(arrow)) return; | |||
e.preventDefault(); | |||
var li = arrow.closest('li.kr-group, li.kr-top'); | |||
if (!li) return; | if (!li) return; | ||
var | |||
var expanded = li.classList.contains('is-collapsed'); | |||
toggleSet(li, arrow, expanded); // invertieren | |||
var id = li.dataset.krId; | |||
if (id) { | |||
STATE[id] = expanded ? 1 : 0; | |||
saveState(); | |||
} | |||
}); | }); | ||
} | } | ||
| Zeile 72: | Zeile 117: | ||
if (d && d.kind === 'group') { | if (d && d.kind === 'group') { | ||
var clickable = hasRealHref(a); // Gruppe mit/ohne Link | |||
var clickable = hasRealHref(a); | |||
li.classList.add('kr-group'); | li.classList.add('kr-group'); | ||
makeHead(li, clickable); | makeHead(li, clickable, ul, (a && a.textContent) || d.value || ''); | ||
if (clickable) | if (!clickable && a) a.remove(); // nur Texttitel | ||
else if ( | else if (clickable) stripDirectiveHash(a); | ||
var sub = document.createElement('ul'); | var sub = document.createElement('ul'); | ||
| Zeile 86: | Zeile 130: | ||
} | } | ||
if (current && !(d && (d.kind === 'group' || d.kind === 'link'))) { | if (current && !(d && (d.kind === 'group' || d.kind === 'link'))) { | ||
current.appendChild(li); | current.appendChild(li); | ||
| Zeile 111: | Zeile 154: | ||
var top = items[topIdx]; | var top = items[topIdx]; | ||
top.classList.add('kr-top'); | top.classList.add('kr-top'); | ||
makeHead(top, true); | makeHead(top, true, ul); // klickbarer Titel | ||
stripDirectiveHash(top.querySelector(':scope > a')); | stripDirectiveHash(top.querySelector(':scope > a')); | ||
| Zeile 119: | Zeile 162: | ||
for (var i = topIdx + 1; i < items.length; i++) sub.appendChild(items[i]); | for (var i = topIdx + 1; i < items.length; i++) sub.appendChild(items[i]); | ||
buildGroups(sub); // Gruppen in der Unterliste | |||
buildGroups(sub); | attachToggle(ul); // Toggle auch für kr-top | ||
attachToggle(ul); | |||
} else { | } else { | ||
buildGroups(ul); | buildGroups(ul); | ||
} | } | ||
// | // aufräumen | ||
ul.querySelectorAll('a[href*="#link:"], a[href*="#group:"]').forEach(stripDirectiveHash); | ul.querySelectorAll('a[href*="#link:"], a[href*="#group:"]').forEach(stripDirectiveHash); | ||
var portlet = ul.closest('.vector-menu'); | var portlet = ul.closest('.vector-menu'); | ||
| Zeile 148: | Zeile 190: | ||
init(); | init(); | ||
mw.hook('wikipage.content').add(init); | mw.hook('wikipage.content').add(init); | ||
// auch wenn Drawer dynamisch montiert wird | |||
var mo = new MutationObserver(function (muts) { | var mo = new MutationObserver(function (muts) { | ||
muts.forEach(function (m) { | muts.forEach(function (m) { | ||
Version vom 10. Oktober 2025, 15:45 Uhr
/* Das folgende JavaScript wird für alle Benutzer geladen. */
/* ==== Sidebar: klickbare Kopfzeile + Gruppen mit Auf/Zu + Persistenz (Vector-2022, auch im Drawer) ==== */
(function () {
if (mw.config.get('skin') !== 'vector-2022') return;
// ---------- Persistenz ----------
var STORAGE_KEY = 'kr-sb-state:v3:' + (mw.config.get('wgUserName') || 'anon');
var STATE = loadState();
function loadState() {
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch (e) { return {}; }
}
function saveState() {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(STATE)); } catch (e) {}
}
// ---------- Helpers ----------
function extractDirective(a) {
if (!a) return null;
var href = a.getAttribute('href') || '';
try { href = new URL(href, location.href).hash || ''; } catch (e) {}
var s = (href || '').trim().toLowerCase();
if (s.includes('#group:')) return { kind: 'group', value: decodeURIComponent(s.split('#group:').pop()) };
if (s.includes('#link:')) return { kind: 'link', value: decodeURIComponent(s.split('#link:').pop()) };
return null;
}
function stripDirectiveHash(a){
if (!a) return;
a.setAttribute('href', (a.getAttribute('href') || '').replace(/#(?:link|group):[^#]*$/i,''));
}
function hasRealHref(a){
if (!a) return false;
var h = a.getAttribute('href') || '';
return !/^#/.test(h);
}
function normId(s){
return String(s || '').trim().toLowerCase().replace(/\s+/g,'-').replace(/[^\w\-:.|]/g,'');
}
function toggleSet(li, arrow, expanded){
if (expanded) {
li.classList.remove('is-collapsed');
arrow.setAttribute('aria-expanded','true');
} else {
li.classList.add('is-collapsed');
arrow.setAttribute('aria-expanded','false');
}
}
// Head bauen und ID vergeben (für Persistenz)
function makeHead(li, keepLink, containerUL, labelOverride) {
var a = li.querySelector(':scope > a');
var head = document.createElement('div');
head.className = 'kr-head';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'kr-arrow';
btn.setAttribute('aria-expanded', 'false');
head.appendChild(btn);
if (keepLink && a) { stripDirectiveHash(a); head.appendChild(a); }
else {
var span = document.createElement('span');
span.className = 'kr-title';
span.textContent = labelOverride || (a && a.textContent) || '';
head.appendChild(span);
if (a) a.remove();
}
li.insertBefore(head, li.firstChild);
// stabile ID für Persistenz: portletId + label
var portlet = containerUL.closest('.vector-menu');
var pid = (portlet && (portlet.id || portlet.getAttribute('id'))) || 'portlet';
var labelText = (labelOverride || (head.querySelector('a, .kr-title') || {}).textContent || '').trim();
li.dataset.krId = normId(pid + '::' + labelText);
// Default: zu, dann Zustand aus STATE anwenden
toggleSet(li, btn, /*expanded*/ false);
var st = STATE[li.dataset.krId];
if (st === 1) toggleSet(li, btn, true);
return head;
}
function attachToggle(containerUL){
if (containerUL.dataset.krToggleAttached) return;
containerUL.dataset.krToggleAttached = '1';
containerUL.addEventListener('click', function (e) {
// robust: von Ziel nach oben zum Button laufen
var arrow = e.target.closest('.kr-arrow');
if (!arrow || !containerUL.contains(arrow)) return;
e.preventDefault();
var li = arrow.closest('li.kr-group, li.kr-top');
if (!li) return;
var expanded = li.classList.contains('is-collapsed');
toggleSet(li, arrow, expanded); // invertieren
var id = li.dataset.krId;
if (id) {
STATE[id] = expanded ? 1 : 0;
saveState();
}
});
}
function buildGroups(ul) {
var lis = Array.from(ul.querySelectorAll(':scope > li.mw-list-item'));
var current = null;
lis.forEach(function (li) {
var a = li.querySelector(':scope > a');
var d = extractDirective(a);
if (d && d.kind === 'group') {
var clickable = hasRealHref(a); // Gruppe mit/ohne Link
li.classList.add('kr-group');
makeHead(li, clickable, ul, (a && a.textContent) || d.value || '');
if (!clickable && a) a.remove(); // nur Texttitel
else if (clickable) stripDirectiveHash(a);
var sub = document.createElement('ul');
sub.className = 'kr-sub';
li.appendChild(sub);
current = sub;
return;
}
if (current && !(d && (d.kind === 'group' || d.kind === 'link'))) {
current.appendChild(li);
}
});
attachToggle(ul);
}
function buildPortlets(root) {
var lists = root.querySelectorAll('.vector-menu .vector-menu-content-list');
lists.forEach(function (ul) {
if (ul.dataset.krSbDone) return;
ul.dataset.krSbDone = '1';
var items = Array.from(ul.querySelectorAll(':scope > li.mw-list-item'));
var topIdx = items.findIndex(function (li) {
var a = li.querySelector(':scope > a');
var d = extractDirective(a);
return d && d.kind === 'link';
});
if (topIdx >= 0) {
var top = items[topIdx];
top.classList.add('kr-top');
makeHead(top, true, ul); // klickbarer Titel
stripDirectiveHash(top.querySelector(':scope > a'));
var sub = document.createElement('ul');
sub.className = 'kr-sub';
top.appendChild(sub);
for (var i = topIdx + 1; i < items.length; i++) sub.appendChild(items[i]);
buildGroups(sub); // Gruppen in der Unterliste
attachToggle(ul); // Toggle auch für kr-top
} else {
buildGroups(ul);
}
// aufräumen
ul.querySelectorAll('a[href*="#link:"], a[href*="#group:"]').forEach(stripDirectiveHash);
var portlet = ul.closest('.vector-menu');
var heading = portlet && portlet.querySelector(':scope > .vector-menu-heading');
if (heading) heading.remove();
});
}
function processRoot(root){
if (!root || root.dataset.krSbRootDone) return;
root.dataset.krSbRootDone = '1';
root.classList.add('kr-sb');
buildPortlets(root);
}
function init() {
document.querySelectorAll('#mw-panel, #vector-main-menu, #vector-main-menu-pinned, .vector-drawer')
.forEach(processRoot);
}
init();
mw.hook('wikipage.content').add(init);
// auch wenn Drawer dynamisch montiert wird
var mo = new MutationObserver(function (muts) {
muts.forEach(function (m) {
m.addedNodes && m.addedNodes.forEach(function (n) {
if (!(n instanceof Element)) return;
if (n.matches('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')) processRoot(n);
n.querySelectorAll && n.querySelectorAll('#vector-main-menu, #vector-main-menu-pinned, .vector-drawer, #mw-panel')
.forEach(processRoot);
});
});
});
mo.observe(document.body, { childList: true, subtree: true });
})();