Session 2026-04-20: Medien-Konvertierung, Umami Analytics, Username/Privacy
- HEIC→JPEG, MOV/AVI→MP4 Konvertierung bei allen Upload-Endpoints (media_utils.py) - ffmpeg im Docker-Image, Video-Thumbnails (extract_video_thumb, poster-Attribut) - Google Analytics entfernt, Umami self-hosted eingebunden (index.html, datenschutz.js) - Admin-Panel Analytics-Tab: Stat-Cards, Sparkline 7 Tage, Top-Seiten (Umami-API-Proxy) - Admin-Panel Tab-Icons korrigiert (aus vorhandenem Phosphor-Sprite) - users.real_name Spalte: Username öffentlich, echter Name privat und optional - Registrierung: Label "Benutzername", Leerzeichen verboten, Profanity-Blockliste - Datenschutzerklärung: GA-Abschnitt durch Umami-Text ersetzt
This commit is contained in:
parent
9a78121a3e
commit
5141ba9969
20 changed files with 524 additions and 143 deletions
|
|
@ -330,79 +330,9 @@
|
|||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
<!-- Google Analytics (Option B: cookieless/anonymisiert, kein Consent nötig)
|
||||
Wechsel zu Option A (mit Consent-Banner): GA_MODE auf 'A' setzen -->
|
||||
<script>
|
||||
(function() {
|
||||
var GA_ID = 'G-YLG780DV3Z';
|
||||
var GA_MODE = 'B'; // 'B' = cookieless | 'A' = mit Consent-Banner
|
||||
<!-- Umami Analytics (self-hosted, cookiefrei, DSGVO-konform) -->
|
||||
<script defer src="https://umami.motocamp.de/script.js" data-website-id="d1b5fe13-0e6f-4461-a176-c5439cbbc27f"></script>
|
||||
|
||||
function _loadGA(withConsent) {
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA_ID;
|
||||
document.head.appendChild(s);
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function(){ dataLayer.push(arguments); };
|
||||
gtag('js', new Date());
|
||||
if (withConsent) {
|
||||
gtag('config', GA_ID, { anonymize_ip: true });
|
||||
} else {
|
||||
// Option B: komplett cookieless
|
||||
gtag('config', GA_ID, { anonymize_ip: true, storage: 'none', client_storage: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
// Opt-out respektieren (setzbar über Datenschutz-Seite)
|
||||
if (localStorage.getItem('gaOptOut') === 'yes') return;
|
||||
|
||||
if (GA_MODE === 'B') {
|
||||
_loadGA(false);
|
||||
|
||||
} else {
|
||||
// Option A: Consent-Banner
|
||||
var consent = localStorage.getItem('gaConsent');
|
||||
if (consent === 'yes') { _loadGA(true); }
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
if (consent !== null) return;
|
||||
var banner = document.getElementById('ga-consent-banner');
|
||||
if (banner) banner.style.display = 'flex';
|
||||
document.getElementById('ga-consent-accept')?.addEventListener('click', function() {
|
||||
localStorage.setItem('gaConsent', 'yes');
|
||||
_loadGA(true);
|
||||
banner.style.display = 'none';
|
||||
});
|
||||
document.getElementById('ga-consent-decline')?.addEventListener('click', function() {
|
||||
localStorage.setItem('gaConsent', 'no');
|
||||
banner.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- GA Consent-Banner (nur für Option A aktiv, im Mode B versteckt) -->
|
||||
<div id="ga-consent-banner"
|
||||
style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:9000;
|
||||
background:var(--c-surface,#fff);border-top:1px solid var(--c-border,#e5e7eb);
|
||||
padding:var(--space-3) var(--space-4);flex-wrap:wrap;
|
||||
align-items:center;gap:var(--space-3)">
|
||||
<span style="flex:1;min-width:200px;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
Wir nutzen Google Analytics (anonymisiert) um die App zu verbessern.
|
||||
<span data-page="datenschutz" style="color:var(--c-primary);cursor:pointer;text-decoration:underline">Mehr erfahren</span>
|
||||
</span>
|
||||
<div style="display:flex;gap:var(--space-2)">
|
||||
<button id="ga-consent-decline"
|
||||
style="padding:var(--space-2) var(--space-3);border:1px solid var(--c-border,#e5e7eb);
|
||||
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||
font-size:var(--text-xs);color:var(--c-text-secondary)">Ablehnen</button>
|
||||
<button id="ga-consent-accept"
|
||||
style="padding:var(--space-2) var(--space-3);border:none;
|
||||
border-radius:var(--radius-md);background:var(--c-primary,#C4843A);
|
||||
color:#fff;cursor:pointer;font-size:var(--text-xs)">Akzeptieren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Offline-Banner Logik -->
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ window.Page_admin = (() => {
|
|||
let _tab = 'uebersicht';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'uebersicht', label: 'Übersicht', icon: 'chart-bar' },
|
||||
{ id: 'nutzer', label: 'Nutzer', icon: 'users' },
|
||||
{ id: 'forum', label: 'Forum & Meldungen',icon: 'chat-circle-dots' },
|
||||
{ id: 'system', label: 'System', icon: 'cpu' },
|
||||
{ id: 'jobs', label: 'Jobs', icon: 'timer' },
|
||||
{ id: 'audit', label: 'Audit-Log', icon: 'list-bullets' },
|
||||
{ id: 'uebersicht', label: 'Übersicht', icon: 'house-line' },
|
||||
{ id: 'nutzer', label: 'Nutzer', icon: 'users' },
|
||||
{ id: 'forum', label: 'Forum & Meldungen', icon: 'chat-circle-dots' },
|
||||
{ id: 'analytics', label: 'Analytics', icon: 'target' },
|
||||
{ id: 'system', label: 'System', icon: 'gear' },
|
||||
{ id: 'jobs', label: 'Jobs', icon: 'clock' },
|
||||
{ id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' },
|
||||
];
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -73,18 +74,108 @@ window.Page_admin = (() => {
|
|||
el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted)">Lade…</div>`;
|
||||
try {
|
||||
switch (_tab) {
|
||||
case 'uebersicht': await _renderStats(el); break;
|
||||
case 'nutzer': await _renderUsers(el); break;
|
||||
case 'forum': await _renderForum(el); break;
|
||||
case 'system': await _renderSystem(el); break;
|
||||
case 'jobs': await _renderJobs(el); break;
|
||||
case 'audit': await _renderAudit(el); break;
|
||||
case 'uebersicht': await _renderStats(el); break;
|
||||
case 'nutzer': await _renderUsers(el); break;
|
||||
case 'forum': await _renderForum(el); break;
|
||||
case 'analytics': await _renderAnalytics(el); break;
|
||||
case 'system': await _renderSystem(el); break;
|
||||
case 'jobs': await _renderJobs(el); break;
|
||||
case 'audit': await _renderAudit(el); break;
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerHTML = _emptyState('warning', 'Fehler', e.message || 'Unbekannter Fehler.');
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TAB: ANALYTICS
|
||||
async function _renderAnalytics(el) {
|
||||
const d = await API.get('/admin/analytics');
|
||||
|
||||
const pv = d.pageviews?.pageviews ?? [];
|
||||
const ses = d.pageviews?.sessions ?? [];
|
||||
|
||||
// Sparkline SVG (Seitenaufrufe 7 Tage)
|
||||
function _sparkline(data, color) {
|
||||
if (!data.length) return '<span style="color:var(--c-text-muted);font-size:var(--text-xs)">Keine Daten</span>';
|
||||
const vals = data.map(p => p.y ?? 0);
|
||||
const max = Math.max(...vals, 1);
|
||||
const W = 200, H = 48, pad = 4;
|
||||
const pts = vals.map((v, i) => {
|
||||
const x = pad + i * ((W - 2*pad) / Math.max(vals.length - 1, 1));
|
||||
const y = H - pad - (v / max) * (H - 2*pad);
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||
}).join(' ');
|
||||
return `<svg viewBox="0 0 ${W} ${H}" style="width:100%;height:48px">
|
||||
<polyline points="${pts}" fill="none" stroke="${color}" stroke-width="2"
|
||||
stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
const tv = v => v?.value ?? 0;
|
||||
const fmt = v => Number(v).toLocaleString('de');
|
||||
|
||||
// Bounce Rate & Verweildauer
|
||||
const bounceToday = d.today?.bounceRate?.value != null
|
||||
? (d.today.bounceRate.value * 100).toFixed(0) + ' %'
|
||||
: (d.today?.bounces?.value != null && d.today?.visits?.value > 0
|
||||
? ((d.today.bounces.value / d.today.visits.value) * 100).toFixed(0) + ' %'
|
||||
: '—');
|
||||
const timeWeek = d.week?.totaltime?.value > 0 && d.week?.visits?.value > 0
|
||||
? Math.round(d.week.totaltime.value / d.week.visits.value) + ' s'
|
||||
: '—';
|
||||
|
||||
el.innerHTML = `
|
||||
<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:var(--space-3)">
|
||||
${_statCard('user', 'Besucher heute', fmt(tv(d.today?.visitors)), 'var(--c-primary)')}
|
||||
${_statCard('eye', 'Aufrufe heute', fmt(tv(d.today?.pageviews)), 'var(--c-primary)')}
|
||||
${_statCard('users','Besucher 7 Tage', fmt(tv(d.week?.visitors)), 'var(--c-success)')}
|
||||
${_statCard('eye', 'Aufrufe 7 Tage', fmt(tv(d.week?.pageviews)), 'var(--c-success)')}
|
||||
${_statCard('arrow-u-up-left','Bounce heute', bounceToday, 'var(--c-text-secondary)')}
|
||||
${_statCard('timer','Ø Verweildauer 7 Tage', timeWeek, 'var(--c-text-secondary)')}
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:var(--space-4)">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin-bottom:var(--space-3)">Seitenaufrufe — letzte 7 Tage</div>
|
||||
${_sparkline(pv, 'var(--c-primary)')}
|
||||
<div style="display:flex;justify-content:space-between;
|
||||
font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">
|
||||
${pv.map(p => `<span>${new Date(p.x).toLocaleDateString('de',{weekday:'short'})}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:var(--space-4)">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin-bottom:var(--space-3)">Top Seiten — letzte 7 Tage</div>
|
||||
${(d.top_pages ?? []).length === 0
|
||||
? `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Daten</p>`
|
||||
: `<div style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${d.top_pages.map(p => {
|
||||
const maxY = d.top_pages[0].y;
|
||||
const pct = maxY > 0 ? (p.y / maxY * 100).toFixed(0) : 0;
|
||||
return `
|
||||
<div>
|
||||
<div style="display:flex;justify-content:space-between;
|
||||
font-size:var(--text-xs);margin-bottom:3px">
|
||||
<span style="color:var(--c-text);overflow:hidden;text-overflow:ellipsis;
|
||||
white-space:nowrap;max-width:75%">${UI.escape(p.x)}</span>
|
||||
<span style="color:var(--c-text-secondary);flex-shrink:0">${fmt(p.y)}</span>
|
||||
</div>
|
||||
<div style="height:4px;border-radius:2px;background:var(--c-surface-3)">
|
||||
<div style="height:100%;width:${pct}%;border-radius:2px;
|
||||
background:var(--c-primary);transition:width .3s"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>`}
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// TAB: ÜBERSICHT
|
||||
// ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -5,36 +5,19 @@
|
|||
window.Page_datenschutz = (() => {
|
||||
|
||||
function init(container) {
|
||||
const optOut = localStorage.getItem('gaOptOut') === 'yes';
|
||||
const gaSection = `
|
||||
const umamiSection = `
|
||||
<section style="margin-bottom:var(--space-6)">
|
||||
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin:0 0 var(--space-2)">Google Analytics</h2>
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0 0 var(--space-3)">
|
||||
Wir nutzen Google Analytics 4 (Google LLC, USA) zur anonymisierten Analyse der App-Nutzung.
|
||||
Deine IP-Adresse wird gekürzt, es werden keine Cookies gesetzt und keine
|
||||
personenbezogenen Daten gespeichert. Die Verarbeitung erfolgt auf Basis von
|
||||
Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an anonymer Nutzungsanalyse).
|
||||
Du kannst der Erhebung jederzeit widersprechen.
|
||||
color:var(--c-text);margin:0 0 var(--space-2)">Nutzungsanalyse (Umami)</h2>
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||
Wir verwenden Umami, ein datenschutzfreundliches Analysetool, das ausschließlich auf
|
||||
unserem eigenen Server betrieben wird. Es werden keine Cookies gesetzt, keine
|
||||
personenbezogenen Daten erhoben und keine Daten an Dritte weitergegeben.
|
||||
Erfasst werden lediglich anonyme Seitenaufrufe zur Verbesserung der App.
|
||||
Eine Rechtsgrundlage nach Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) ist
|
||||
gegeben; ein Widerspruch oder Opt-out ist nicht erforderlich, da keine
|
||||
personenbezogenen Daten verarbeitet werden.
|
||||
</p>
|
||||
${optOut ? `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-success,#16a34a);margin:0 0 var(--space-3)">
|
||||
Analytics ist für dich <strong>deaktiviert</strong>.
|
||||
</p>
|
||||
<button id="ga-optin-btn"
|
||||
style="padding:var(--space-2) var(--space-4);border:1px solid var(--c-border,#e5e7eb);
|
||||
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||
font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
Analytics wieder aktivieren
|
||||
</button>
|
||||
` : `
|
||||
<button id="ga-optout-btn"
|
||||
style="padding:var(--space-2) var(--space-4);border:1px solid var(--c-border,#e5e7eb);
|
||||
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||
font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
Analytics deaktivieren (Opt-out)
|
||||
</button>
|
||||
`}
|
||||
</section>
|
||||
`;
|
||||
|
||||
|
|
@ -78,7 +61,7 @@ window.Page_datenschutz = (() => {
|
|||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||
Die Verarbeitung erfolgt auf Basis von Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung)
|
||||
für alle zur Bereitstellung des Dienstes notwendigen Daten, sowie Art. 6 Abs. 1 lit. a
|
||||
DSGVO (Einwilligung) für optionale Funktionen wie Standortfreigabe und Analytics.
|
||||
DSGVO (Einwilligung) für optionale Funktionen wie Standortfreigabe.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
|
@ -92,7 +75,7 @@ window.Page_datenschutz = (() => {
|
|||
</p>
|
||||
</section>
|
||||
|
||||
${gaSection}
|
||||
${umamiSection}
|
||||
|
||||
<section style="margin-bottom:var(--space-6)">
|
||||
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||
|
|
@ -123,16 +106,6 @@ window.Page_datenschutz = (() => {
|
|||
</div>
|
||||
`;
|
||||
|
||||
container.querySelector('#ga-optout-btn')?.addEventListener('click', () => {
|
||||
localStorage.setItem('gaOptOut', 'yes');
|
||||
UI.toast.success('Analytics deaktiviert. Wirksam nach nächstem App-Neustart.');
|
||||
init(container);
|
||||
});
|
||||
container.querySelector('#ga-optin-btn')?.addEventListener('click', () => {
|
||||
localStorage.removeItem('gaOptOut');
|
||||
UI.toast.success('Analytics wieder aktiviert. Wirksam nach nächstem App-Neustart.');
|
||||
init(container);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,11 @@ window.Page_diary = (() => {
|
|||
if (!url) return false;
|
||||
return _VIDEO_EXT.has(url.slice(url.lastIndexOf('.')).toLowerCase());
|
||||
}
|
||||
function _videoPoster(url) { return url.replace(/\.[^.]+$/, '_thumb.jpg'); }
|
||||
function _mediaHtml(url, style = '') {
|
||||
if (!url) return '';
|
||||
return _isVideo(url)
|
||||
? `<video src="${url}" controls playsinline style="width:100%;border-radius:var(--radius-md);${style}"></video>`
|
||||
? `<video src="${url}" poster="${_videoPoster(url)}" controls playsinline style="width:100%;border-radius:var(--radius-md);${style}"></video>`
|
||||
: `<img src="${url}" alt="Foto" style="width:100%;border-radius:var(--radius-md);${style}">`;
|
||||
}
|
||||
|
||||
|
|
@ -454,7 +455,7 @@ window.Page_diary = (() => {
|
|||
<span style="font-size:12px;color:var(--c-text-secondary)">PDF öffnen</span>
|
||||
</a>`
|
||||
: m.media_type === 'video'
|
||||
? `<video src="${UI.escape(m.url)}" controls playsinline style="width:100%;max-height:55vh;display:block;object-fit:contain;background:#000"></video>`
|
||||
? `<video src="${UI.escape(m.url)}" poster="${UI.escape(_videoPoster(m.url))}" controls playsinline style="width:100%;max-height:55vh;display:block;object-fit:contain;background:#000"></video>`
|
||||
: `<img src="${UI.escape(m.url)}" data-idx="${allMedia.indexOf(m)}" style="width:100%;max-height:55vh;object-fit:cover;display:block;cursor:zoom-in">`;
|
||||
|
||||
let mediaSection = '';
|
||||
|
|
@ -470,7 +471,7 @@ window.Page_diary = (() => {
|
|||
${m.media_type === 'pdf'
|
||||
? `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:var(--c-surface-2)"><svg class="ph-icon" style="width:28px;height:28px;color:var(--c-danger)" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg></div>`
|
||||
: m.media_type === 'video'
|
||||
? `<video src="${UI.escape(m.url)}" style="width:100%;height:100%;object-fit:cover;pointer-events:none"></video>`
|
||||
? `<video src="${UI.escape(m.url)}" poster="${UI.escape(_videoPoster(m.url))}" style="width:100%;height:100%;object-fit:cover;pointer-events:none"></video>`
|
||||
: `<img src="${UI.escape(m.url)}" style="width:100%;height:100%;object-fit:cover">`}
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
|
|
@ -761,7 +762,7 @@ window.Page_diary = (() => {
|
|||
<div style="position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--c-surface-2)"
|
||||
data-media-id="${m.id ?? ''}">
|
||||
${m.media_type === 'video'
|
||||
? `<video src="${m.url}" style="width:100%;height:100%;object-fit:cover;display:block" muted playsinline></video>`
|
||||
? `<video src="${m.url}" poster="${_videoPoster(m.url)}" style="width:100%;height:100%;object-fit:cover;display:block" muted playsinline></video>`
|
||||
: `<img src="${m.url}" alt="" style="width:100%;height:100%;object-fit:cover;display:block">`}
|
||||
<button type="button" class="diary-media-thumb-del"
|
||||
data-media-id="${m.id ?? ''}" data-legacy="${m.id == null ? '1' : ''}"
|
||||
|
|
|
|||
|
|
@ -328,9 +328,11 @@ window.Page_forum = (() => {
|
|||
if (u.endsWith('.pdf'))
|
||||
return `<a href="${_esc(u)}" target="_blank" rel="noopener" class="forum-pdf-card">
|
||||
${UI.icon('file-text')} <span>${_esc(u.split('/').pop())}</span></a>`;
|
||||
if (/\.(mp4|mov|webm|m4v|avi)$/i.test(u))
|
||||
return `<video src="${_esc(u)}" controls playsinline
|
||||
if (/\.(mp4|mov|webm|m4v|avi)$/i.test(u)) {
|
||||
const poster = u.replace(/\.[^.]+$/, '_thumb.jpg');
|
||||
return `<video src="${_esc(u)}" poster="${_esc(poster)}" controls playsinline
|
||||
style="max-width:100%;max-height:320px;border-radius:var(--radius-md);display:block"></video>`;
|
||||
}
|
||||
return `<img src="${_esc(u)}" class="forum-foto-img" data-src="${_esc(u)}" alt="" loading="lazy">`;
|
||||
};
|
||||
const fotoGallery = (thread.foto_urls?.length)
|
||||
|
|
|
|||
|
|
@ -429,6 +429,13 @@ window.Page_settings = (() => {
|
|||
title: 'Profil bearbeiten',
|
||||
body: `
|
||||
<form id="profile-form" style="display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Echter Name (privat)</label>
|
||||
<input name="real_name" type="text" maxlength="80"
|
||||
placeholder="z. B. Maria Müller"
|
||||
value="${_esc(u.real_name || '')}"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Bio</label>
|
||||
<textarea name="bio" maxlength="300" rows="4"
|
||||
|
|
@ -473,6 +480,7 @@ window.Page_settings = (() => {
|
|||
const fd = UI.formData(e.target);
|
||||
await UI.asyncButton(btn, async () => {
|
||||
const updated = await API.patch('/profile', {
|
||||
real_name: fd.real_name || '',
|
||||
bio: fd.bio || '',
|
||||
wohnort: fd.wohnort || '',
|
||||
erfahrung: fd.erfahrung || '',
|
||||
|
|
@ -714,9 +722,13 @@ window.Page_settings = (() => {
|
|||
return `
|
||||
<form id="auth-form" autocomplete="on" novalidate>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Dein Name</label>
|
||||
<label class="form-label">Benutzername</label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
placeholder="z. B. Maria" autocomplete="name" required>
|
||||
placeholder="z. B. bellas_mama" autocomplete="username" required
|
||||
pattern="^\\S+$" title="Kein Leerzeichen erlaubt">
|
||||
<p style="margin:var(--space-1) 0 0;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
Dieser Name ist öffentlich sichtbar und wird bei deinen Beiträgen, Pins und Kommentaren angezeigt.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">E-Mail</label>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue