mirror of
https://github.com/guyg1474/hcrb.git
synced 2026-10-15 23:13:28 +02:00
Rollen (Admin/Mitglied), Seite Kooperationen, Textanpassungen
- Gemeinsame Zugänge mit Rollen: Admin darf alles, Mitglied nutzt Kalender, Fahrtplaner, Treffpunkte und bearbeitet Mitglieder (kein Hinzufügen, Löschen, Import, keine Patches, keine Statistik-Korrektur) - Server prüft die Rechte (403), Konten ohne Rolle gelten als Admin - npm run user: add <name> [admin|mitglied], role <name> <rolle> - Neue öffentliche Seite Kooperationen mit Titelbild, Anführer, Social-Media-Links und Beschreibung, Pflege durch Admin - Kopfzeile bleibt bei mittlerer Breite einzeilig - Texte auf Wunsch gekürzt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f46016487d
commit
819219fb2d
18 files changed
+661
-106
No files matched your search
+7
-30
@@ -1,10 +1,10 @@
|
||||
import { $, api, esc, showError, renderHeader, renderFooter, currentUser } from './common.js';
|
||||
import { $, api, esc, showError, renderHeader, renderFooter, isAdmin, prepareImage } from './common.js';
|
||||
|
||||
renderHeader('patches');
|
||||
renderFooter();
|
||||
|
||||
let patches = [];
|
||||
let loggedIn = false;
|
||||
let admin = false; // nur der Admin-Zugang darf Patches hochladen, ändern, löschen
|
||||
let editingId = null;
|
||||
let imageData = null; // data:-URL des gewählten (verkleinerten) Bildes
|
||||
|
||||
@@ -13,9 +13,9 @@ const imgUrl = (p) => `/patch-img/${encodeURIComponent(p.image)}`;
|
||||
// ---------- Galerie ----------
|
||||
function render() {
|
||||
$('#patch-empty').hidden = patches.length > 0;
|
||||
$('#patch-empty-hint').textContent = loggedIn
|
||||
$('#patch-empty-hint').textContent = admin
|
||||
? 'Lade über „Patch hochladen“ den ersten Patch hoch.'
|
||||
: 'Sobald jemand aus der Gruppe einen Patch hochlädt, erscheint er hier.';
|
||||
: 'Sobald der Admin einen Patch hochlädt, erscheint er hier.';
|
||||
for (const status of ['vorhanden', 'geplant']) {
|
||||
const list = patches.filter((p) => p.status === status);
|
||||
$(`#group-${status}`).hidden = list.length === 0;
|
||||
@@ -34,7 +34,7 @@ function patchHtml(p) {
|
||||
${p.status === 'geplant' ? '<span class="badge nogame">In Planung</span>' : ''}
|
||||
<h3>${esc(p.name)}</h3>
|
||||
${p.description ? `<p>${esc(p.description)}</p>` : ''}
|
||||
${loggedIn ? `
|
||||
${admin ? `
|
||||
<div class="actions">
|
||||
<button class="link" data-edit="${p.id}">Bearbeiten</button>
|
||||
<button class="link danger" data-delete="${p.id}">Löschen</button>
|
||||
@@ -107,29 +107,6 @@ function showPreview(src) {
|
||||
: '<strong>Bild auswählen</strong><br>oder hierher ziehen · PNG, JPEG, WebP oder SVG';
|
||||
}
|
||||
|
||||
// Bild im Browser auf höchstens 1600 px verkleinern; Transparenz bleibt (WebP bzw. PNG)
|
||||
async function prepareImage(file) {
|
||||
// SVG bleibt unverändert (Vektorgrafik, gestochen scharf in jeder Größe)
|
||||
if (file.type === 'image/svg+xml' || /\.svg$/i.test(file.name)) {
|
||||
if (file.size > 2 * 1024 * 1024) throw new Error('SVG ist zu groß (max. 2 MB).');
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
let bin = '';
|
||||
for (let i = 0; i < bytes.length; i += 0x8000) bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
||||
return `data:image/svg+xml;base64,${btoa(bin)}`;
|
||||
}
|
||||
if (!/^image\/(png|jpeg|webp)$/.test(file.type)) throw new Error('Bitte ein PNG-, JPEG-, WebP- oder SVG-Bild wählen.');
|
||||
const bitmap = await createImageBitmap(file);
|
||||
const scale = Math.min(1, 1600 / Math.max(bitmap.width, bitmap.height));
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = Math.round(bitmap.width * scale);
|
||||
canvas.height = Math.round(bitmap.height * scale);
|
||||
canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||
bitmap.close?.();
|
||||
let url = canvas.toDataURL('image/webp', 0.9);
|
||||
// Browser ohne WebP-Export (ältere Safari) liefern PNG – für Fotos ist JPEG dann kleiner
|
||||
if (!url.startsWith('data:image/webp') && file.type === 'image/jpeg') url = canvas.toDataURL('image/jpeg', 0.88);
|
||||
return url;
|
||||
}
|
||||
|
||||
async function pickFile(file) {
|
||||
if (!file) return;
|
||||
@@ -195,8 +172,8 @@ async function load() {
|
||||
render();
|
||||
}
|
||||
|
||||
loggedIn = !!(await currentUser());
|
||||
$('#add-btn').hidden = !loggedIn;
|
||||
admin = await isAdmin();
|
||||
$('#add-btn').hidden = !admin;
|
||||
await load().catch(showError);
|
||||
// Link von der Startseite (/patches#id) springt zum Patch
|
||||
if (location.hash) document.getElementById(location.hash.slice(1))?.scrollIntoView({ block: 'center' });
|
||||
Reference in new issue
Block a user