Anhaenge an Aufgaben -- ohne eine zweite Dateiablage
Stufe 9 aus dem Plan. Eine Aufgabe "Vertrag pruefen" ohne den Vertrag daneben ist eine Aufforderung zum Suchen. KEIN ZWEITER HOCHLADEWEG Dateien leben in der Dateiablage -- mit ihrer Sichtbarkeit, ihren Freigaben, ihren Fassungen und ihrem Protokoll. Ein eigener Weg an der Aufgabe waere eine zweite Ablage mit einer zweiten Rechtelogik gewesen, und die zweite ist immer die, die etwas durchlaesst. Deshalb nur eine Spalte: Die Datei WEISS, zu welcher Aufgabe sie gehoert. Hochgeladen wird ueber denselben Weg wie immer, mit einem Kopf mehr. Alles andere bleibt, wo es schon richtig ist -- die Anhaenge tragen deshalb auch ihre Fassungsnummer und die Warnung "nicht mehr aktuell" mit, ohne dass dafuer eine Zeile geschrieben werden musste. DER RIEGEL Waere `x-aufgabe` ungeprueft, waere der Kopf ein Weg, die Existenz fremder Aufgaben zu erfahren -- man muesste nur Zahlen durchprobieren. Geprueft wird deshalb gegen dieselbe Schranke wie fuer die Datei selbst: `darfCreator`, die eine Stelle im Haus, an der diese Frage beantwortet wird. Eine eigene Herleitung hier waere eine zweite Meinung darueber gewesen. (Erster Anlauf: genau so eine Herleitung, mit einer Funktion, die gar nicht importiert war.) Gemessen: Ein Scout darf an die Aufgabe SEINER Creatorin, an die eines fremden Creators nicht -- mit wortgleicher Absage wie bei einer erfundenen Nummer. Der Unterschied waere sonst die Auskunft. Und es bleibt auch keine lose Datei liegen: Die Pruefung findet vor dem ersten Byte statt. ON DELETE SET NULL, NICHT CASCADE Der wichtigste Teil der Spalte: Wer eine Aufgabe loescht, will die Aufgabe loeschen, nicht den Vertrag, der daran hing. Die Datei verliert nur ihren Bezug und steht danach wieder in der Ablage. Gemessen. EIN EIGENER FEHLER, GEFUNDEN BEIM MESSEN Der Verweis am Anhang zeigte auf /workspace/api/dateien/:id -- den es gar nicht gibt, der Weg heisst /inhalt. Die Pruefung ruft ihn jetzt wirklich auf und erwartet 200, statt nur die Adresse zu vergleichen. PRUEFUNGEN: pruef-anhaenge neu mit 37, davon 8 im Browser. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -995,3 +995,46 @@
|
||||
@media (forced-colors: active) {
|
||||
.karte__wichtig { border: 1px solid CanvasText; }
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
ANHAENGE IM AUFGABEN-DIALOG (15.09.2026)
|
||||
|
||||
Eine Datei je Zeile, anklickbar, mit Groesse dahinter. "nicht mehr
|
||||
aktuell" darf auffallen -- wer hier die alte Fassung herunterlaedt,
|
||||
merkt es sonst nicht.
|
||||
===================================================================== */
|
||||
|
||||
.anhaenge__liste { display: grid; gap: 5px; margin: 4px 0 8px; }
|
||||
.anhaenge__datei {
|
||||
display: flex; align-items: center; flex-wrap: wrap; gap: 9px;
|
||||
padding: 8px 11px;
|
||||
text-decoration: none;
|
||||
color: var(--text-leise);
|
||||
background: rgba(0, 0, 0, .22);
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.anhaenge__datei:hover { color: var(--text); border-color: var(--rand-hell); }
|
||||
.anhaenge__name { font-size: .86rem; font-weight: 600; }
|
||||
.anhaenge__groesse {
|
||||
font-size: .76rem; color: var(--text-still); margin-left: auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.anhaenge__fassung { font-size: .76rem; color: var(--text-still); }
|
||||
.anhaenge__alt {
|
||||
font-size: .76rem; font-weight: 640;
|
||||
padding: 1px 8px; border-radius: 999px;
|
||||
color: #d8a76a;
|
||||
border: 1px solid color-mix(in srgb, #cc9451 45%, transparent);
|
||||
}
|
||||
.anhaenge__leer { font-size: .82rem; color: var(--text-still); }
|
||||
.anhaenge__zufuegen { display: inline-block; cursor: pointer; }
|
||||
.anhaenge__knopf {
|
||||
display: inline-block;
|
||||
font-size: .8rem; font-weight: 600;
|
||||
padding: 7px 14px; border-radius: 999px;
|
||||
color: var(--text-leise);
|
||||
border: 1px solid var(--rand);
|
||||
}
|
||||
.anhaenge__zufuegen:hover .anhaenge__knopf { color: var(--text); border-color: var(--rand-hell); }
|
||||
.anhaenge__hinweis { margin: 6px 0 0; font-size: .78rem; color: var(--text-still); }
|
||||
|
||||
@@ -843,6 +843,7 @@
|
||||
}
|
||||
kategorieFeldZeigen('b');
|
||||
if ($('b-kategorie')) $('b-kategorie').value = a.kategorie || '';
|
||||
window.anhaengeLaden?.(a.id);
|
||||
dialog.showModal();
|
||||
$('b-titel').focus();
|
||||
}
|
||||
@@ -1384,3 +1385,106 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/* =====================================================================
|
||||
ANHAENGE AN EINER AUFGABE (15.09.2026, Stufe 9)
|
||||
|
||||
KEIN ZWEITER HOCHLADEWEG. Die Datei geht an denselben Weg wie in der
|
||||
Dateiablage (/workspace/api/dateien) und bekommt nur einen Kopf mehr:
|
||||
die Nummer der Aufgabe. Sichtbarkeit, Freigaben, Fassungen und
|
||||
Protokoll bleiben damit genau dort, wo sie schon richtig sind.
|
||||
|
||||
WAS BEIM LOESCHEN DER AUFGABE PASSIERT, steht in der Datenbank
|
||||
(ON DELETE SET NULL) und nicht hier: Die Datei bleibt, sie verliert
|
||||
nur ihren Bezug. Wer eine Aufgabe loescht, will die Aufgabe loeschen.
|
||||
===================================================================== */
|
||||
(() => {
|
||||
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 feld = $('b-datei');
|
||||
const liste = $('b-anhaenge');
|
||||
const stand = $('b-anhang-stand');
|
||||
if (!feld || !liste) return;
|
||||
|
||||
let fuerAufgabe = null;
|
||||
|
||||
const groesse = (n) => (n >= 1048576
|
||||
? (n / 1048576).toFixed(1) + ' MB'
|
||||
: Math.max(1, Math.round(n / 1024)) + ' KB');
|
||||
|
||||
const sagen = (t) => { if (stand) stand.textContent = t || ''; };
|
||||
|
||||
window.anhaengeLaden = async function (aufgabeId) {
|
||||
fuerAufgabe = aufgabeId;
|
||||
liste.textContent = '';
|
||||
sagen('');
|
||||
try {
|
||||
const a = await fetch('/workspace/api/aufgaben/' + aufgabeId + '/dateien',
|
||||
{ credentials: 'same-origin' });
|
||||
if (!a.ok) return;
|
||||
const d = await a.json();
|
||||
if (!d.dateien?.length) {
|
||||
liste.append(el('span', 'anhaenge__leer', 'Noch nichts angehängt.'));
|
||||
return;
|
||||
}
|
||||
for (const x of d.dateien) {
|
||||
const v = el('a', 'anhaenge__datei');
|
||||
/* Der Herunterlade-Weg heisst /inhalt -- /dateien/:id allein
|
||||
gibt es nicht. Beim ersten Anlauf stand hier der Verweis ins
|
||||
Leere; gefunden hat es der Blick in die Routen, nicht der
|
||||
Klick. */
|
||||
v.href = '/workspace/api/dateien/' + x.id + '/inhalt';
|
||||
v.append(el('span', 'anhaenge__name', x.name_original));
|
||||
v.append(el('span', 'anhaenge__groesse', groesse(x.groesse)));
|
||||
/* Eine ueberholte Fassung ALS SOLCHE kennzeichnen -- sonst laedt
|
||||
jemand hier die alte herunter und merkt es nicht. Genau der
|
||||
Fall, fuer den es die Fassungen gibt. */
|
||||
if (x.ersetzt_durch) {
|
||||
const w = el('span', 'anhaenge__alt', 'nicht mehr aktuell');
|
||||
w.title = 'Es gibt eine neuere Fassung dieser Datei.';
|
||||
v.append(w);
|
||||
} else if (x.fassung > 1) {
|
||||
v.append(el('span', 'anhaenge__fassung', 'Fassung ' + x.fassung));
|
||||
}
|
||||
liste.append(v);
|
||||
}
|
||||
} catch { /* der Dialog ist auch ohne Anhaenge benutzbar */ }
|
||||
};
|
||||
|
||||
feld.addEventListener('change', async () => {
|
||||
const f = feld.files?.[0];
|
||||
if (!f || !fuerAufgabe) return;
|
||||
sagen('Wird hochgeladen …');
|
||||
try {
|
||||
const a = await fetch('/workspace/api/dateien', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': f.type || 'application/octet-stream',
|
||||
'x-dateiname': encodeURIComponent(f.name),
|
||||
'x-aufgabe': String(fuerAufgabe),
|
||||
},
|
||||
body: f,
|
||||
});
|
||||
if (!a.ok) {
|
||||
const j = await a.json().catch(() => ({}));
|
||||
sagen(j.fehler === 'zu_gross'
|
||||
? 'Die Datei ist zu groß.'
|
||||
: (j.fehler || 'Das hat nicht geklappt.'));
|
||||
return;
|
||||
}
|
||||
sagen('Angehängt.');
|
||||
await window.anhaengeLaden(fuerAufgabe);
|
||||
} catch {
|
||||
sagen('Keine Verbindung.');
|
||||
} finally {
|
||||
/* Zuruecksetzen, sonst loest dieselbe Datei kein zweites Mal aus. */
|
||||
feld.value = '';
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user