/* ===================================================================== „Wer sieht was" — ansehen und umstellen (11.09.2026) Filipe: *„so dass ich die da auch manuell wechseln und speichern kann."* --------------------------------------------------------------------- GESPEICHERT WIRD SOFORT, MIT JEDEM KLICK Kein „Speichern"-Knopf am Ende. Bei einer Tafel mit zweihundert Feldern ist der die Stelle, an der man eine halbe Änderung verliert — und eine halbe Änderung an Rechten ist die gefährlichste Lage von allen. Zurückgenommen wird mit demselben Klick. --------------------------------------------------------------------- WAS HIER NICHT ENTSCHIEDEN WIRD Ob jemand etwas darf. Der Browser fragt und zeichnet; jede Absage kommt vom Server. Die rechte Hand sieht dieselbe Tafel, bekommt aber keine Knöpfe — nicht weil dieses Skript sie versteckt, sondern weil der Server `darf_aendern: false` schickt. Ein Knopf weniger ist Bequemlichkeit, kein Riegel. --------------------------------------------------------------------- UND EIN UNTERSCHIED, DEN MAN SEHEN MUSS Der Code in rechte.js ist der GRUNDSTAND. Was davon abweicht, ist von Hand gesetzt — und das steht an der Zelle. Ohne diese Markierung wüsste nach vier Wochen niemand mehr, was gedacht war und was jemand daraus gemacht hat. ===================================================================== */ (() => { const $ = (id) => document.getElementById(id); const el = (t, k, text) => { const n = document.createElement(t); if (k) n.className = k; if (text !== undefined) n.textContent = text; return n; }; const melde = (t, art) => { const f = $('fehler'); if (!f) return; f.textContent = t; f.hidden = !t; f.dataset.art = art || 'fehler'; }; let daten = null; async function hole(pfad, optionen = {}) { const a = await fetch(pfad, { credentials: 'same-origin', ...optionen }); if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); } if (a.status === 404) { location.assign('/workspace/start.html'); throw new Error('unbekannt'); } return a; } /* ---- Ein Feld ------------------------------------------------------- */ function feld(seite, rolle) { const darf = seite.rollen.includes(rolle.schluessel); const anders = darf !== seite.grund.includes(rolle.schluessel); const fest = seite.fest.includes(rolle.schluessel); const td = document.createElement('td'); if (anders) td.dataset.anders = 'ja'; /* OHNE RECHT ZUM UMSTELLEN BLEIBT ES EIN ZEICHEN, KEIN KNOPF. Ein Knopf, der nichts tut, ist schlimmer als keiner — man drückt ihn immer wieder. */ if (!daten.darf_aendern) { td.append(zeichen(darf)); if (anders) td.title = 'Von Hand gesetzt – weicht vom Grundstand ab.'; return td; } const b = el('button', 'rechte-feld'); b.type = 'button'; b.setAttribute('aria-pressed', String(darf)); b.setAttribute('aria-label', `${rolle.name} · ${seite.datei}: ${darf ? 'darf' : 'darf nicht'}`); if (fest) b.dataset.fest = 'ja'; b.title = fest ? 'Diese Seite kannst du dir nicht selbst wegnehmen – sonst kämst du ' + 'nicht mehr an die Rechte, die Zugänge oder die Startseite.' : (anders ? 'Von Hand gesetzt. Klicken stellt es um.' : 'Klicken stellt es um.'); b.append(zeichen(darf)); b.addEventListener('click', () => umstellen(seite, rolle, !darf, b)); td.append(b); return td; } function zeichen(darf) { if (darf) { const punkt = el('span', 'rechte-ja'); punkt.setAttribute('role', 'img'); punkt.setAttribute('aria-label', 'darf'); return punkt; } /* Ein Zeichen und nicht ein leeres Feld: Ein Bildschirmleser liest eine leere Zelle als nichts vor — man hört dann nicht, dass die Antwort „nein" ist, sondern gar nichts. */ const nein = el('span', 'rechte-nein', '–'); nein.setAttribute('aria-label', 'darf nicht'); return nein; } async function umstellen(seite, rolle, erlaubt, knopf) { /* EINE RÜCKFRAGE NUR BEIM ÖFFNEN, nicht beim Schließen. Etwas wegzunehmen kann man in einer Sekunde zurückdrehen und es sieht sofort jemand. Etwas aufzumachen merkt unter Umständen lange niemand — und genau das ist der Klick, bei dem eine Sekunde Nachdenken etwas wert ist. */ if (erlaubt && !await frageNach({ titel: `„${rolle.name}" darf danach ${seite.datei} öffnen`, was: 'Das gilt sofort, für alle mit dieser Rolle.', bleibt: 'Zurücknehmen geht jederzeit mit demselben Schalter – aber bis ' + 'dahin hat jeder mit dieser Rolle Zugang.', ja: 'Freigeben', })) return; knopf.disabled = true; try { const a = await hole('/workspace/api/rechte/feld', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pfad: seite.pfad, rolle: rolle.schluessel, erlaubt }), }); if (!a.ok) { melde(sagWas((await a.json().catch(() => ({}))).fehler, 'Ging nicht.')); return; } melde(`Gespeichert: „${rolle.name}" darf ${seite.datei} ` + `${erlaubt ? 'jetzt' : 'nicht mehr'} öffnen.`, 'gut'); await laden(); } finally { knopf.disabled = false; } } /* ---- Zeichnen --------------------------------------------------------- */ function zeichne() { const gesamt = daten.seiten.length; /* Die Zahlen je Rolle. Sortiert nach Anzahl — man sieht auf einen Blick, wer viel sieht und wer wenig, und ob irgendwo eine Rolle steht, die mehr sieht als sie sollte. */ const zahlen = $('zahlen'); zahlen.textContent = ''; for (const rolle of [...daten.rollen].sort((a, b) => b.seiten - a.seiten)) { const li = document.createElement('li'); li.append(rolle.name + ' '); const b = document.createElement('b'); b.textContent = `${rolle.seiten} von ${gesamt}`; li.append(b); if (rolle.abweichend) { const m = el('span', 'rechte-zahlen__anders', ` · ${rolle.abweichend} von Hand`); m.title = 'So viele Felder weichen bei dieser Rolle vom Grundstand ab.'; li.append(m); } zahlen.append(li); } /* Die Leiste: was ist von Hand gesetzt, und wie kommt man zurück. */ const leiste = $('leiste'); leiste.textContent = ''; if (daten.abweichungen) { leiste.hidden = false; leiste.append(el('span', 'rechte-leiste__satz', `${daten.abweichungen} ${daten.abweichungen === 1 ? 'Feld ist' : 'Felder sind'} ` + 'von Hand gesetzt und weicht vom Grundstand ab.')); if (daten.darf_aendern) { const b = el('button', 'schritt', 'Alles zurück auf Grundstand'); b.type = 'button'; b.addEventListener('click', async () => { if (!await frageNach({ titel: `${daten.abweichungen} Änderungen zurücknehmen?`, was: 'Danach gilt wieder genau das, was im Code steht.', bleibt: 'Die Rechte selbst gehen nicht verloren – nur die von Hand ' + 'gesetzten Abweichungen. Du kannst sie jederzeit neu setzen.', ja: 'Auf Grundstand zurück', })) return; b.disabled = true; const a = await hole('/workspace/api/rechte/abweichungen', { method: 'DELETE' }); b.disabled = false; if (!a.ok) { melde('Ging nicht.'); return; } melde('Alles zurück auf Grundstand.', 'gut'); await laden(); }); leiste.append(b); } } else { leiste.hidden = false; leiste.append(el('span', 'rechte-leiste__satz', daten.darf_aendern ? 'Alles steht so, wie es im Code steht. Klick auf ein Feld, um es umzustellen – ' + 'das wird sofort gespeichert.' : 'Alles steht so, wie es im Code steht. Umstellen kann das nur DogFather.')); } /* Die Tafel. */ const ziel = $('tafel'); ziel.textContent = ''; const tab = document.createElement('table'); const kopf = document.createElement('thead'); const kz = document.createElement('tr'); kz.append(el('th', null, 'Seite')); for (const rolle of daten.rollen) { const th = el('th', null, rolle.name); /* Der Schlüssel als Titel: Im Alltag liest man „Community", im Code steht „gast". Wer die beiden nicht zusammenbringen kann, sucht im Quelltext an der falschen Stelle. */ th.title = rolle.schluessel; kz.append(th); } kopf.append(kz); tab.append(kopf); const koerper = document.createElement('tbody'); for (const seite of daten.seiten) { const tr = document.createElement('tr'); tr.append(el('td', null, seite.datei)); for (const rolle of daten.rollen) tr.append(feld(seite, rolle)); koerper.append(tr); } tab.append(koerper); ziel.append(tab); ziel.setAttribute('aria-busy', 'false'); const offen = $('offen'); offen.textContent = ''; for (const pfad of daten.ohne_anmeldung) offen.append(el('li', null, pfad)); } async function laden() { let a; try { a = await hole('/workspace/api/rechte/tafel'); } catch { return; } if (!a.ok) { melde('Die Tafel konnte nicht geladen werden.'); return; } daten = await a.json(); zeichne(); } (async () => { try { await laden(); } catch { /* umgeleitet */ } })(); })();