Filipe: "Es darf vor allem keine Stellen geben, an denen ein Benutzer
etwas falsch machen kann, nur weil die Seite es nicht verstaendlich
genug erklaert."
Gemessen: 30 Stellen in 16 Dateien benutzten confirm() oder prompt().
Das Haus hatte die richtige Bauweise laengst -- einen <dialog>, in
aufgaben.html sogar ausfuehrlich begruendet -- aber sie stand IN EINER
SEITE. Wer anderswo etwas loeschen liess, hatte sie nicht.
confirm('Wirklich loeschen?') stellt die falsche Frage: Es fragt, ob
man sicher ist, und nennt nicht, WAS passiert, was BLEIBT und ob es
ZURUECK geht. Jetzt beantwortet jeder der 41 Dialoge alle drei.
Neu: workspace/assets/js/nachfrage.js -- window.frageNach() mit
Pflichtgrund, Zahlenfeld, einzeiliger Eingabe und Abtippsicherung.
Drei Ausgaenge: <dialog> / confirm()-Notnagel fuer Safari vor 15.4 /
Abbruch (Esc, Klick daneben, "Doch nicht" -- immer false).
DREIMAL DERSELBE FALLSTRICK, dreimal nachgemessen statt vermutet:
.dialog stand in aufgaben.css und leistung.css -> auf dateien.html
waere der Dialog ein weisser Systemkasten gewesen. 14 Regeln
klammergenau nach module.css verschoben (Klammern gezaehlt, nicht
per Muster geschnitten -- heute frueh hat ein nicht-gieriges
Muster schon einmal CSS zerrissen).
Das Formular trug .neu neu--blank -- und .neu gibt seine Abstaende
nur in aufgaben.css. Gemessen: padding 0px, und die Felder
verloren ihre height:44px. Jetzt steht alles unter
.nachfrage__form in module.css; der Dialog borgt nichts mehr.
Die erste Fassung der Pruefung zaehlte nachfrage.js SELBST als
Nutzer -- damit war jede Seite trivialerweise "Nutzer" und die
Pruefung gruen ohne Inhalt. Jetzt ausdruecklich ausgenommen.
ZWEI FUNDE NEBENBEI:
hilfeAufraeumen() wird im Betrieb NIE aufgerufen. Der Kommentar
behauptete "wird beim Start aufgerufen (siehe index.js)" -- das
war nie wahr; einziger Aufrufer ist die eigene Pruefung. Folge:
geschlossene vertrauliche Faelle bleiben unbegrenzt stehen. NICHT
eingeschaltet (das loescht echte Daten und ist Filipes
Entscheidung), sondern der Kommentar richtiggestellt.
Einen Hilfe-Fall zu schliessen ist endgueltig -- es gibt keine
Route, die ihn wieder oeffnet. Vorher stand darueber nur die
Frage nach einem Schlusswort. Jetzt sagt der Dialog es.
pruef-struktur hat meine eigene Pruefung von heute Nachmittag
erwischt: Sie bildete ihr Datum aus UTC. Beim Beheben erst
heuteLokal(datum) genommen -- die Funktion nimmt gar kein Argument
und haette still "heute" statt "+3 Tage" geliefert. Jetzt tagLokal(3),
nachgerechnet: Abstand 3 Tage.
Am Bildschirm angesehen (Rechner 1280, Handy 390): passt rein, Esc
ergibt false, Fokus liegt auf dem harmlosen Knopf, Knoepfe 44px auf
Touch. Der Platzhalter im Abtippfeld zeigte den erwarteten Namen --
das sah aus wie ein schon ausgefuelltes Feld, entfernt.
Neu: server/pruef-nachfrage.mjs -- 17/0, mit sechs Gegenproben und
beiden Richtungen (wer fragt, laedt die Datei; wer nie fragt, laedt
sie nicht -- sonst truege die Anmeldewand 4,8 KB fuer nichts).
pruef-meldungen 8/0, pruef-css-klassen gruen, pruef-struktur gruen,
pruef-leistung gruen.
134 lines
5.2 KiB
JavaScript
134 lines
5.2 KiB
JavaScript
/* ===================================================================
|
||
kalender-abo.js — den eigenen Kalender abonnierbar machen.
|
||
|
||
Eine eigene Datei und nicht ein Anhängsel in kalender.js: Der
|
||
Kalender selbst ist die größte Datei der Anwendung, und diese Sache
|
||
hier hat mit dem Zeichnen von Monaten nichts zu tun. Fällt sie aus,
|
||
steht der Kalender trotzdem.
|
||
|
||
Der Link wird NICHT beim Öffnen der Seite angelegt. Ein Geheimnis,
|
||
das entsteht, ohne dass jemand es wollte, liegt danach herum -- und
|
||
niemand weiß, dass es existiert. Angelegt wird es beim Klick.
|
||
=================================================================== */
|
||
(() => {
|
||
'use strict';
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
if (!$('abo-block')) return; // Seite ohne diesen Block
|
||
|
||
const melde = (t) => { $('abo-fehler').textContent = t || ''; };
|
||
|
||
async function hole(weg, einst) {
|
||
const a = await fetch(weg, { credentials: 'same-origin', ...einst });
|
||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('weg'); }
|
||
return a;
|
||
}
|
||
|
||
function zeige(daten) {
|
||
const hat = !!daten?.link;
|
||
$('abo-hat').hidden = !hat;
|
||
$('abo-hilfe').hidden = !hat;
|
||
$('abo-erzeugen').hidden = hat;
|
||
$('abo-neu').hidden = !hat;
|
||
$('abo-aus').hidden = !hat;
|
||
if (hat) $('abo-link').value = daten.link;
|
||
|
||
/* WANN ZULETZT ABGEHOLT -- die einzige Auskunft, die wirklich sagt,
|
||
ob die Sache funktioniert. "Link erzeugt" beweist nur, dass ein
|
||
Link da ist; erst der Abruf beweist, dass ein Kalender ihn auch
|
||
benutzt. Ohne diese Zeile sucht man bei "kommt nicht an" an der
|
||
falschen Stelle. */
|
||
const stand = $('abo-stand');
|
||
if (!hat) { stand.textContent = ''; return; }
|
||
if (!daten.zuletzt) {
|
||
stand.textContent = 'Noch nie abgerufen – sobald dein Kalender ihn holt, steht es hier.';
|
||
stand.dataset.warten = 'ja';
|
||
return;
|
||
}
|
||
delete stand.dataset.warten;
|
||
const d = new Date(daten.zuletzt);
|
||
const her = Math.round((Date.now() - d.getTime()) / 60000);
|
||
const wann = her < 2 ? 'gerade eben'
|
||
: her < 60 ? `vor ${her} Minuten`
|
||
: her < 60 * 48 ? `vor ${Math.round(her / 60)} Stunden`
|
||
: `am ${d.toLocaleDateString('de-DE')}`;
|
||
stand.textContent = `Dein Kalender hat die Termine zuletzt ${wann} abgeholt.`;
|
||
}
|
||
|
||
async function laden() {
|
||
try {
|
||
const a = await hole('/workspace/api/ics');
|
||
if (!a.ok) return;
|
||
zeige(await a.json());
|
||
} catch { /* umgeleitet */ }
|
||
}
|
||
|
||
async function erzeugen(erneuern) {
|
||
if (erneuern && !await frageNach({
|
||
titel: 'Neuen Link erzeugen?',
|
||
was: 'Der alte hört im selben Moment auf zu funktionieren.',
|
||
bleibt: 'Deine Termine selbst bleiben. Ein Kalender, der den alten Link '
|
||
+ 'abonniert hat, bekommt aber keine mehr – bis du ihm den neuen gibst.',
|
||
endgueltig: true, ja: 'Neuen Link erzeugen',
|
||
})) return;
|
||
melde('');
|
||
try {
|
||
const a = await hole('/workspace/api/ics', {
|
||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
|
||
});
|
||
if (!a.ok) { melde('Ging gerade nicht. Bitte später noch einmal.'); return; }
|
||
const d = await a.json();
|
||
zeige({ link: d.link, zuletzt: null });
|
||
/* Nach dem Erzeugen die Anleitung aufklappen -- wer gerade einen
|
||
Link bekommen hat, will als Nächstes wissen, wohin damit. */
|
||
$('abo-hilfe').hidden = false;
|
||
} catch { /* umgeleitet */ }
|
||
}
|
||
|
||
async function abschalten() {
|
||
if (!await frageNach({
|
||
titel: 'Kalender-Link abschalten?',
|
||
was: 'Deine Termine verschwinden aus jedem Kalender, der sie abonniert hat.',
|
||
bleibt: 'Die Termine selbst bleiben hier im Workspace vollständig stehen. '
|
||
+ 'Du kannst jederzeit einen neuen Link erzeugen.',
|
||
ja: 'Abschalten',
|
||
})) return;
|
||
melde('');
|
||
try {
|
||
const a = await hole('/workspace/api/ics', { method: 'DELETE' });
|
||
if (!a.ok) { melde('Ging gerade nicht.'); return; }
|
||
zeige(null);
|
||
} catch { /* umgeleitet */ }
|
||
}
|
||
|
||
async function kopieren() {
|
||
const feld = $('abo-link');
|
||
const knopf = $('abo-kopieren');
|
||
try {
|
||
await navigator.clipboard.writeText(feld.value);
|
||
} catch {
|
||
/* Ohne Zwischenablage-Recht (unsicherer Kontext, alter Browser)
|
||
wenigstens markieren -- dann reicht Strg+C. Wortlos scheitern
|
||
wäre das Schlechteste: Man klickt dreimal und weiß nicht, ob
|
||
etwas passiert ist. */
|
||
feld.focus();
|
||
feld.select();
|
||
knopf.textContent = 'Jetzt Strg+C';
|
||
setTimeout(() => { knopf.textContent = 'Kopieren'; }, 3000);
|
||
return;
|
||
}
|
||
knopf.textContent = 'Kopiert';
|
||
setTimeout(() => { knopf.textContent = 'Kopieren'; }, 2000);
|
||
}
|
||
|
||
$('abo-erzeugen').addEventListener('click', () => erzeugen(false));
|
||
$('abo-neu').addEventListener('click', () => erzeugen(true));
|
||
$('abo-aus').addEventListener('click', abschalten);
|
||
$('abo-kopieren').addEventListener('click', kopieren);
|
||
/* Erst laden, wenn jemand aufklappt -- die Seite hat beim Öffnen
|
||
Wichtigeres zu tun, und die meisten klappen nie auf. */
|
||
$('abo-block').addEventListener('toggle', () => {
|
||
if ($('abo-block').open) laden();
|
||
}, { once: true });
|
||
})();
|