Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.
Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.
WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE
Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus
„<a href="https://… ein kaputtes Attribut
/^["'„»\s]+|["'“«.\s]+$/ einen kaputten Ausdruck
"… nichts „mal " + "eben …" eine kaputte Zeichenkette
DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter.
Ein deutsches Schlusszeichen steht NIE
hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.
Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.
Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.
KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.
DIE WACHE DAZU
pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.
GEPRUEFT
pruef-struktur 59 -> 68 Pruefungen, 0 Fehler
node --check auf allen geaenderten JS-Dateien
nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben
gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
reaktion 421, support 63, content 45, terminregel 35, treff 85
Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
Ausgabe, keine einzige ein Vergleich mit dem Seitentext.
GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.
Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.
Co-Authored-By: Claude Opus 5 <[email protected]>
287 lines
11 KiB
JavaScript
287 lines
11 KiB
JavaScript
/* =====================================================================
|
||
„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;
|
||
|
||
/* Der Abruf steht seit dem 22.09.2026 in assets/js/holen.js.
|
||
Er stand ACHTMAL da, Zeichen fuer Zeichen gleich -- und als
|
||
die Regel darin sich als falsch herausstellte (jeder 404
|
||
warf auf die Startseite, auch bei einer blossen Handlung),
|
||
haette man sie achtmal aendern muessen. */
|
||
const hole = (pfad, optionen) => window.holen(pfad, optionen);
|
||
|
||
/* ---- 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;
|
||
}
|
||
|
||
/* EIN FESTES FELD IST GAR KEIN KNOPF MEHR (24.09.2026).
|
||
|
||
Vorher war es einer mit `data-fest="ja"` — man konnte ihn
|
||
drücken, der Server sagte nein, und die Meldung erschien oben
|
||
am Bildschirmrand. Das ist genau das, was im Haus sonst
|
||
überall vermieden wird: ein Knopf, der eine Absage bringt.
|
||
|
||
Für DogFather ändert sich dadurch fast nichts (er hat drei
|
||
feste Felder). Die rechte Hand hat deutlich mehr — bei ihr wäre
|
||
es sonst eine ganze Spalte zum Danebendrücken. */
|
||
if (fest) {
|
||
const z = el('span', 'rechte-feld rechte-feld--fest');
|
||
z.dataset.fest = 'ja';
|
||
z.title = seite.fest_grund?.[rolle.schluessel]
|
||
|| 'Das kannst du nicht umstellen.';
|
||
z.append(zeichen(darf));
|
||
td.append(z);
|
||
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';
|
||
/* EIN FESTES FELD SAGT, WARUM ES FEST IST (24.09.2026).
|
||
|
||
Hier stand EIN Satz für alle festen Felder: „Diese Seite kannst
|
||
du dir nicht selbst wegnehmen." Für DogFather stimmt er. Seit
|
||
die rechte Hand umstellen darf, gibt es drei weitere Gründe —
|
||
die Spalte „DogFather", die Zeile „Personen & Zugänge" und ihre
|
||
eigenen zwei Zugänge. Ein Satz, der bei drei von vier Fällen
|
||
das Falsche sagt, schickt sie auf die Suche nach einem Fehler,
|
||
den es nicht gibt.
|
||
|
||
Der Grund kommt NICHT aus einer Liste hier: `festGrund` steht in
|
||
der Antwort des Servers, aus derselben Funktion, die das Feld
|
||
überhaupt als fest markiert hat. */
|
||
b.title = fest
|
||
? (seite.fest_grund?.[rolle.schluessel]
|
||
|| '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.'));
|
||
/* ZURUECKSETZEN NUR FUER DOGFATHER. Der Knopf nimmt ALLE
|
||
Abweichungen mit -- auch die an der Spalte „DogFather" und an
|
||
„Personen & Zugänge", die die rechte Hand nicht anfassen
|
||
darf. Eine Sperre, die ein zweiter Knopf daneben aufhebt, ist
|
||
keine. Der Server lehnt es ohnehin ab (403); hier steht der
|
||
Knopf deshalb gar nicht erst. */
|
||
if (daten.darf_zuruecksetzen) {
|
||
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 '
|
||
+ 'und die rechte Hand.'));
|
||
}
|
||
|
||
/* 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 */ } })();
|
||
})();
|