|
|
| 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 + Persistenz (Vector-2022 & Drawer) ==== */
| |
| (function () {
| |
| 'use strict';
| |
| if ( mw.config.get('skin') !== 'vector-2022' ) return;
| |
|
| |
| /* ---------- Persistenz ---------- */
| |
| var STORAGE_KEY = 'kr-sb-state:v3:' + ( mw.config.get('wgUserName') || 'anon' );
| |
| function loadState() {
| |
| try { return JSON.parse( localStorage.getItem(STORAGE_KEY) || '{}' ); }
| |
| catch (e) { return {}; }
| |
| }
| |
| function saveState(state) {
| |
| try { localStorage.setItem( STORAGE_KEY, JSON.stringify(state) ); }
| |
| catch (e) {}
| |
| }
| |
| var STATE = loadState();
| |
|
| |
| /* ---------- Utils ---------- */
| |
| 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;
| |
| return !/^#/.test( a.getAttribute('href') || '' );
| |
| }
| |
| 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'); }
| |
| }
| |
|
| |
| /* ---------- Kopf + ID ---------- */
| |
| function makeHead(li, keepLink, containerUL, labelOverride) {
| |
| var a = li.querySelector(':scope > a');
| |
|
| |
| var head = document.createElement('div');
| |
| head.className = 'kr-head has-toggle'; // ganze Kopfzeile darf toggeln (außer Link)
| |
|
| |
| 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
| |
| 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; gespeicherten Zustand anwenden
| |
| toggleSet(li, btn, false);
| |
| var st = STATE[ li.dataset.krId ];
| |
| if (st === 1) toggleSet(li, btn, true);
| |
|
| |
| return head;
| |
| }
| |
|
| |
| /* ---------- Toggle-Handler ---------- */
| |
| function handleArrowClick(btn) {
| |
| var li = btn.closest('li.kr-group, li.kr-top');
| |
| if (!li) return;
| |
|
| |
| var willExpand = li.classList.contains('is-collapsed');
| |
| toggleSet(li, btn, willExpand);
| |
|
| |
| var id = li.dataset.krId;
| |
| if (id) {
| |
| STATE[id] = willExpand ? 1 : 0;
| |
| saveState(STATE);
| |
| }
| |
| }
| |
|
| |
| /* Globale Delegation: Pfeil klickt immer; Kopfzeile klickt nur, wenn NICHT im Link */
| |
| document.addEventListener('click', function (e) {
| |
| var arrow = e.target && e.target.closest && e.target.closest('.kr-arrow');
| |
| if (arrow) {
| |
| e.preventDefault();
| |
| handleArrowClick(arrow);
| |
| return;
| |
| }
| |
| var head = e.target && e.target.closest && e.target.closest('.kr-head');
| |
| if (head && !e.target.closest('.kr-head a')) {
| |
| e.preventDefault(); // freie Fläche der Kopfzeile
| |
| var btn = head.querySelector('.kr-arrow');
| |
| if (btn) handleArrowClick(btn);
| |
| }
| |
| }, true); // capture=true, damit nichts dazwischenfunkt
| |
|
| |
| /* ---------- Aufbau der Gruppen ---------- */
| |
| 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);
| |
| li.classList.add('kr-group');
| |
| makeHead(li, clickable, ul, (a && a.textContent) || d.value || '');
| |
| if (!clickable && a) a.remove(); 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);
| |
| }
| |
| });
| |
| }
| |
|
| |
| /* ---------- Portlets verarbeiten ---------- */
| |
| 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);
| |
| 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);
| |
| } else {
| |
| buildGroups(ul);
| |
| }
| |
|
| |
| // Direktiven aus hrefs entfernen; Vector-Portlet-Heading ausblenden
| |
| 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();
| |
| });
| |
| }
| |
|
| |
| /* ---------- Bootstrapping ---------- */
| |
| 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);
| |
|
| |
| // Drawer/DOM-Änderungen beobachten
| |
| 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 });
| |
| })();
| |