Statische Codes benennen und auf Wunsch ablegen

Das Feld „Name (optional)" stand nur im Dynamisch-Block. Bei einem statischen
Code gab es damit keine Moeglichkeit, ihm einen Namen zum Wiederfinden zu
geben.

Jetzt gilt es in beiden Betriebsarten und tut zweierlei: Es bestimmt den
Dateinamen beim Herunterladen — aus „Plakat Eingang" wird
qr-plakat-eingang.png statt qr-example-com-plakat.png — und es laesst sich
damit ein statischer Code in „Meine Codes" ablegen.

Ein abgelegter statischer Code ist ein Schnappschuss, kein steuerbares Ziel.
Aenderbar ist nur der Name; Inhalt und Gestaltung bleiben, wie sie beim
Ablegen waren, denn genau das steckt im gedruckten Bild. Er bekommt keinen
Kurz-Link, ist unter /r nicht erreichbar und zaehlt keine Scans — in der
Liste steht dort „statisch" und ein Strich. Wer nach dem Ablegen den Inhalt
aendert, bekommt das Ablegen erneut angeboten; der vorherige Eintrag bleibt
als eigener Schnappschuss.

Dafuer traegt QrCode jetzt `mode`, und `slug` darf leer sein. In PostgreSQL
schliessen sich mehrere NULL in einem eindeutigen Index nicht aus, die
Bedingung „je Domain eindeutig" gilt fuer dynamische Codes also unveraendert.

Refs #2
This commit is contained in:
Florian Wilde
2026-08-20 19:33:49 +02:00
parent 46e7eb0b59
commit eeaf3820b7
15 changed files with 370 additions and 108 deletions
+28 -10
View File
@@ -36,6 +36,8 @@ export const qrRequestSchema = z.object({
slug: z.string().trim().optional(),
/** Domain, unter der die Kurz-URL entsteht. */
domainId: z.string().trim().optional(),
/** Name des Codes — bestimmt den Dateinamen beim Herunterladen. */
label: z.string().trim().max(120).optional(),
});
export type QrRequest = z.infer<typeof qrRequestSchema>;
@@ -137,25 +139,41 @@ export async function resolveQrRequest(
};
}
/** Dateiname für den Download, abgeleitet aus dem Inhalt des Codes. */
/** Macht aus beliebigem Text einen brauchbaren Dateinamen-Bestandteil. */
function alsDateiname(text: string): string {
return text
.toLowerCase()
.replaceAll("ä", "ae")
.replaceAll("ö", "oe")
.replaceAll("ü", "ue")
.replaceAll("ß", "ss")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 60);
}
/**
* Dateiname für den Download.
*
* Wenn ein Name vergeben wurde, gewinnt er — genau dafür ist er da: damit man
* die Datei später wiederfindet. Sonst wie bisher aus dem Inhalt abgeleitet.
*/
export function downloadFilename(
qrType: QrTypeModule,
data: unknown,
extension: string,
label?: string | null,
): string {
const ausName = label ? alsDateiname(label) : "";
if (ausName) return `qr-${ausName}.${extension}`;
let base = "qr-code";
try {
const summary = qrType
.summary(data)
.toLowerCase()
.replaceAll("ä", "ae")
.replaceAll("ö", "oe")
.replaceAll("ü", "ue")
.replaceAll("ß", "ss")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 60);
if (summary) base = `qr-${summary}`;
.toLowerCase();
const gesaeubert = alsDateiname(summary);
if (gesaeubert) base = `qr-${gesaeubert}`;
} catch {
// Zusammenfassung ist optional — im Zweifel bleibt der Standardname.
}