Screenshots zu Ideen, und der Bot legt die Forum-Tags selbst an
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Zwei Fragen, zwei Antworten — eine davon ein Nein mit Umweg.

## Bilder im Eingabefenster: geht nicht

Discord-Modals koennen nur Textfelder. Kein Datei-Anhang, keine Umgehung — das
ist eine Grenze der Plattform, nicht unseres Codes.

Der Umweg ist besser als er klingt: Wer nach dem Einreichen einen Screenshot in
den Thread haengt, sieht ihn auf der Ideen-Seite. Die Bestaetigung nach dem
Absenden sagt das jetzt auch. Damit kann man auch nachtraeglich noch Bilder
dazulegen, was im Eingabefenster gar nicht ginge.

## Warum nicht einfach der Discord-Link

Discord haengt seit einiger Zeit eine Signatur an die Anhang-Links
(?ex=&is=&hm=), die nach etwa einem Tag ablaeuft. Ein gespeicherter Link zeigt
morgen ein kaputtes Bild. Deshalb laden Devlogs und Galerie seit dem ersten Tag
herunter — im Kopf von devlog-archive.js steht der Grund. Wuensche machen es
jetzt genauso, ausgeliefert unter /wish-assets/.

Beim Loeschen eines Beitrags oder eines ganzen Wunsches gehen die Dateien mit;
sonst waechst der Ordner mit Bildern, auf die nichts mehr zeigt.

Das Herunterladen stand vorher zweimal fast wortgleich im Code. Mit den
Wuenschen waeren es drei Kopien geworden — also einmal nach bot/bilder.js, und
Devlogs und Galerie ziehen mit um. Ein Fehler steckt jetzt an einer Stelle
statt an dreien.

## Forum-Tags per Knopf

Bisher musstest du sie von Hand anlegen. Jetzt macht es das Panel: die Bereiche
von dort plus die fuenf Staende, mit ihren Emojis. Vorhandene bleiben, auch
fremde — die Liste wird ergaenzt und nicht ersetzt. Discords Grenze von 20 Tags
wird eingehalten, was nicht mehr reinpasst, wird gemeldet statt still
verschluckt.

Geprueft: forumTagsAnlegen gegen eine Kanal-Attrappe — leeres Forum, teilweise
vorhandene, Gross/Kleinschreibung, fremde Tags, nichts zu tun (dann wird auch
nicht geschrieben), volles Forum, kein Forum, kein Kanal. Im Browser die
Ideen-Seite mit Bildern in einem Beitrag, einem Beitrag nur aus Bild ohne Text,
und der Lightbox darueber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 00:17:01 +02:00
co-authored by Claude Opus 5
parent 5073e7bcf4
commit b3758f1f76
12 changed files with 237 additions and 74 deletions
+21 -5
View File
@@ -1887,12 +1887,14 @@ export default function Settings({ me }) {
</p>
<p className="section-intro">
Ist der Voting-Kanal ein <strong>Forum</strong>, wird jeder Wunsch ein
Forums-Beitrag und bekommt Tags. Leg dort Tags mit denselben Namen an wie hier
die Bereiche, dazu <em>Wird geprüft</em>, <em>Geplant</em>, <em>In Arbeit</em>,
<em>Umgesetzt</em> und <em>Nicht geplant</em> dann kann man auch in Discord
danach filtern, und der Stand zieht automatisch mit. Fehlt ein Tag, bleibt es
einfach ohne.
Forums-Beitrag und bekommt Tags dann kann man auch in Discord danach
filtern, und der Stand zieht beim Ändern automatisch mit. Der Knopf legt die
fehlenden Tags an: die Bereiche von hier plus die fünf Stände. Vorhandene
bleiben, wie sie sind.
</p>
<div className="field-row" style={{ marginBottom: '1rem' }}>
<button className="btn" onClick={forumTags}>Fehlende Tags im Forum anlegen</button>
</div>
{bereiche.length === 0 && <p className="notice">Noch keine Bereiche.</p>}
@@ -2174,6 +2176,20 @@ export default function Settings({ me }) {
}
}
/** Fehlende Tags im Forum anlegen — Bereiche plus die fuenf Staende */
async function forumTags() {
try {
const res = await apiPost('/api/wish-categories/tags');
const teile = [];
if (res.angelegt.length) teile.push(`${res.angelegt.length} angelegt`);
if (res.schonDa.length) teile.push(`${res.schonDa.length} gab es schon`);
if (res.keinPlatz.length) teile.push(`${res.keinPlatz.length} passten nicht mehr rein`);
flash(`✓ ${teile.join(', ') || 'nichts zu tun'}`);
} catch (e) {
flash(`✗ ${e.body?.error ?? 'Fehlgeschlagen'}`);
}
}
async function bereichLoeschen(b) {
if (!window.confirm(`Bereich „${b.name}" entfernen? Die Wünsche darin bleiben, stehen dann nur ohne Schild da.`)) return;
try {
+30 -1
View File
@@ -8,6 +8,7 @@ import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { apiGet, apiPost } from '../api.js';
import { SkeletonCards } from '../components/Skeleton.jsx';
import Lightbox from '../components/Lightbox.jsx';
import { IconThumbsUp, IconMessage, IconArrowLeft } from '../icons.jsx';
import { useT, useFmt } from '../i18n.jsx';
@@ -25,6 +26,7 @@ export default function WunschDetail() {
const { datum } = useFmt();
const [daten, setDaten] = useState(null);
const [fehler, setFehler] = useState(false);
const [lightbox, setLightbox] = useState(null);
useEffect(() => {
setDaten(null);
@@ -130,7 +132,26 @@ export default function WunschDetail() {
})}
</span>
</div>
<p className="wd-komm-inhalt">{k.content}</p>
{k.content && <p className="wd-komm-inhalt">{k.content}</p>}
{/* Bilder liegen lokal — Discord-Links
laufen ab und waeren morgen kaputt */}
{k.images?.length > 0 && (
<div className="wd-bilder">
{k.images.map((datei, i) => (
<img
key={datei}
className="wd-bild"
src={`/wish-assets/${datei}`}
alt=""
loading="lazy"
onClick={() => setLightbox({
images: k.images.map((f) => `/wish-assets/${f}`),
start: i,
})}
/>
))}
</div>
)}
</div>
</div>
))}
@@ -145,6 +166,14 @@ export default function WunschDetail() {
</>
)}
</section>
{lightbox && (
<Lightbox
images={lightbox.images}
start={lightbox.start}
onClose={() => setLightbox(null)}
/>
)}
</>
);
}
+6
View File
@@ -2052,6 +2052,12 @@ label.toggle {
margin: .25rem 0 0; font-weight: 300; line-height: 1.55;
white-space: pre-wrap; overflow-wrap: anywhere;
}
.wd-bilder { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .5rem; }
.wd-bild {
max-height: 180px; max-width: 100%; border-radius: 6px; cursor: zoom-in;
border: 1px solid var(--border); object-fit: cover;
}
.wd-bild:hover { border-color: var(--neon); }
.wd-mitreden { margin-top: 1.2rem; display: inline-flex; align-items: center; gap: .45rem; }
/* Die Idee in der Liste führt jetzt zur eigenen Seite */