Refactor: 1167 _esc() → UI.escape() in 36 Dateien, SW by-v1113
Bündel 1 aus dem Duplikat-Audit: existierende zentrale Helper nutzen
statt lokale Duplikate.
Pure Migration ohne neuen Code:
- 1167 _esc()-Aufrufe in 36 Page-Modulen migriert auf UI.escape()
- 24 lokale _esc/_escape-Definitionen entfernt
- lost.js hatte _escape() (Variante) — 17 Aufrufe ebenfalls migriert
- jobs.js + breeder.js: tote Alias-Wrapper entfernt
UI.escape() existierte schon — wurde nur überall lokal nochmal
implementiert. Funktional identisch (gleiche 4-replace-chain für
& < > ").
Tests 19/19 grün. Frontend-LOC um ~120 Zeilen reduziert.
Hinweis: _emptyState (7 Stellen) und _icon (8 Stellen) wurden NICHT
migriert — sie haben abweichende Signaturen von UI.emptyState({...})
bzw. UI.icon(name). Eigener Sprint nötig.
This commit is contained in:
parent
e7939ce98e
commit
c517c9281d
42 changed files with 1115 additions and 1341 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1112
|
||||
1113
|
||||
|
|
@ -86,14 +86,14 @@
|
|||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||
<script src="/js/boot-early.js?v=1112"></script>
|
||||
<script src="/js/boot-early.js?v=1113"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1112">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1112">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1112">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1112">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1112">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1113">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1113">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1113">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1113">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1113">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -617,11 +617,11 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js?v=1112"></script>
|
||||
<script src="/js/ui.js?v=1112"></script>
|
||||
<script src="/js/app.js?v=1112"></script>
|
||||
<script src="/js/worlds.js?v=1112"></script>
|
||||
<script src="/js/offline-indicator.js?v=1112"></script>
|
||||
<script src="/js/api.js?v=1113"></script>
|
||||
<script src="/js/ui.js?v=1113"></script>
|
||||
<script src="/js/app.js?v=1113"></script>
|
||||
<script src="/js/worlds.js?v=1113"></script>
|
||||
<script src="/js/offline-indicator.js?v=1113"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
@ -631,7 +631,7 @@
|
|||
|
||||
|
||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||
<script src="/js/boot.js?v=1112"></script>
|
||||
<script src="/js/boot.js?v=1113"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1112'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1113'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
||||
window.APP_VERSION = APP_VERSION;
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ window.Page_admin = (() => {
|
|||
// Manager-Tabelle
|
||||
const managerRows = d.managers.map(m => `
|
||||
<tr>
|
||||
<td style="font-weight:600">${_esc(m.name)}</td>
|
||||
<td style="font-weight:600">${UI.escape(m.name)}</td>
|
||||
<td class="text-right">${m.published}</td>
|
||||
<td class="text-right">${m.with_link}
|
||||
${m.published > 0 ? `<span style="font-size:10px;color:var(--c-text-muted)">
|
||||
|
|
@ -241,13 +241,13 @@ window.Page_admin = (() => {
|
|||
<tr>
|
||||
<td style="color:var(--c-text-muted);white-space:nowrap">${_fmt(p.published_at)}</td>
|
||||
<td style="font-weight:500;max-width:200px;overflow:hidden;text-overflow:ellipsis;
|
||||
white-space:nowrap">${_esc(p.topic)}</td>
|
||||
white-space:nowrap">${UI.escape(p.topic)}</td>
|
||||
<td>${_PL[p.platform]||p.platform||'–'}</td>
|
||||
<td style="font-size:10px;color:var(--c-text-muted)">${_esc(p.category||'–')}</td>
|
||||
<td style="font-size:10px;color:var(--c-text-muted)">${UI.escape(p.category||'–')}</td>
|
||||
<td>${p.ai_score ? '⭐'.repeat(p.ai_score) : '–'}</td>
|
||||
<td style="font-weight:500">${_esc(p.manager||'–')}</td>
|
||||
<td style="font-weight:500">${UI.escape(p.manager||'–')}</td>
|
||||
<td>${p.post_url
|
||||
? `<a href="${_esc(p.post_url)}" target="_blank" rel="noopener"
|
||||
? `<a href="${UI.escape(p.post_url)}" target="_blank" rel="noopener"
|
||||
style="font-size:11px;color:var(--c-primary)">🔗 Link</a>`
|
||||
: `<span style="font-size:11px;color:var(--c-text-muted)">–</span>`}</td>
|
||||
</tr>`).join('');
|
||||
|
|
@ -319,7 +319,7 @@ window.Page_admin = (() => {
|
|||
try { d = await API.get('/admin/analytics'); }
|
||||
catch (err) {
|
||||
el.innerHTML = `<div style="padding:var(--space-4);color:var(--c-danger);font-size:var(--text-sm)">
|
||||
${UI.icon('warning')} Fehler: ${_esc(err.message || String(err))}</div>`;
|
||||
${UI.icon('warning')} Fehler: ${UI.escape(err.message || String(err))}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +396,7 @@ window.Page_admin = (() => {
|
|||
const pct = ((p[valKey] ?? 0) / maxV * 100).toFixed(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:78%">${_esc(p[labelKey] || '—')}</span>
|
||||
<span style="color:var(--c-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:78%">${UI.escape(p[labelKey] || '—')}</span>
|
||||
<span style="color:var(--c-text-secondary);flex-shrink:0;margin-left:var(--space-2)">${fmt(p[valKey] ?? 0)}</span>
|
||||
</div>
|
||||
<div style="height:4px;border-radius:2px;background:var(--c-surface-2)">
|
||||
|
|
@ -549,7 +549,7 @@ window.Page_admin = (() => {
|
|||
box-shadow:0 0 4px ${dot}"></span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:600">${label}</span>
|
||||
<span class="text-xs-muted">·</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);font-family:monospace">${_esc(model)}</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);font-family:monospace">${UI.escape(model)}</span>
|
||||
<span style="margin-left:auto;font-size:10px;padding:1px 6px;border-radius:10px;
|
||||
background:var(--c-surface);color:var(--c-text-muted);border:1px solid var(--c-border)">
|
||||
Modus: ${ki.local_reachable ? 'local' : 'cloud'}
|
||||
|
|
@ -641,8 +641,8 @@ window.Page_admin = (() => {
|
|||
</tr></thead>
|
||||
<tbody>
|
||||
${(kiH.top_users).map(u => `<tr>
|
||||
<td style="font-weight:600">${_esc(u.name)}</td>
|
||||
<td class="text-muted">${_esc(u.email.length > 22 ? u.email.split('@')[1] : u.email)}</td>
|
||||
<td style="font-weight:600">${UI.escape(u.name)}</td>
|
||||
<td class="text-muted">${UI.escape(u.email.length > 22 ? u.email.split('@')[1] : u.email)}</td>
|
||||
<td style="color:var(--c-primary);font-weight:600">${u.cloud}</td>
|
||||
<td>${u.total}</td>
|
||||
<td class="text-muted">${u.last_date?.slice(5) || '—'}</td>
|
||||
|
|
@ -837,24 +837,24 @@ window.Page_admin = (() => {
|
|||
background:var(--c-surface-2);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-weight:var(--weight-bold);color:var(--c-text-secondary)">
|
||||
${_esc(u.name[0].toUpperCase())}
|
||||
${UI.escape(u.name[0].toUpperCase())}
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--c-text)">
|
||||
${_esc(u.name)}
|
||||
${UI.escape(u.name)}
|
||||
${u.is_banned ? `<span style="font-size:10px;padding:1px 5px;border-radius:3px;
|
||||
background:var(--c-danger);color:#fff;margin-left:4px">
|
||||
GESPERRT</span>` : ''}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
${_esc(u.email)} ·
|
||||
${UI.escape(u.email)} ·
|
||||
<span style="color:${u.rolle === 'admin' ? 'var(--c-danger)' : u.rolle === 'moderator' ? '#f59e0b' : 'var(--c-text-muted)'}">
|
||||
${_esc(u.rolle)}
|
||||
${UI.escape(u.rolle)}
|
||||
</span>
|
||||
· <span style="color:${u.subscription_tier && u.subscription_tier !== 'standard' ? 'var(--c-primary)' : 'var(--c-text-muted)'}">
|
||||
${_esc(u.subscription_tier || 'standard')}
|
||||
${UI.escape(u.subscription_tier || 'standard')}
|
||||
</span>
|
||||
· ${u.dog_count} Hund${u.dog_count !== 1 ? 'e' : ''}
|
||||
· ${u.thread_count} Threads
|
||||
|
|
@ -875,28 +875,28 @@ window.Page_admin = (() => {
|
|||
<!-- Aktionen -->
|
||||
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
|
||||
${u.is_banned
|
||||
? `<button class="btn btn-sm btn-ghost adm-unban" data-uid="${u.id}" data-name="${_esc(u.name)}"
|
||||
? `<button class="btn btn-sm btn-ghost adm-unban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
|
||||
title="Sperre aufheben" class="text-success">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock-open"></use></svg>
|
||||
</button>`
|
||||
: `<button class="btn btn-sm btn-ghost adm-ban" data-uid="${u.id}" data-name="${_esc(u.name)}"
|
||||
: `<button class="btn btn-sm btn-ghost adm-ban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
|
||||
title="Sperren" class="text-danger">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock"></use></svg>
|
||||
</button>`
|
||||
}
|
||||
${isAdmin ? `
|
||||
<button class="btn btn-sm btn-ghost adm-rolle" data-uid="${u.id}"
|
||||
data-name="${_esc(u.name)}" data-rolle="${_esc(u.rolle)}"
|
||||
data-name="${UI.escape(u.name)}" data-rolle="${UI.escape(u.rolle)}"
|
||||
title="Rolle ändern">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#shield"></use></svg>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost adm-tier" data-uid="${u.id}"
|
||||
data-name="${_esc(u.name)}" data-tier="${_esc(u.subscription_tier || 'standard')}"
|
||||
data-name="${UI.escape(u.name)}" data-tier="${UI.escape(u.subscription_tier || 'standard')}"
|
||||
title="Abo-Stufe ändern">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#star"></use></svg>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost adm-delete" data-uid="${u.id}"
|
||||
data-name="${_esc(u.name)}" title="Löschen"
|
||||
data-name="${UI.escape(u.name)}" title="Löschen"
|
||||
class="text-danger">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
|
||||
</button>
|
||||
|
|
@ -1087,18 +1087,18 @@ window.Page_admin = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-1)">
|
||||
${r.resolved ? '✓ Erledigt · ' : ''}
|
||||
${_esc(r.target_type)} #${r.target_id} ·
|
||||
Gemeldet von <strong>${_esc(r.melder_name)}</strong>
|
||||
${UI.escape(r.target_type)} #${r.target_id} ·
|
||||
Gemeldet von <strong>${UI.escape(r.melder_name)}</strong>
|
||||
</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin-bottom:var(--space-1)">
|
||||
Grund: ${_esc(r.grund)}
|
||||
Grund: ${UI.escape(r.grund)}
|
||||
</div>
|
||||
${r.content_preview ? `
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
padding:var(--space-2) var(--space-3);background:var(--c-surface-2);
|
||||
border-radius:var(--radius-sm)">
|
||||
${_esc(r.content_preview)}
|
||||
${UI.escape(r.content_preview)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);flex-shrink:0">
|
||||
|
|
@ -1175,10 +1175,10 @@ window.Page_admin = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
${t.is_deleted ? '<s>' : ''}${_esc(t.titel)}${t.is_deleted ? '</s>' : ''}
|
||||
${t.is_deleted ? '<s>' : ''}${UI.escape(t.titel)}${t.is_deleted ? '</s>' : ''}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
von ${_esc(t.autor_name)} ·
|
||||
von ${UI.escape(t.autor_name)} ·
|
||||
${t.antworten} Antworten ·
|
||||
${t.is_pinned ? '📌 ' : ''}${t.is_locked ? '🔒 ' : ''}${t.is_deleted ? '🗑 gelöscht' : ''}
|
||||
</div>
|
||||
|
|
@ -1313,8 +1313,8 @@ window.Page_admin = (() => {
|
|||
return `<div style="border-bottom:1px solid var(--c-border);padding:2px 0">` +
|
||||
`<span class="text-muted">${r.t}</span> ` +
|
||||
`<span style="color:${color};font-weight:600">${r.l}</span> ` +
|
||||
`<span class="text-secondary">${_esc(r.n)}</span> ` +
|
||||
`<span>${_esc(r.m)}</span></div>`;
|
||||
`<span class="text-secondary">${UI.escape(r.n)}</span> ` +
|
||||
`<span>${UI.escape(r.m)}</span></div>`;
|
||||
}).join('') || '<span class="text-muted">Keine Einträge</span>';
|
||||
};
|
||||
el.querySelector('#adm-sys-refresh').addEventListener('click', () => {
|
||||
|
|
@ -1385,13 +1385,13 @@ window.Page_admin = (() => {
|
|||
const scoreBar = v => `<span style="color:${scoreColor(v)};font-weight:600">${v.toFixed(1)}</span>`;
|
||||
const rows = d.results.filter(r => !r.error).map(r =>
|
||||
`<tr>
|
||||
<td style="padding:2px 6px">${_esc(r.name)}</td>
|
||||
<td style="padding:2px 6px">${UI.escape(r.name)}</td>
|
||||
<td style="text-align:center;padding:2px 6px">${scoreBar(r.vollstaendigkeit)}</td>
|
||||
<td style="text-align:center;padding:2px 6px">${scoreBar(r.korrektheit)}</td>
|
||||
<td style="text-align:center;padding:2px 6px">${scoreBar(r.sprachqualitaet)}</td>
|
||||
<td style="text-align:center;padding:2px 6px">${scoreBar(r.konsistenz)}</td>
|
||||
<td style="text-align:center;padding:2px 6px;font-weight:700">${scoreBar(r.gesamt)}</td>
|
||||
<td style="padding:2px 6px;color:var(--c-text-muted);font-size:0.9em">${_esc(r.hinweis || '')}</td>
|
||||
<td style="padding:2px 6px;color:var(--c-text-muted);font-size:0.9em">${UI.escape(r.hinweis || '')}</td>
|
||||
</tr>`
|
||||
).join('');
|
||||
box.style.display = 'block';
|
||||
|
|
@ -1459,9 +1459,9 @@ window.Page_admin = (() => {
|
|||
</div>
|
||||
</div>
|
||||
<div style="margin-top:var(--space-3);font-size:var(--text-xs);color:var(--c-text-muted);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:4px">
|
||||
<span>Python ${_esc(s.python_version)}</span>
|
||||
<span>Python ${UI.escape(s.python_version)}</span>
|
||||
<span style="font-family:monospace;font-size:var(--text-xs);background:var(--c-surface-2);padding:2px 8px;border-radius:4px;color:var(--c-text-secondary)">
|
||||
APP v${typeof APP_VER !== 'undefined' ? APP_VER : '—'} · ${_esc(s.sw_version || '?')}
|
||||
APP v${typeof APP_VER !== 'undefined' ? APP_VER : '—'} · ${UI.escape(s.sw_version || '?')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1504,9 +1504,9 @@ window.Page_admin = (() => {
|
|||
const userRows = topUsers.map(u => {
|
||||
const emailDisplay = (u.email || '').length > 20
|
||||
? '@' + (u.email || '').split('@')[1]
|
||||
: _esc(u.email || '');
|
||||
: UI.escape(u.email || '');
|
||||
return `<tr>
|
||||
<td style="padding:5px 8px;font-weight:500">${_esc(u.name || '–')}</td>
|
||||
<td style="padding:5px 8px;font-weight:500">${UI.escape(u.name || '–')}</td>
|
||||
<td style="padding:5px 8px;color:var(--c-text-muted);font-size:var(--text-xs)">${emailDisplay}</td>
|
||||
<td style="padding:5px 8px;text-align:right;font-weight:600">${u.total ?? 0}</td>
|
||||
<td style="padding:5px 8px;text-align:right;color:var(--c-text-muted);font-size:var(--text-xs)">${u.last_week || '–'}</td>
|
||||
|
|
@ -1549,7 +1549,7 @@ window.Page_admin = (() => {
|
|||
<div style="display:flex;justify-content:space-between;font-size:var(--text-xs);
|
||||
color:var(--c-text-muted);margin-bottom:var(--space-4)">
|
||||
<span>30 Tage</span>
|
||||
<span>${_esc(lastDate)}</span>
|
||||
<span>${UI.escape(lastDate)}</span>
|
||||
</div>
|
||||
|
||||
${topUsers.length ? `
|
||||
|
|
@ -1704,12 +1704,12 @@ window.Page_admin = (() => {
|
|||
</tr></thead><tbody>
|
||||
${zuchterPending.map((z, i) => `
|
||||
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
|
||||
<td class="adm-td" style="font-weight:var(--weight-semibold)">${_esc(z.rasse_slug)}</td>
|
||||
<td class="adm-td">${_esc(z.name)}${z.zwingername ? `<br><span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(z.zwingername)}</span>` : ''}</td>
|
||||
<td class="adm-td">${_esc([z.plz, z.ort, z.bundesland].filter(Boolean).join(' '))}</td>
|
||||
<td class="adm-td" style="font-weight:var(--weight-semibold)">${UI.escape(z.rasse_slug)}</td>
|
||||
<td class="adm-td">${UI.escape(z.name)}${z.zwingername ? `<br><span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(z.zwingername)}</span>` : ''}</td>
|
||||
<td class="adm-td">${UI.escape([z.plz, z.ort, z.bundesland].filter(Boolean).join(' '))}</td>
|
||||
<td class="adm-td">${z.vdh_mitglied ? `<span style="color:var(--c-success);display:flex;align-items:center;gap:2px"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> VDH</span>` : '—'}</td>
|
||||
<td class="adm-td">${_ageLabel(z.created_at)}</td>
|
||||
<td class="adm-td">${z.website ? `<a href="${_esc(z.website)}" target="_blank" style="color:var(--c-primary);font-size:var(--text-xs)">Link</a>` : '—'}</td>
|
||||
<td class="adm-td">${z.website ? `<a href="${UI.escape(z.website)}" target="_blank" style="color:var(--c-primary);font-size:var(--text-xs)">Link</a>` : '—'}</td>
|
||||
<td class="adm-td" style="text-align:right;white-space:nowrap">
|
||||
<button class="btn btn-sm btn-primary adm-zuchter-approve" data-id="${z.id}" style="margin-right:4px"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button>
|
||||
<button class="btn btn-sm btn-ghost adm-zuchter-delete" data-id="${z.id}" class="text-danger"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg></button>
|
||||
|
|
@ -1719,8 +1719,8 @@ window.Page_admin = (() => {
|
|||
}
|
||||
// Züchter-History
|
||||
if (zuchterDone.length) html += _historySection('Züchter-Einreichungen', zuchterDone,
|
||||
z => `<span style="font-weight:600">${_esc(z.name)}</span> · ${_esc(z.rasse_slug)} ·
|
||||
${UI.icon('check-circle')} ${_esc(z.verified_by_name||'?')} · ${(z.verified_at||'').slice(0,10)}`);
|
||||
z => `<span style="font-weight:600">${UI.escape(z.name)}</span> · ${UI.escape(z.rasse_slug)} ·
|
||||
${UI.icon('check-circle')} ${UI.escape(z.verified_by_name||'?')} · ${(z.verified_at||'').slice(0,10)}`);
|
||||
|
||||
// --- Wiki-Foto-Einreichungen ---
|
||||
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
|
|
@ -1737,12 +1737,12 @@ window.Page_admin = (() => {
|
|||
html += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--space-4);margin-bottom:var(--space-3)">
|
||||
${fotosPending.map(f => `
|
||||
<div class="card p-4">
|
||||
<img src="${_esc(f.foto_url)}" alt=""
|
||||
<img src="${UI.escape(f.foto_url)}" alt=""
|
||||
style="width:100%;height:140px;object-fit:cover;border-radius:var(--radius-md);margin-bottom:var(--space-3)">
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${_esc(f.rasse_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">von ${_esc(f.user_name)}</div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${UI.escape(f.rasse_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">von ${UI.escape(f.user_name)}</div>
|
||||
<div class="mb-3">${_ageLabel(f.created_at)}</div>
|
||||
${f.aktuell_foto ? `<img src="${_esc(f.aktuell_foto)}" alt="Aktuell"
|
||||
${f.aktuell_foto ? `<img src="${UI.escape(f.aktuell_foto)}" alt="Aktuell"
|
||||
style="width:100%;height:80px;object-fit:cover;border-radius:var(--radius-sm);
|
||||
opacity:.5;margin-bottom:var(--space-2)">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-3)">↑ aktuelles Foto</div>` : ''}
|
||||
|
|
@ -1756,10 +1756,10 @@ window.Page_admin = (() => {
|
|||
|
||||
// Fotos-History
|
||||
if (fotosDone.length) html += _historySection('Foto-Einreichungen', fotosDone,
|
||||
f => `<img src="${_esc(f.foto_url)}" style="width:32px;height:32px;object-fit:cover;border-radius:4px;vertical-align:middle;margin-right:6px">
|
||||
<span style="font-weight:600">${_esc(f.rasse_name||'?')}</span> · von ${_esc(f.user_name||'?')} ·
|
||||
f => `<img src="${UI.escape(f.foto_url)}" style="width:32px;height:32px;object-fit:cover;border-radius:4px;vertical-align:middle;margin-right:6px">
|
||||
<span style="font-weight:600">${UI.escape(f.rasse_name||'?')}</span> · von ${UI.escape(f.user_name||'?')} ·
|
||||
${f.status==='approved' ? `${UI.icon('check-circle')} genehmigt` : `${UI.icon('x-circle')} abgelehnt`}
|
||||
${f.reviewed_by_name ? ` von ${_esc(f.reviewed_by_name)}` : ''} · ${(f.reviewed_at||'').slice(0,10)}`);
|
||||
${f.reviewed_by_name ? ` von ${UI.escape(f.reviewed_by_name)}` : ''} · ${(f.reviewed_at||'').slice(0,10)}`);
|
||||
|
||||
// --- Forum-Meldungen ---
|
||||
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
|
|
@ -1780,16 +1780,16 @@ window.Page_admin = (() => {
|
|||
<div style="display:flex;align-items:flex-start;gap:var(--space-3)">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-1);display:flex;align-items:center;flex-wrap:wrap;gap:4px">
|
||||
${_esc(r.target_type)} #${r.target_id} · Gemeldet von <strong>${_esc(r.melder_name || '?')}</strong>
|
||||
${UI.escape(r.target_type)} #${r.target_id} · Gemeldet von <strong>${UI.escape(r.melder_name || '?')}</strong>
|
||||
${_ageLabel(r.created_at)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);margin-bottom:var(--space-1)">
|
||||
Grund: ${_esc(r.grund)}
|
||||
Grund: ${UI.escape(r.grund)}
|
||||
</div>
|
||||
${r.content_preview ? `
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
padding:var(--space-2) var(--space-3);background:var(--c-surface-2);
|
||||
border-radius:var(--radius-sm)">${_esc(r.content_preview)}</div>` : ''}
|
||||
border-radius:var(--radius-sm)">${UI.escape(r.content_preview)}</div>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary adm-mod-resolve" data-rid="${r.id}" title="Als erledigt markieren">
|
||||
${UI.icon('check')}
|
||||
|
|
@ -1801,8 +1801,8 @@ window.Page_admin = (() => {
|
|||
|
||||
// Meldungen-History
|
||||
if (reportsDone.length) html += _historySection('Forum-Meldungen', reportsDone,
|
||||
r => `${_esc(r.target_type)} #${r.target_id} · ${_esc(r.grund)} · Gemeldet von ${_esc(r.melder_name||'?')} ·
|
||||
${UI.icon('check-circle')} ${_esc(r.resolved_by_name||'?')} · ${(r.resolved_at||'').slice(0,10)}`);
|
||||
r => `${UI.escape(r.target_type)} #${r.target_id} · ${UI.escape(r.grund)} · Gemeldet von ${UI.escape(r.melder_name||'?')} ·
|
||||
${UI.icon('check-circle')} ${UI.escape(r.resolved_by_name||'?')} · ${(r.resolved_at||'').slice(0,10)}`);
|
||||
|
||||
// --- POI-Korrekturen ---
|
||||
html += `<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
|
|
@ -1831,11 +1831,11 @@ window.Page_admin = (() => {
|
|||
<tbody>
|
||||
${poiPending.map((e, i) => `
|
||||
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
|
||||
<td class="adm-td" style="font-weight:var(--weight-semibold)">${_esc(e.poi_name || `OSM #${e.osm_id}`)}</td>
|
||||
<td class="adm-td"><code class="text-xs">${_esc(e.field)}</code></td>
|
||||
<td class="adm-td" style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(e.old_value || '—')}</td>
|
||||
<td class="adm-td text-xs">${_esc(e.new_value || '—')}</td>
|
||||
<td class="adm-td text-muted">${_esc(e.einreicher_name || '?')}</td>
|
||||
<td class="adm-td" style="font-weight:var(--weight-semibold)">${UI.escape(e.poi_name || `OSM #${e.osm_id}`)}</td>
|
||||
<td class="adm-td"><code class="text-xs">${UI.escape(e.field)}</code></td>
|
||||
<td class="adm-td" style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(e.old_value || '—')}</td>
|
||||
<td class="adm-td text-xs">${UI.escape(e.new_value || '—')}</td>
|
||||
<td class="adm-td text-muted">${UI.escape(e.einreicher_name || '?')}</td>
|
||||
<td class="adm-td">${_ageLabel(e.created_at)}</td>
|
||||
<td class="adm-td" style="text-align:right;white-space:nowrap">
|
||||
<button class="btn btn-sm btn-primary adm-poi-approve" data-id="${e.id}" style="margin-right:4px">
|
||||
|
|
@ -1852,12 +1852,12 @@ window.Page_admin = (() => {
|
|||
}
|
||||
// POI-History
|
||||
if (poiDone.length) html += _historySection('POI-Korrekturen', poiDone,
|
||||
e => `<span style="font-weight:600">${_esc(e.poi_name||`OSM #${e.osm_id}`)}</span> ·
|
||||
<code class="text-xs">${_esc(e.field)}</code>:
|
||||
<span style="text-decoration:line-through;color:var(--c-text-muted)">${_esc(e.old_value||'—')}</span> →
|
||||
${_esc(e.new_value||'—')} ·
|
||||
e => `<span style="font-weight:600">${UI.escape(e.poi_name||`OSM #${e.osm_id}`)}</span> ·
|
||||
<code class="text-xs">${UI.escape(e.field)}</code>:
|
||||
<span style="text-decoration:line-through;color:var(--c-text-muted)">${UI.escape(e.old_value||'—')}</span> →
|
||||
${UI.escape(e.new_value||'—')} ·
|
||||
${e.status==='approved' ? `${UI.icon('check-circle')} freigegeben` : `${UI.icon('x-circle')} abgelehnt`}
|
||||
${e.mod_name ? ` von ${_esc(e.mod_name)}` : ''} · ${(e.resolved_at||'').slice(0,10)}`);
|
||||
${e.mod_name ? ` von ${UI.escape(e.mod_name)}` : ''} · ${(e.resolved_at||'').slice(0,10)}`);
|
||||
|
||||
el.innerHTML = html;
|
||||
|
||||
|
|
@ -1990,17 +1990,17 @@ window.Page_admin = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm);
|
||||
color:var(--c-text);margin-bottom:var(--space-1)">
|
||||
${_esc(a.name)}
|
||||
${UI.escape(a.name)}
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);font-weight:400;margin-left:6px">
|
||||
${_esc(a.email)}
|
||||
${UI.escape(a.email)}
|
||||
</span>
|
||||
</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-3);font-size:var(--text-xs);
|
||||
color:var(--c-text-secondary);margin-bottom:var(--space-2)">
|
||||
<span>${UI.icon('paw-print')} ${_esc(a.rasse_text || '–')}</span>
|
||||
<span>${UI.icon('house-line')} ${_esc(a.zwingername || '–')}</span>
|
||||
<span>${UI.icon('users')} ${_esc(a.verein || '–')}</span>
|
||||
<span>${UI.icon('map-pin')} ${_esc(a.stadt || '–')}</span>
|
||||
<span>${UI.icon('paw-print')} ${UI.escape(a.rasse_text || '–')}</span>
|
||||
<span>${UI.icon('house-line')} ${UI.escape(a.zwingername || '–')}</span>
|
||||
<span>${UI.icon('users')} ${UI.escape(a.verein || '–')}</span>
|
||||
<span>${UI.icon('map-pin')} ${UI.escape(a.stadt || '–')}</span>
|
||||
<span style="color:${a.vdh_mitglied ? 'var(--c-success)' : 'var(--c-text-muted)'}">
|
||||
${UI.icon('certificate')} VDH: ${a.vdh_mitglied ? 'ja' : 'nein'}
|
||||
</span>
|
||||
|
|
@ -2010,7 +2010,7 @@ window.Page_admin = (() => {
|
|||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
padding:var(--space-2) var(--space-3);background:var(--c-surface-2);
|
||||
border-radius:var(--radius-sm);margin-top:var(--space-1)">
|
||||
${_esc(a.beschreibung)}
|
||||
${UI.escape(a.beschreibung)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
||||
|
|
@ -2021,11 +2021,11 @@ window.Page_admin = (() => {
|
|||
${UI.icon('file-text')} Dokumente
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary adm-breeder-approve"
|
||||
data-uid="${a.user_id || a.id}" data-name="${_esc(a.name)}">
|
||||
data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}">
|
||||
${UI.icon('check')} Freischalten
|
||||
</button>
|
||||
<button class="btn btn-sm btn-ghost adm-breeder-reject"
|
||||
data-uid="${a.user_id || a.id}" data-name="${_esc(a.name)}"
|
||||
data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}"
|
||||
class="text-danger">
|
||||
${UI.icon('x')} Ablehnen
|
||||
</button>
|
||||
|
|
@ -2053,11 +2053,11 @@ window.Page_admin = (() => {
|
|||
body: docs.length
|
||||
? `<div class="flex-col-gap-3">
|
||||
${docs.map(d => `
|
||||
<a href="${_esc(API.breeder.documentUrl(uid, d.id))}"
|
||||
<a href="${UI.escape(API.breeder.documentUrl(uid, d.id))}"
|
||||
target="_blank" rel="noopener"
|
||||
class="btn btn-secondary"
|
||||
style="text-align:left;word-break:break-all">
|
||||
${UI.icon('file')} ${_esc(d.filename || d.name || 'Dokument ' + d.id)}
|
||||
${UI.icon('file')} ${UI.escape(d.filename || d.name || 'Dokument ' + d.id)}
|
||||
</a>`).join('')}
|
||||
</div>`
|
||||
: `<p class="text-sm-muted">Keine Dokumente hochgeladen.</p>`,
|
||||
|
|
@ -2155,12 +2155,12 @@ window.Page_admin = (() => {
|
|||
const rows = breeders.map(b => `
|
||||
<tr>
|
||||
<td style="padding:var(--space-2) var(--space-3)">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(b.name)}</div>
|
||||
<div class="text-xs-muted">${_esc(b.email)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(b.name)}</div>
|
||||
<div class="text-xs-muted">${UI.escape(b.email)}</div>
|
||||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm)">${_esc(b.zwingername || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(b.rasse_text || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(b.stadt || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm)">${UI.escape(b.zwingername || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(b.rasse_text || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(b.stadt || '—')}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);text-align:center;font-size:var(--text-xs)">
|
||||
${b.wuerfe_count || 0} Würfe<br>
|
||||
<span class="text-muted">${b.hunde_count || 0} Hunde</span>
|
||||
|
|
@ -2171,7 +2171,7 @@ window.Page_admin = (() => {
|
|||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3)">
|
||||
<button class="btn btn-sm btn-ghost adm-breeder-tier-btn"
|
||||
data-uid="${b.id}" data-name="${_esc(b.name)}" data-tier="${_esc(b.subscription_tier || 'standard')}"
|
||||
data-uid="${b.id}" data-name="${UI.escape(b.name)}" data-tier="${UI.escape(b.subscription_tier || 'standard')}"
|
||||
class="text-xs">
|
||||
Abo
|
||||
</button>
|
||||
|
|
@ -2241,17 +2241,17 @@ window.Page_admin = (() => {
|
|||
${jobs.map((j, i) => `
|
||||
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
|
||||
<td class="adm-td" style="font-weight:var(--weight-semibold);color:var(--c-text)">
|
||||
${_esc(j.name)}
|
||||
<div class="adm-job-id">${_esc(j.id)}</div>
|
||||
${UI.escape(j.name)}
|
||||
<div class="adm-job-id">${UI.escape(j.id)}</div>
|
||||
</td>
|
||||
<td class="adm-td" style="color:var(--c-text-secondary);white-space:nowrap">
|
||||
${j.next_run_time ? _formatDateTime(j.next_run_time) : '<span class="text-muted">—</span>'}
|
||||
</td>
|
||||
<td class="adm-td adm-td-trigger">
|
||||
${_esc(j.trigger)}
|
||||
${UI.escape(j.trigger)}
|
||||
</td>
|
||||
<td class="adm-td text-right">
|
||||
<button class="btn btn-sm btn-ghost adm-job-trigger adm-icon-btn" data-id="${_esc(j.id)}" data-name="${_esc(j.name)}"
|
||||
<button class="btn btn-sm btn-ghost adm-job-trigger adm-icon-btn" data-id="${UI.escape(j.id)}" data-name="${UI.escape(j.name)}"
|
||||
title="Jetzt ausführen" class="text-primary">
|
||||
${UI.icon('play')}
|
||||
</button>
|
||||
|
|
@ -2542,11 +2542,11 @@ window.Page_admin = (() => {
|
|||
background:var(--c-bg-elevated);border-radius:var(--radius-md);border:1px solid var(--c-border)">
|
||||
<div class="flex-1-min">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<span style="font-size:var(--text-sm);font-weight:600">${_esc(t.label)}</span>
|
||||
<span style="font-size:var(--text-sm);font-weight:600">${UI.escape(t.label)}</span>
|
||||
${accountBadge(t.from_account)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(t.subject)}
|
||||
${UI.escape(t.subject)}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2);flex-shrink:0">
|
||||
|
|
@ -2632,9 +2632,9 @@ window.Page_admin = (() => {
|
|||
onmouseover="this.style.background='var(--c-surface-2)'"
|
||||
onmouseout="this.style.background=''">
|
||||
<td class="p-2">${accountBadge(l.from_account)}</td>
|
||||
<td class="p-2">${_esc(l.recipient)}</td>
|
||||
<td style="padding:var(--space-2);color:var(--c-text-secondary)">${_esc(l.subject)}</td>
|
||||
<td style="padding:var(--space-2);color:var(--c-text-muted)">${_esc(l.sent_by_name || '')}</td>
|
||||
<td class="p-2">${UI.escape(l.recipient)}</td>
|
||||
<td style="padding:var(--space-2);color:var(--c-text-secondary)">${UI.escape(l.subject)}</td>
|
||||
<td style="padding:var(--space-2);color:var(--c-text-muted)">${UI.escape(l.sent_by_name || '')}</td>
|
||||
<td style="padding:var(--space-2);color:var(--c-text-muted)">${(l.sent_at||'').slice(0,16).replace('T',' ')}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
|
|
@ -2650,17 +2650,17 @@ window.Page_admin = (() => {
|
|||
const l = log[Number(row.dataset.logIdx)];
|
||||
if (!l) return;
|
||||
UI.modal.open({
|
||||
title: _esc(l.subject),
|
||||
title: UI.escape(l.subject),
|
||||
body: `
|
||||
<div style="margin-bottom:var(--space-3);font-size:var(--text-sm);color:var(--c-text-muted)">
|
||||
<strong>An:</strong> ${_esc(l.recipient)} ·
|
||||
<strong>Von:</strong> ${_esc(l.from_account)}@banyaro.app ·
|
||||
<strong>An:</strong> ${UI.escape(l.recipient)} ·
|
||||
<strong>Von:</strong> ${UI.escape(l.from_account)}@banyaro.app ·
|
||||
${(l.sent_at||'').slice(0,16).replace('T',' ')}
|
||||
</div>
|
||||
<pre style="white-space:pre-wrap;font-family:inherit;font-size:var(--text-sm);
|
||||
background:var(--c-surface-2);border-radius:var(--radius-md);
|
||||
padding:var(--space-3);max-height:60vh;overflow-y:auto;
|
||||
color:var(--c-text)">${_esc(l.body || '(kein Text gespeichert)')}</pre>`,
|
||||
color:var(--c-text)">${UI.escape(l.body || '(kein Text gespeichert)')}</pre>`,
|
||||
footer: `<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>`,
|
||||
});
|
||||
});
|
||||
|
|
@ -2733,7 +2733,7 @@ window.Page_admin = (() => {
|
|||
<div>
|
||||
<label class="form-label text-xs">Name (intern)</label>
|
||||
<input class="form-control" id="${id}-key" type="text" placeholder="z.B. willkommen_neu"
|
||||
value="${_esc(tpl?.key || '')}" ${isNew ? '' : 'readonly'}>
|
||||
value="${UI.escape(tpl?.key || '')}" ${isNew ? '' : 'readonly'}>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label text-xs">Absender</label>
|
||||
|
|
@ -2746,17 +2746,17 @@ window.Page_admin = (() => {
|
|||
<div>
|
||||
<label class="form-label text-xs">Bezeichnung (sichtbar)</label>
|
||||
<input class="form-control" id="${id}-label" type="text" placeholder="z.B. Willkommensnachricht"
|
||||
value="${_esc(tpl?.label || '')}">
|
||||
value="${UI.escape(tpl?.label || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label text-xs">Betreff</label>
|
||||
<input class="form-control" id="${id}-subject" type="text"
|
||||
value="${_esc(tpl?.subject || '')}">
|
||||
value="${UI.escape(tpl?.subject || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label text-xs">Text</label>
|
||||
<textarea id="${id}-body" class="form-control" rows="12"
|
||||
style="font-family:monospace;font-size:var(--text-sm);resize:vertical">${_esc(tpl?.body || '')}</textarea>
|
||||
style="font-family:monospace;font-size:var(--text-sm);resize:vertical">${UI.escape(tpl?.body || '')}</textarea>
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
|
|
@ -2826,14 +2826,14 @@ window.Page_admin = (() => {
|
|||
${_formatDateTime(r.created_at)}
|
||||
</td>
|
||||
<td class="adm-td" style="color:var(--c-text);white-space:nowrap">
|
||||
${_esc(r.admin_name || '—')}
|
||||
${UI.escape(r.admin_name || '—')}
|
||||
</td>
|
||||
<td class="adm-td">
|
||||
<span class="adm-badge-mono">${_esc(r.action)}</span>
|
||||
${r.detail ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${_esc(r.detail)}</div>` : ''}
|
||||
<span class="adm-badge-mono">${UI.escape(r.action)}</span>
|
||||
${r.detail ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${UI.escape(r.detail)}</div>` : ''}
|
||||
</td>
|
||||
<td class="adm-td" style="color:var(--c-text-secondary);font-size:var(--text-xs);white-space:nowrap">
|
||||
${_esc(r.target || '—')}
|
||||
${UI.escape(r.target || '—')}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
|
|
@ -2889,11 +2889,6 @@ window.Page_admin = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// BEWERBUNGEN — Social-Media-Job
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -2932,19 +2927,19 @@ window.Page_admin = (() => {
|
|||
<div class="card" style="margin-bottom:var(--space-3);padding:var(--space-4)" data-id="${r.id}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-3)">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${_esc(r.name)}
|
||||
${r.username ? `<span style="color:var(--c-text-muted);font-weight:400;font-size:var(--text-sm)">(@${_esc(r.username)})</span>` : ''}
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${UI.escape(r.name)}
|
||||
${r.username ? `<span style="color:var(--c-text-muted);font-weight:400;font-size:var(--text-sm)">(@${UI.escape(r.username)})</span>` : ''}
|
||||
</div>
|
||||
<div style="color:var(--c-text-secondary);font-size:var(--text-sm);margin-top:2px">
|
||||
${_esc(r.email)} · @${_esc(r.social_handle||'—')}
|
||||
${r.dog_name ? ` · 🐕 ${_esc(r.dog_name)} (${_esc(r.dog_rasse||'')})` : ''}
|
||||
${UI.escape(r.email)} · @${UI.escape(r.social_handle||'—')}
|
||||
${r.dog_name ? ` · 🐕 ${UI.escape(r.dog_name)} (${UI.escape(r.dog_rasse||'')})` : ''}
|
||||
</div>
|
||||
<div style="color:var(--c-text-muted);font-size:var(--text-xs);margin-top:2px">
|
||||
${r.created_at?.slice(0,16).replace('T',' ')} · ${r.doc_count} Anhang/Anhänge
|
||||
</div>
|
||||
<div style="margin-top:var(--space-2);font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-2) var(--space-3)">
|
||||
${_esc((r.motivation||'').slice(0,200))}${(r.motivation||'').length>200?'…':''}
|
||||
${UI.escape((r.motivation||'').slice(0,200))}${(r.motivation||'').length>200?'…':''}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);min-width:120px">
|
||||
|
|
@ -2976,25 +2971,25 @@ window.Page_admin = (() => {
|
|||
const docsHtml = app.docs?.length
|
||||
? app.docs.map(d => `<a href="/api/jobs/admin/applications/${id}/docs/${d.id}"
|
||||
target="_blank" style="display:block;color:var(--c-primary);font-size:var(--text-sm);margin:4px 0">
|
||||
📎 ${_esc(d.filename)}</a>`).join('')
|
||||
📎 ${UI.escape(d.filename)}</a>`).join('')
|
||||
: '<span class="text-sm-muted">Keine Anhänge</span>';
|
||||
|
||||
UI.modal.open({
|
||||
title: `Bewerbung — ${_esc(app.name)}`,
|
||||
title: `Bewerbung — ${UI.escape(app.name)}`,
|
||||
body: `
|
||||
<div style="display:grid;gap:var(--space-3)">
|
||||
<div><b>E-Mail:</b> ${_esc(app.email)}</div>
|
||||
<div><b>Social:</b> @${_esc(app.social_handle||'—')}</div>
|
||||
${app.dog_name ? `<div><b>Hund:</b> ${_esc(app.dog_name)} (${_esc(app.dog_rasse||'')})</div>` : ''}
|
||||
<div><b>E-Mail:</b> ${UI.escape(app.email)}</div>
|
||||
<div><b>Social:</b> @${UI.escape(app.social_handle||'—')}</div>
|
||||
${app.dog_name ? `<div><b>Hund:</b> ${UI.escape(app.dog_name)} (${UI.escape(app.dog_rasse||'')})</div>` : ''}
|
||||
<div><b>Motivation:</b><br>
|
||||
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-3);
|
||||
margin-top:var(--space-1);font-size:var(--text-sm);white-space:pre-wrap">${_esc(app.motivation)}</div>
|
||||
margin-top:var(--space-1);font-size:var(--text-sm);white-space:pre-wrap">${UI.escape(app.motivation)}</div>
|
||||
</div>
|
||||
<div><b>Anhänge:</b><br>${docsHtml}</div>
|
||||
<div>
|
||||
<b>Admin-Notiz:</b>
|
||||
<textarea id="adm-bew-note" class="form-control" rows="2" style="margin-top:var(--space-1)"
|
||||
placeholder="Interne Notiz / Nachricht an Bewerber">${_esc(app.admin_note||'')}</textarea>
|
||||
placeholder="Interne Notiz / Nachricht an Bewerber">${UI.escape(app.admin_note||'')}</textarea>
|
||||
</div>
|
||||
</div>`,
|
||||
footer: `
|
||||
|
|
@ -3052,7 +3047,7 @@ window.Page_admin = (() => {
|
|||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
background:var(--c-surface);color:var(--c-text);font-size:var(--text-sm)">
|
||||
${Object.entries(KAT_LABEL).map(([k,v]) =>
|
||||
`<option value="${k}">${_esc(v)}</option>`
|
||||
`<option value="${k}">${UI.escape(v)}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -3125,7 +3120,7 @@ window.Page_admin = (() => {
|
|||
text-transform:uppercase;letter-spacing:0.05em;
|
||||
padding:var(--space-2) 0;margin-bottom:var(--space-2);
|
||||
border-bottom:1px solid var(--c-border)">
|
||||
${_esc(label)}
|
||||
${UI.escape(label)}
|
||||
</div>
|
||||
`;
|
||||
for (const a of items) {
|
||||
|
|
@ -3138,7 +3133,7 @@ window.Page_admin = (() => {
|
|||
padding:var(--space-3) var(--space-4)">
|
||||
<span style="flex:1;font-size:var(--text-sm);font-weight:500;
|
||||
${a.aktiv ? '' : 'opacity:0.45;text-decoration:line-through'}">
|
||||
${_esc(a.frage)}
|
||||
${UI.escape(a.frage)}
|
||||
</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);
|
||||
white-space:nowrap">
|
||||
|
|
@ -3159,7 +3154,7 @@ window.Page_admin = (() => {
|
|||
<button class="btn btn-sm adm-hilfe-del-btn"
|
||||
style="padding:2px 8px;font-size:var(--text-xs);
|
||||
background:var(--c-danger-bg,#fee2e2);color:var(--c-danger,#991b1b)"
|
||||
data-id="${a.id}" data-frage="${_esc(a.frage)}">
|
||||
data-id="${a.id}" data-frage="${UI.escape(a.frage)}">
|
||||
${UI.icon('trash')}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -3177,7 +3172,7 @@ window.Page_admin = (() => {
|
|||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
background:var(--c-surface);color:var(--c-text);font-size:var(--text-sm)">
|
||||
${Object.entries(KAT_LABEL).map(([k,v]) =>
|
||||
`<option value="${k}" ${k === a.kategorie ? 'selected' : ''}>${_esc(v)}</option>`
|
||||
`<option value="${k}" ${k === a.kategorie ? 'selected' : ''}>${UI.escape(v)}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -3185,7 +3180,7 @@ window.Page_admin = (() => {
|
|||
<label style="font-size:var(--text-xs);font-weight:600;display:block;
|
||||
margin-bottom:4px;color:var(--c-text-secondary)">Frage</label>
|
||||
<input type="text" class="adm-hilfe-edit-frage"
|
||||
value="${_esc(a.frage)}"
|
||||
value="${UI.escape(a.frage)}"
|
||||
style="width:100%;padding:var(--space-2) var(--space-3);
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
background:var(--c-surface);color:var(--c-text);
|
||||
|
|
@ -3199,7 +3194,7 @@ window.Page_admin = (() => {
|
|||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
background:var(--c-surface);color:var(--c-text);
|
||||
font-size:var(--text-sm);box-sizing:border-box;
|
||||
resize:vertical;font-family:inherit">${_esc(a.antwort)}</textarea>
|
||||
resize:vertical;font-family:inherit">${UI.escape(a.antwort)}</textarea>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
<label style="font-size:var(--text-xs);font-weight:600;
|
||||
|
|
@ -3494,8 +3489,8 @@ window.Page_admin = (() => {
|
|||
const topRows = d.top_referrers.map((r, i) => `
|
||||
<tr>
|
||||
<td style="padding:8px 10px;color:var(--c-text-muted);font-weight:600">${i + 1}</td>
|
||||
<td style="padding:8px 10px;font-weight:600">${_esc(r.name)}</td>
|
||||
<td style="padding:8px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(r.email)}</td>
|
||||
<td style="padding:8px 10px;font-weight:600">${UI.escape(r.name)}</td>
|
||||
<td style="padding:8px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(r.email)}</td>
|
||||
<td style="padding:8px 10px;text-align:right">
|
||||
<span style="font-size:var(--text-lg);font-weight:800;color:var(--c-primary)">${r.invited_count}</span>
|
||||
</td>
|
||||
|
|
@ -3503,8 +3498,8 @@ window.Page_admin = (() => {
|
|||
|
||||
const recentRows = d.recent_invites.slice(0, 50).map(r => `
|
||||
<tr>
|
||||
<td style="padding:6px 10px;font-weight:500">${_esc(r.name)}</td>
|
||||
<td style="padding:6px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(r.referrer_name)}</td>
|
||||
<td style="padding:6px 10px;font-weight:500">${UI.escape(r.name)}</td>
|
||||
<td style="padding:6px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(r.referrer_name)}</td>
|
||||
<td style="padding:6px 10px;color:var(--c-text-muted);font-size:var(--text-xs)">${(r.created_at || '').slice(0, 10)}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
|
|
@ -3575,8 +3570,8 @@ window.Page_admin = (() => {
|
|||
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(r.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">${_esc(r.email)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(r.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">${UI.escape(r.email)}</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
|
||||
${tierBadge(r.tier)}
|
||||
${r.discount_pct > 0 ? `<span style="display:inline-block;padding:1px 8px;border-radius:999px;
|
||||
|
|
@ -3590,7 +3585,7 @@ window.Page_admin = (() => {
|
|||
${r.message ? `<div style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
|
||||
background:var(--c-surface-raised,rgba(0,0,0,.04))">
|
||||
${_esc(r.message)}
|
||||
${UI.escape(r.message)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3598,15 +3593,15 @@ window.Page_admin = (() => {
|
|||
${r.existing_invoice_id ? `
|
||||
<button class="btn adm-invoice-edit-btn"
|
||||
data-invoice-id="${r.existing_invoice_id}"
|
||||
title="Rechnung ${_esc(r.existing_invoice_number)} (${_esc(r.existing_invoice_status)}) bearbeiten"
|
||||
title="Rechnung ${UI.escape(r.existing_invoice_number)} (${UI.escape(r.existing_invoice_status)}) bearbeiten"
|
||||
style="background:#eab308;color:#1a1a1a;border:none;
|
||||
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
|
||||
cursor:pointer;font-size:var(--text-sm);font-weight:600">
|
||||
${UI.icon('receipt')} Rechnung bearbeiten
|
||||
</button>` : `
|
||||
<button class="btn adm-invoice-btn"
|
||||
data-name="${_esc(r.name)}" data-email="${_esc(r.email)}"
|
||||
data-tier="${r.tier}" data-address="${_esc(r.billing_address || '')}"
|
||||
data-name="${UI.escape(r.name)}" data-email="${UI.escape(r.email)}"
|
||||
data-tier="${r.tier}" data-address="${UI.escape(r.billing_address || '')}"
|
||||
data-discount="${r.discount_pct || 0}"
|
||||
data-discount-reason="${r.discount_reason || ''}"
|
||||
data-referral-count="${r.referral_count || 0}"
|
||||
|
|
@ -3615,7 +3610,7 @@ window.Page_admin = (() => {
|
|||
cursor:pointer;font-size:var(--text-sm);font-weight:600">
|
||||
${UI.icon('receipt')} Rechnung erstellen
|
||||
</button>`}
|
||||
<button class="btn adm-fulfill-btn" data-id="${r.id}" data-name="${_esc(r.name)}" data-tier="${r.tier}"
|
||||
<button class="btn adm-fulfill-btn" data-id="${r.id}" data-name="${UI.escape(r.name)}" data-tier="${r.tier}"
|
||||
style="background:#16a34a;color:#fff;border:none;
|
||||
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
|
||||
cursor:pointer;font-size:var(--text-sm);font-weight:600">
|
||||
|
|
@ -3627,8 +3622,8 @@ window.Page_admin = (() => {
|
|||
// Erledigte als kompakte Tabellenzeilen
|
||||
const _doneRow = r => `
|
||||
<tr>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm)">${_esc(r.name)}<br>
|
||||
<span class="text-xs-muted">${_esc(r.email)}</span></td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm)">${UI.escape(r.name)}<br>
|
||||
<span class="text-xs-muted">${UI.escape(r.email)}</span></td>
|
||||
<td style="padding:var(--space-2) var(--space-3)">${tierBadge(r.tier)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-xs);color:var(--c-success)">
|
||||
✓ ${r.fulfilled_at?.slice(0,10) || ''}</td>
|
||||
|
|
@ -3841,12 +3836,12 @@ window.Page_admin = (() => {
|
|||
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-edit" data-id="${inv.id}" title="Bearbeiten">
|
||||
${UI.icon('pencil')} Bearbeiten
|
||||
</button>`);
|
||||
actions.push(`<button class="btn btn-sm btn-primary adm-inv-send" data-id="${inv.id}" data-num="${_esc(inv.invoice_number)}" title="Senden">
|
||||
actions.push(`<button class="btn btn-sm btn-primary adm-inv-send" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" title="Senden">
|
||||
${UI.icon('paper-plane-tilt')} Senden
|
||||
</button>`);
|
||||
}
|
||||
if (inv.status === 'sent') {
|
||||
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-send" data-id="${inv.id}" data-num="${_esc(inv.invoice_number)}" title="Erneut senden"
|
||||
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-send" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" title="Erneut senden"
|
||||
class="text-muted">
|
||||
${UI.icon('paper-plane-tilt')} Erneut senden
|
||||
</button>`);
|
||||
|
|
@ -3855,7 +3850,7 @@ window.Page_admin = (() => {
|
|||
actions.push(`<button class="btn btn-sm btn-secondary adm-inv-pay" data-id="${inv.id}" data-amount="${inv.amount_gross}" title="Als bezahlt markieren">
|
||||
${UI.icon('check-circle')} Bezahlt
|
||||
</button>`);
|
||||
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel" data-id="${inv.id}" data-num="${_esc(inv.invoice_number)}"
|
||||
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}"
|
||||
class="text-danger" title="Stornieren">
|
||||
${UI.icon('x-circle')} Storno
|
||||
</button>`);
|
||||
|
|
@ -3869,11 +3864,11 @@ window.Page_admin = (() => {
|
|||
return `
|
||||
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
|
||||
<td class="adm-td" style="font-weight:600;font-family:monospace;font-size:var(--text-xs)">
|
||||
${_esc(inv.invoice_number)}
|
||||
${UI.escape(inv.invoice_number)}
|
||||
</td>
|
||||
<td class="adm-td">
|
||||
<div style="font-weight:500">${_esc(inv.recipient_name)}</div>
|
||||
<div class="text-xs-muted">${_esc(inv.recipient_email || '')}</div>
|
||||
<div style="font-weight:500">${UI.escape(inv.recipient_name)}</div>
|
||||
<div class="text-xs-muted">${UI.escape(inv.recipient_email || '')}</div>
|
||||
</td>
|
||||
<td class="adm-td" style="text-align:right;font-weight:700;white-space:nowrap">
|
||||
${_fmtEur(inv.amount_gross)}
|
||||
|
|
@ -4007,12 +4002,12 @@ window.Page_admin = (() => {
|
|||
<div>
|
||||
<label class="form-label text-xs">Empfänger Name *</label>
|
||||
<input class="form-control" name="recipient_name" type="text" required
|
||||
placeholder="Max Muster" value="${_esc(p.recipient_name || '')}">
|
||||
placeholder="Max Muster" value="${UI.escape(p.recipient_name || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label text-xs">E-Mail</label>
|
||||
<input class="form-control" name="recipient_email" type="email"
|
||||
placeholder="max@example.com" value="${_esc(p.recipient_email || '')}">
|
||||
placeholder="max@example.com" value="${UI.escape(p.recipient_email || '')}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -4024,14 +4019,14 @@ window.Page_admin = (() => {
|
|||
</label>
|
||||
<textarea class="form-control" name="recipient_address" rows="2"
|
||||
placeholder="Musterstr. 1 12345 Berlin"
|
||||
style="resize:vertical;font-family:inherit">${_esc(p.recipient_address || '')}</textarea>
|
||||
style="resize:vertical;font-family:inherit">${UI.escape(p.recipient_address || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label text-xs">Leistungszeitraum <span class="text-muted">(optional)</span></label>
|
||||
<input class="form-control" name="service_period" type="text"
|
||||
placeholder="z.B. 15.05.2026 oder einmalige Leistung"
|
||||
value="${_esc(p.service_period || '')}">
|
||||
value="${UI.escape(p.service_period || '')}">
|
||||
</div>
|
||||
|
||||
<!-- Positionen -->
|
||||
|
|
@ -4066,7 +4061,7 @@ window.Page_admin = (() => {
|
|||
<label class="form-label text-xs">Notizen <span class="text-muted">(optional)</span></label>
|
||||
<textarea class="form-control" name="notes" rows="2"
|
||||
style="resize:vertical;font-family:inherit"
|
||||
placeholder="Interne Notiz / Zahlungshinweis">${_esc(p.notes || (!isEdit && !p.recipient_name ? 'Zahlbar innerhalb von 14 Tagen ab Rechnungsdatum.' : ''))}</textarea>
|
||||
placeholder="Interne Notiz / Zahlungshinweis">${UI.escape(p.notes || (!isEdit && !p.recipient_name ? 'Zahlbar innerhalb von 14 Tagen ab Rechnungsdatum.' : ''))}</textarea>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -4120,7 +4115,7 @@ window.Page_admin = (() => {
|
|||
itemEl.style.cssText = 'display:grid;grid-template-columns:1fr 60px 100px auto;gap:var(--space-2);align-items:center';
|
||||
itemEl.innerHTML = `
|
||||
<input class="form-control inv-item-desc" type="text" placeholder="Beschreibung *"
|
||||
value="${_esc(desc)}" class="text-sm">
|
||||
value="${UI.escape(desc)}" class="text-sm">
|
||||
<input class="form-control inv-item-qty" type="number" min="1" value="${qty}"
|
||||
style="font-size:var(--text-sm);text-align:right" title="Menge">
|
||||
<input class="form-control inv-item-price" type="number" min="0" step="0.01" value="${price.toFixed(2)}"
|
||||
|
|
@ -4303,7 +4298,7 @@ window.Page_admin = (() => {
|
|||
body: `
|
||||
<form id="${id}" class="flex-col-gap-3">
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0">
|
||||
Rechnung <strong>${_esc(invoiceNum)}</strong> stornieren.
|
||||
Rechnung <strong>${UI.escape(invoiceNum)}</strong> stornieren.
|
||||
</p>
|
||||
<div>
|
||||
<label class="form-label text-xs">Stornierungsgrund *</label>
|
||||
|
|
@ -4360,7 +4355,7 @@ window.Page_admin = (() => {
|
|||
|
||||
const itemsHtml = (inv.items || []).map(item => `
|
||||
<tr>
|
||||
<td style="padding:6px 8px">${_esc(item.description)}</td>
|
||||
<td style="padding:6px 8px">${UI.escape(item.description)}</td>
|
||||
<td style="padding:6px 8px;text-align:right">${item.quantity}</td>
|
||||
<td style="padding:6px 8px;text-align:right">${_fmtEur(item.unit_price)}</td>
|
||||
<td style="padding:6px 8px;text-align:right;font-weight:600">${_fmtEur(item.total)}</td>
|
||||
|
|
@ -4368,15 +4363,15 @@ window.Page_admin = (() => {
|
|||
`).join('');
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('receipt')} ${_esc(inv.invoice_number)}`,
|
||||
title: `${UI.icon('receipt')} ${UI.escape(inv.invoice_number)}`,
|
||||
body: `
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-3);font-size:var(--text-sm)">
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Empfänger</div>
|
||||
<div style="font-weight:600">${_esc(inv.recipient_name)}</div>
|
||||
${inv.recipient_email ? `<div style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(inv.recipient_email)}</div>` : ''}
|
||||
${inv.recipient_address ? `<div style="color:var(--c-text-secondary);font-size:var(--text-xs);white-space:pre-line;margin-top:2px">${_esc(inv.recipient_address)}</div>` : ''}
|
||||
<div style="font-weight:600">${UI.escape(inv.recipient_name)}</div>
|
||||
${inv.recipient_email ? `<div style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(inv.recipient_email)}</div>` : ''}
|
||||
${inv.recipient_address ? `<div style="color:var(--c-text-secondary);font-size:var(--text-xs);white-space:pre-line;margin-top:2px">${UI.escape(inv.recipient_address)}</div>` : ''}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Status</div>
|
||||
|
|
@ -4392,7 +4387,7 @@ window.Page_admin = (() => {
|
|||
${inv.service_period ? `
|
||||
<div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Leistungszeitraum</div>
|
||||
<div>${_esc(inv.service_period)}</div>
|
||||
<div>${UI.escape(inv.service_period)}</div>
|
||||
</div>` : ''}
|
||||
|
||||
<!-- Positionen -->
|
||||
|
|
@ -4421,7 +4416,7 @@ window.Page_admin = (() => {
|
|||
<div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Notizen</div>
|
||||
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-2) var(--space-3);
|
||||
font-size:var(--text-xs);white-space:pre-wrap">${_esc(inv.notes)}</div>
|
||||
font-size:var(--text-xs);white-space:pre-wrap">${UI.escape(inv.notes)}</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`,
|
||||
|
|
@ -4451,7 +4446,7 @@ window.Page_admin = (() => {
|
|||
|
||||
const monthRows = (cf.monthly || []).map((m, i) => `
|
||||
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
|
||||
<td class="adm-td">${_esc(m.month)}</td>
|
||||
<td class="adm-td">${UI.escape(m.month)}</td>
|
||||
<td class="adm-td text-right">${m.count}</td>
|
||||
<td class="adm-td" style="text-align:right;font-weight:600">${_fmtEur(m.revenue)}</td>
|
||||
</tr>`).join('');
|
||||
|
|
@ -4593,8 +4588,8 @@ window.Page_admin = (() => {
|
|||
? ` <span class="text-xs-muted">(RG: ${_fmtE(inv.amount_gross)})</span>` : '';
|
||||
return `
|
||||
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
|
||||
<td class="adm-td" style="font-family:monospace;font-size:var(--text-xs);${isStorno?'color:var(--c-danger)':''}">${_esc(inv.invoice_number)}</td>
|
||||
<td class="adm-td">${_esc(inv.recipient_name)}</td>
|
||||
<td class="adm-td" style="font-family:monospace;font-size:var(--text-xs);${isStorno?'color:var(--c-danger)':''}">${UI.escape(inv.invoice_number)}</td>
|
||||
<td class="adm-td">${UI.escape(inv.recipient_name)}</td>
|
||||
<td class="adm-td" style="text-align:right;font-weight:600;${amtColor}">${_fmtE(effectiveAmt)}${amtNote}</td>
|
||||
<td class="adm-td" style="${isStorno?'color:var(--c-danger)':''}">${sL[inv.status]||inv.status}</td>
|
||||
<td class="adm-td text-xs-muted">${_fmtD(inv.created_at)}</td>
|
||||
|
|
@ -4602,7 +4597,7 @@ window.Page_admin = (() => {
|
|||
}).join('');
|
||||
resultEl.innerHTML = `
|
||||
<div style="font-size:var(--text-xs);font-weight:700;color:var(--c-text-secondary);margin-bottom:var(--space-2)">
|
||||
${_esc(data.period || `Q${q} ${year}`)} — ${data.count} Buchung(en) · Summe: ${_fmtE(data.total_gross)}
|
||||
${UI.escape(data.period || `Q${q} ${year}`)} — ${data.count} Buchung(en) · Summe: ${_fmtE(data.total_gross)}
|
||||
</div>
|
||||
<div class="adm-table-scroll">
|
||||
<table class="adm-table">
|
||||
|
|
@ -4622,7 +4617,7 @@ window.Page_admin = (() => {
|
|||
</table>
|
||||
</div>`;
|
||||
} catch (e) {
|
||||
resultEl.innerHTML = `<div style="color:var(--c-danger);font-size:var(--text-xs)">Fehler: ${_esc(e.message)}</div>`;
|
||||
resultEl.innerHTML = `<div style="color:var(--c-danger);font-size:var(--text-xs)">Fehler: ${UI.escape(e.message)}</div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ window.Page_adoption = (() => {
|
|||
<input id="adp-rasse" class="form-control" type="text"
|
||||
placeholder="Rasse filtern…"
|
||||
style="flex:1;min-width:120px;max-width:220px"
|
||||
value="${_esc(_rasseFilter)}">
|
||||
value="${UI.escape(_rasseFilter)}">
|
||||
<button class="btn btn-secondary" id="adp-btn-locate"
|
||||
style="white-space:nowrap">
|
||||
${UI.icon('map-pin')} Mein Standort
|
||||
|
|
@ -306,7 +306,7 @@ window.Page_adoption = (() => {
|
|||
if (!animals.length) {
|
||||
content.innerHTML = `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
${_rasseFilter ? `Keine Hunde gefunden für "<strong>${_esc(_rasseFilter)}</strong>"` : `Keine Hunde im Umkreis von ${_radius} km gefunden.`}
|
||||
${_rasseFilter ? `Keine Hunde gefunden für "<strong>${UI.escape(_rasseFilter)}</strong>"` : `Keine Hunde im Umkreis von ${_radius} km gefunden.`}
|
||||
</p>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-3);max-width:380px">
|
||||
<a href="https://www.tierheimhelden.de/hunde/liste"
|
||||
|
|
@ -355,7 +355,7 @@ window.Page_adoption = (() => {
|
|||
|
||||
function _animalCard(a) {
|
||||
const foto = a.foto_url
|
||||
? `<img src="${_esc(a.foto_url)}" alt="${_esc(a.name)}"
|
||||
? `<img src="${UI.escape(a.foto_url)}" alt="${UI.escape(a.name)}"
|
||||
style="width:100%;height:100%;object-fit:cover"
|
||||
onerror="this.parentElement.innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:2rem">🐶</div>'">`
|
||||
: '<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:2.5rem">🐶</div>';
|
||||
|
|
@ -366,7 +366,7 @@ window.Page_adoption = (() => {
|
|||
const tierheim = a.tierheim || '';
|
||||
|
||||
return `
|
||||
<div data-adp-url="${_esc(a.adoptions_url)}"
|
||||
<div data-adp-url="${UI.escape(a.adoptions_url)}"
|
||||
style="border-radius:var(--radius-md);overflow:hidden;
|
||||
background:var(--c-surface-2);cursor:pointer;
|
||||
box-shadow:0 1px 4px rgba(0,0,0,0.08);
|
||||
|
|
@ -379,16 +379,16 @@ window.Page_adoption = (() => {
|
|||
<div style="padding:var(--space-2) var(--space-2) var(--space-3)">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);
|
||||
margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(a.name)}
|
||||
${UI.escape(a.name)}
|
||||
</div>
|
||||
${rasseTxt ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(rasseTxt)}
|
||||
${UI.escape(rasseTxt)}
|
||||
</div>` : ''}
|
||||
<div style="display:flex;gap:var(--space-1);flex-wrap:wrap;margin-top:var(--space-1)">
|
||||
${alterTxt ? `<span style="font-size:10px;background:var(--c-surface-3);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-text-secondary)">
|
||||
${_esc(alterTxt)}
|
||||
${UI.escape(alterTxt)}
|
||||
</span>` : ''}
|
||||
${a.geschlecht ? `<span style="font-size:10px;background:var(--c-surface-3);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-text-secondary)">
|
||||
|
|
@ -396,12 +396,12 @@ window.Page_adoption = (() => {
|
|||
</span>` : ''}
|
||||
${distTxt ? `<span style="font-size:10px;background:var(--c-primary-light,#ede9fe);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-primary)">
|
||||
${_esc(distTxt)}
|
||||
${UI.escape(distTxt)}
|
||||
</span>` : ''}
|
||||
</div>
|
||||
${tierheim ? `<div style="font-size:10px;color:var(--c-text-muted);margin-top:var(--space-1);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${_esc(tierheim)}">
|
||||
${UI.icon('house-line')} ${_esc(tierheim)}
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${UI.escape(tierheim)}">
|
||||
${UI.icon('house-line')} ${UI.escape(tierheim)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -459,7 +459,7 @@ window.Page_adoption = (() => {
|
|||
|
||||
function _shelterRow(s) {
|
||||
return `
|
||||
<a href="${_esc(s.url)}" target="_blank" rel="noopener noreferrer"
|
||||
<a href="${UI.escape(s.url)}" target="_blank" rel="noopener noreferrer"
|
||||
style="display:flex;align-items:center;gap:var(--space-3);
|
||||
padding:var(--space-3);border-radius:var(--radius-md);
|
||||
background:var(--c-surface-2);text-decoration:none;color:inherit;
|
||||
|
|
@ -476,10 +476,10 @@ window.Page_adoption = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(s.name)}
|
||||
${UI.escape(s.name)}
|
||||
</div>
|
||||
<div class="text-xs-secondary">
|
||||
${_esc(s.plz)} ${_esc(s.stadt)}
|
||||
${UI.escape(s.plz)} ${UI.escape(s.stadt)}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:2px;flex-shrink:0">
|
||||
|
|
@ -610,7 +610,7 @@ window.Page_adoption = (() => {
|
|||
|
||||
function _communityCard(l) {
|
||||
const foto = l.foto_url
|
||||
? `<img src="${_esc(l.foto_url)}" alt="${_esc(l.name)}"
|
||||
? `<img src="${UI.escape(l.foto_url)}" alt="${UI.escape(l.name)}"
|
||||
style="width:100%;height:100%;object-fit:cover"
|
||||
onerror="this.parentElement.innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:2.5rem">🐾</div>'">`
|
||||
: '<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:2.5rem">🐾</div>';
|
||||
|
|
@ -635,11 +635,11 @@ window.Page_adoption = (() => {
|
|||
|
||||
const interestBtn = l.user_interested
|
||||
? `<button class="btn btn-secondary btn-sm" style="width:100%;font-size:var(--text-xs)"
|
||||
data-adp-interest="${_esc(l.id)}" data-adp-interested="true">
|
||||
data-adp-interest="${UI.escape(l.id)}" data-adp-interested="true">
|
||||
✓ Bereits gemeldet
|
||||
</button>`
|
||||
: `<button class="btn btn-primary btn-sm" style="width:100%;font-size:var(--text-xs)"
|
||||
data-adp-interest="${_esc(l.id)}" data-adp-interested="false"
|
||||
data-adp-interest="${UI.escape(l.id)}" data-adp-interested="false"
|
||||
${!isActive ? 'disabled' : ''}>
|
||||
Interesse bekunden
|
||||
</button>`;
|
||||
|
|
@ -657,7 +657,7 @@ window.Page_adoption = (() => {
|
|||
display:flex;align-items:center;justify-content:center">
|
||||
<span style="color:#fff;font-weight:700;font-size:var(--text-sm);
|
||||
background:rgba(0,0,0,0.6);padding:4px 12px;border-radius:999px">
|
||||
${_esc(statusLabel)}
|
||||
${UI.escape(statusLabel)}
|
||||
</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
|
@ -666,17 +666,17 @@ window.Page_adoption = (() => {
|
|||
<div style="padding:var(--space-2) var(--space-2) var(--space-3);flex:1;display:flex;flex-direction:column;gap:var(--space-1)">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(l.name)}
|
||||
${UI.escape(l.name)}
|
||||
</div>
|
||||
${l.rasse ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(l.rasse)}
|
||||
${UI.escape(l.rasse)}
|
||||
</div>` : ''}
|
||||
<!-- Badges -->
|
||||
<div style="display:flex;gap:4px;flex-wrap:wrap">
|
||||
${alterLabel ? `<span style="font-size:10px;background:var(--c-surface-3);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-text-secondary)">
|
||||
${_esc(alterLabel)}
|
||||
${UI.escape(alterLabel)}
|
||||
</span>` : ''}
|
||||
${genderIcon ? `<span style="font-size:10px;background:var(--c-surface-3);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-text-secondary)">
|
||||
|
|
@ -684,14 +684,14 @@ window.Page_adoption = (() => {
|
|||
</span>` : ''}
|
||||
${distTxt ? `<span style="font-size:10px;background:var(--c-primary-light,#ede9fe);
|
||||
border-radius:999px;padding:1px 6px;color:var(--c-primary)">
|
||||
${_esc(distTxt)}
|
||||
${UI.escape(distTxt)}
|
||||
</span>` : ''}
|
||||
</div>
|
||||
${ort ? `<div style="font-size:10px;color:var(--c-text-muted)">${_esc(ort)}</div>` : ''}
|
||||
${ort ? `<div style="font-size:10px;color:var(--c-text-muted)">${UI.escape(ort)}</div>` : ''}
|
||||
${l.beschreibung ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
overflow:hidden;display:-webkit-box;
|
||||
-webkit-line-clamp:2;-webkit-box-orient:vertical">
|
||||
${_esc(l.beschreibung)}
|
||||
${UI.escape(l.beschreibung)}
|
||||
</div>` : ''}
|
||||
${l.interesse_count ? `<div style="font-size:10px;color:var(--c-text-muted)">
|
||||
❤️ ${l.interesse_count} Interessent${l.interesse_count !== 1 ? 'en' : ''}
|
||||
|
|
@ -717,20 +717,20 @@ window.Page_adoption = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(l.name)}
|
||||
${UI.escape(l.name)}
|
||||
</div>
|
||||
<div class="text-xs-secondary">
|
||||
${l.interesse_count || 0} Interessent${(l.interesse_count || 0) !== 1 ? 'en' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<select class="form-control" style="width:auto;font-size:var(--text-xs)"
|
||||
data-adp-status-change="${_esc(l.id)}">
|
||||
data-adp-status-change="${UI.escape(l.id)}">
|
||||
${statusOptions.map(o => `
|
||||
<option value="${o.value}" ${l.status === o.value ? 'selected' : ''}>${o.label}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
<button class="btn btn-danger btn-sm" style="font-size:var(--text-xs);white-space:nowrap"
|
||||
data-adp-delete="${_esc(l.id)}">
|
||||
data-adp-delete="${UI.escape(l.id)}">
|
||||
${UI.icon('trash')} Löschen
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -849,7 +849,7 @@ window.Page_adoption = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">PLZ</label>
|
||||
<input class="form-control" name="plz" inputmode="numeric" maxlength="5"
|
||||
placeholder="z.B. 80331" value="${_esc(_lat ? '' : '')}">
|
||||
placeholder="z.B. 80331" value="${UI.escape(_lat ? '' : '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ort</label>
|
||||
|
|
@ -941,15 +941,6 @@ window.Page_adoption = (() => {
|
|||
return 'Senior';
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC API
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ window.Page_breeder_editor = (() => {
|
|||
background:var(--c-surface-2);overflow:hidden;flex-shrink:0;
|
||||
display:flex;align-items:center;justify-content:center">
|
||||
${p.logo_url
|
||||
? `<img src="${_esc(p.logo_url)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
? `<img src="${UI.escape(p.logo_url)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
: `<svg class="ph-icon" style="width:32px;height:32px;opacity:.3"><use href="/icons/phosphor.svg#image"></use></svg>`}
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -70,45 +70,45 @@ window.Page_breeder_editor = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Zwingername *</label>
|
||||
<input class="form-control" name="zwingername" type="text" required
|
||||
value="${_esc(p.zwingername || '')}">
|
||||
value="${UI.escape(p.zwingername || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rasse(n)</label>
|
||||
<input class="form-control" name="rasse_text" type="text"
|
||||
value="${_esc(p.rasse_text || '')}">
|
||||
value="${UI.escape(p.rasse_text || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Slogan <span style="font-weight:400;color:var(--c-text-muted)">(max. 80 Zeichen)</span></label>
|
||||
<input class="form-control" name="tagline" type="text" maxlength="80"
|
||||
placeholder="z. B. Liebevolle Aufzucht seit 2010 · VDH-anerkannt"
|
||||
value="${_esc(p.tagline || '')}">
|
||||
value="${UI.escape(p.tagline || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Über uns / Zwingerbeschreibung</label>
|
||||
<textarea class="form-control" name="beschreibung" rows="4" maxlength="800"
|
||||
placeholder="Wer seid ihr, was ist euch bei der Zucht wichtig?">${_esc(p.beschreibung || '')}</textarea>
|
||||
placeholder="Wer seid ihr, was ist euch bei der Zucht wichtig?">${UI.escape(p.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Stadt</label>
|
||||
<input class="form-control" name="stadt" type="text" value="${_esc(p.stadt || '')}">
|
||||
<input class="form-control" name="stadt" type="text" value="${UI.escape(p.stadt || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Verein</label>
|
||||
<input class="form-control" name="verein" type="text" value="${_esc(p.verein || '')}">
|
||||
<input class="form-control" name="verein" type="text" value="${UI.escape(p.verein || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Website</label>
|
||||
<input class="form-control" name="website" type="url"
|
||||
placeholder="https://" value="${_esc(p.website || '')}">
|
||||
placeholder="https://" value="${UI.escape(p.website || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Instagram</label>
|
||||
<input class="form-control" name="instagram" type="text"
|
||||
placeholder="@zwingername" value="${_esc(p.instagram || '')}">
|
||||
placeholder="@zwingername" value="${UI.escape(p.instagram || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary btn-sm" style="align-self:flex-start">
|
||||
|
|
@ -161,10 +161,10 @@ window.Page_breeder_editor = (() => {
|
|||
return `
|
||||
<div style="position:relative;aspect-ratio:1;border-radius:var(--radius-md);overflow:hidden;background:var(--c-surface-2)">
|
||||
${isVid
|
||||
? `<video src="${_esc(ph.url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
|
||||
? `<video src="${UI.escape(ph.url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
|
||||
onmouseenter="this.play()" onmouseleave="this.pause()"></video>
|
||||
<div style="position:absolute;bottom:4px;left:4px;background:rgba(0,0,0,.55);border-radius:4px;padding:1px 5px;font-size:10px;color:#fff">▶ Video</div>`
|
||||
: `<img src="${_esc(ph.thumbnail_url || ph.url)}" style="width:100%;height:100%;object-fit:cover">`}
|
||||
: `<img src="${UI.escape(ph.thumbnail_url || ph.url)}" style="width:100%;height:100%;object-fit:cover">`}
|
||||
${ph.is_primary ? `<div style="position:absolute;top:4px;left:4px;background:rgba(196,132,58,.9);border-radius:3px;padding:1px 5px;font-size:9px;color:#fff;font-weight:700">LOGO</div>` : ''}
|
||||
<button class="be-photo-del" data-id="${ph.id}"
|
||||
style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);
|
||||
|
|
@ -190,14 +190,14 @@ window.Page_breeder_editor = (() => {
|
|||
<div style="border:1px solid var(--c-border);border-radius:var(--radius-md);padding:var(--space-3)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-2)">
|
||||
<div>
|
||||
<div style="font-weight:700;font-size:var(--text-sm)">${_esc(label)}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-sm)">${UI.escape(label)}</div>
|
||||
<div class="text-xs-muted">${info}</div>
|
||||
</div>
|
||||
<label class="btn btn-secondary btn-sm" style="cursor:pointer">
|
||||
<svg class="ph-icon" style="width:14px;height:14px"><use href="/icons/phosphor.svg#upload-simple"></use></svg>
|
||||
Upload
|
||||
<input type="file" class="be-litter-input" data-litter-id="${l.id}"
|
||||
data-label="${_esc(label)}" accept="image/*,video/*" class="hidden">
|
||||
data-label="${UI.escape(label)}" accept="image/*,video/*" class="hidden">
|
||||
</label>
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -312,10 +312,6 @@ window.Page_breeder_editor = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return String(s ?? '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ window.Page_breeder = (() => {
|
|||
let _container = null;
|
||||
let _appState = null;
|
||||
|
||||
const _esc = s => UI.esc ? UI.esc(s) : String(s ?? '').replace(/[&<>"']/g,
|
||||
c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// INIT
|
||||
|
|
@ -51,7 +49,7 @@ window.Page_breeder = (() => {
|
|||
} catch (e) {
|
||||
document.getElementById('breeder-profile-body').innerHTML =
|
||||
`<div style="padding:var(--space-8);text-align:center;color:var(--c-text-secondary)">
|
||||
${UI.icon('magnifying-glass')} ${_esc(e.message || 'Züchter nicht gefunden.')}
|
||||
${UI.icon('magnifying-glass')} ${UI.escape(e.message || 'Züchter nicht gefunden.')}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
|
@ -80,17 +78,17 @@ window.Page_breeder = (() => {
|
|||
${UI.icon('seal-check')} Verifizierter Züchter
|
||||
</p>
|
||||
<h1 style="margin:0 0 var(--space-2);font-size:clamp(1.3rem,4vw,1.9rem);font-weight:800;line-height:1.2;word-break:break-word">
|
||||
${_esc(p.zwingername)}
|
||||
${UI.escape(p.zwingername)}
|
||||
</h1>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center">
|
||||
${p.rasse_text ? `<span style="background:rgba(255,255,255,.2);border-radius:999px;padding:2px 10px;font-size:var(--text-xs);font-weight:600">${_esc(p.rasse_text)}</span>` : ''}
|
||||
${p.rasse_text ? `<span style="background:rgba(255,255,255,.2);border-radius:999px;padding:2px 10px;font-size:var(--text-xs);font-weight:600">${UI.escape(p.rasse_text)}</span>` : ''}
|
||||
${p.vdh_mitglied ? `<span style="background:rgba(255,255,255,.2);border-radius:999px;padding:2px 10px;font-size:var(--text-xs);font-weight:600">${UI.icon('certificate')} VDH</span>` : ''}
|
||||
${p.stadt ? `<span style="opacity:.8;font-size:var(--text-xs)">${UI.icon('map-pin')} ${_esc(p.stadt)}</span>` : ''}
|
||||
${seit ? `<span style="opacity:.7;font-size:var(--text-xs)">Züchter seit ${_esc(seit)}</span>` : ''}
|
||||
${p.stadt ? `<span style="opacity:.8;font-size:var(--text-xs)">${UI.icon('map-pin')} ${UI.escape(p.stadt)}</span>` : ''}
|
||||
${seit ? `<span style="opacity:.7;font-size:var(--text-xs)">Züchter seit ${UI.escape(seit)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${p.logo_url
|
||||
? `<img src="${_esc(p.logo_url)}" alt="Zwinger-Logo"
|
||||
? `<img src="${UI.escape(p.logo_url)}" alt="Zwinger-Logo"
|
||||
style="width:72px;height:72px;border-radius:50%;object-fit:cover;
|
||||
border:3px solid rgba(255,255,255,.5);flex-shrink:0;box-shadow:0 2px 12px rgba(0,0,0,.25)"
|
||||
onerror="this.style.display='none'">`
|
||||
|
|
@ -117,7 +115,7 @@ window.Page_breeder = (() => {
|
|||
Anmelden um zu schreiben
|
||||
</button>`
|
||||
}
|
||||
${p.website ? `<a href="${_esc(p.website)}" target="_blank" rel="noopener noreferrer"
|
||||
${p.website ? `<a href="${UI.escape(p.website)}" target="_blank" rel="noopener noreferrer"
|
||||
style="background:rgba(255,255,255,.2);color:white;border:1px solid rgba(255,255,255,.4);
|
||||
border-radius:999px;padding:var(--space-2) var(--space-5);
|
||||
font-weight:600;font-size:var(--text-sm);text-decoration:none;
|
||||
|
|
@ -134,7 +132,7 @@ window.Page_breeder = (() => {
|
|||
${p.beschreibung ? `
|
||||
<div style="background:var(--c-bg-secondary);border:1px solid var(--c-border);border-radius:var(--radius-lg);
|
||||
padding:var(--space-4);margin-bottom:var(--space-4)">
|
||||
<p style="margin:0;line-height:1.7;color:var(--c-text-secondary);white-space:pre-line">${_esc(p.beschreibung)}</p>
|
||||
<p style="margin:0;line-height:1.7;color:var(--c-text-secondary);white-space:pre-line">${UI.escape(p.beschreibung)}</p>
|
||||
</div>` : ''}
|
||||
|
||||
<!-- Zuchthunde -->
|
||||
|
|
@ -192,8 +190,8 @@ window.Page_breeder = (() => {
|
|||
${p.website ? `
|
||||
<div style="display:flex;gap:var(--space-2);align-items:baseline">
|
||||
<dt style="color:var(--c-text-secondary);min-width:110px;font-size:var(--text-sm);flex-shrink:0">Website</dt>
|
||||
<dd style="margin:0"><a href="${_esc(p.website)}" target="_blank" rel="noopener noreferrer"
|
||||
style="color:var(--c-primary);word-break:break-all">${_esc(p.website)}</a></dd>
|
||||
<dd style="margin:0"><a href="${UI.escape(p.website)}" target="_blank" rel="noopener noreferrer"
|
||||
style="color:var(--c-primary);word-break:break-all">${UI.escape(p.website)}</a></dd>
|
||||
</div>` : ''}
|
||||
${seit ? _dl('Züchter seit', seit) : ''}
|
||||
</dl>
|
||||
|
|
@ -209,11 +207,11 @@ window.Page_breeder = (() => {
|
|||
</h2>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-2)">
|
||||
${p.fotos.map((ph, i) => `
|
||||
<a href="${_esc(ph.url)}" target="_blank" rel="noopener noreferrer"
|
||||
<a href="${UI.escape(ph.url)}" target="_blank" rel="noopener noreferrer"
|
||||
style="display:block;border-radius:var(--radius-md);overflow:hidden;
|
||||
border:${ph.primary ? '2px solid var(--c-primary)' : '1px solid var(--c-border)'};
|
||||
aspect-ratio:1;position:relative">
|
||||
<img src="${_esc(ph.thumb)}" alt="${_esc(ph.caption)}"
|
||||
<img src="${UI.escape(ph.thumb)}" alt="${UI.escape(ph.caption)}"
|
||||
loading="${i < 6 ? 'eager' : 'lazy'}"
|
||||
style="width:100%;height:100%;object-fit:cover;display:block"
|
||||
onerror="this.parentElement.style.display='none'">
|
||||
|
|
@ -221,7 +219,7 @@ window.Page_breeder = (() => {
|
|||
color:white;font-size:9px;font-weight:700;border-radius:999px;padding:1px 6px">Logo</span>` : ''}
|
||||
${ph.caption ? `<div style="position:absolute;bottom:0;left:0;right:0;
|
||||
background:linear-gradient(transparent,rgba(0,0,0,.6));
|
||||
color:white;font-size:10px;padding:12px 6px 4px;line-height:1.3">${_esc(ph.caption)}</div>` : ''}
|
||||
color:white;font-size:10px;padding:12px 6px 4px;line-height:1.3">${UI.escape(ph.caption)}</div>` : ''}
|
||||
</a>`).join('')}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
|
@ -251,14 +249,14 @@ window.Page_breeder = (() => {
|
|||
const augeTest = h.health_tests?.find(t => t.test_typ === 'augen');
|
||||
|
||||
const testPills = [
|
||||
hdTest ? `<span style="${_testPillStyle(hdTest.ergebnis,'HD')}">HD ${_esc(hdTest.ergebnis)}</span>` : '',
|
||||
edTest ? `<span style="${_testPillStyle(edTest.ergebnis,'ED')}">ED ${_esc(edTest.ergebnis)}</span>` : '',
|
||||
hdTest ? `<span style="${_testPillStyle(hdTest.ergebnis,'HD')}">HD ${UI.escape(hdTest.ergebnis)}</span>` : '',
|
||||
edTest ? `<span style="${_testPillStyle(edTest.ergebnis,'ED')}">ED ${UI.escape(edTest.ergebnis)}</span>` : '',
|
||||
augeTest ? `<span style="${_testPillStyle('clear','augen')}">Augen ✓</span>` : '',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const titlePills = (h.titel || []).map(t =>
|
||||
`<span style="background:var(--c-primary-light,#f5e6d3);color:var(--c-primary-dark,#a86e2e);
|
||||
border-radius:999px;padding:1px 8px;font-size:10px;font-weight:700">${_esc(t)}</span>`
|
||||
border-radius:999px;padding:1px 8px;font-size:10px;font-weight:700">${UI.escape(t)}</span>`
|
||||
).join('');
|
||||
|
||||
const genBadge = h.gentests_total > 0
|
||||
|
|
@ -272,11 +270,11 @@ window.Page_breeder = (() => {
|
|||
padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<span class="text-primary">${gIcon}</span>
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${_esc(h.name)}</span>
|
||||
${h.rufname ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">"${_esc(h.rufname)}"</span>` : ''}
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${UI.escape(h.name)}</span>
|
||||
${h.rufname ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">"${UI.escape(h.rufname)}"</span>` : ''}
|
||||
${alter !== null ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs);margin-left:auto">${alter} J.</span>` : ''}
|
||||
</div>
|
||||
${h.farbe ? `<p style="margin:0;font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(h.farbe)}</p>` : ''}
|
||||
${h.farbe ? `<p style="margin:0;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(h.farbe)}</p>` : ''}
|
||||
${testPills ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${testPills}</div>` : ''}
|
||||
${titlePills ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${titlePills}</div>` : ''}
|
||||
${genBadge}
|
||||
|
|
@ -318,16 +316,16 @@ window.Page_breeder = (() => {
|
|||
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius-lg);
|
||||
padding:var(--space-3) var(--space-4)">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${_esc(eltern)}</span>
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${UI.escape(eltern)}</span>
|
||||
<span style="background:${sc}1a;color:${sc};border:1px solid ${sc}40;
|
||||
border-radius:999px;padding:1px 8px;font-size:var(--text-xs);font-weight:600">${sl}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
${datum ? `<span>${UI.icon('calendar-dots')} ${_esc(datum)}</span>` : ''}
|
||||
${datum ? `<span>${UI.icon('calendar-dots')} ${UI.escape(datum)}</span>` : ''}
|
||||
${w.welpen_gesamt ? `<span>${UI.icon('dog')} ${w.welpen_verfuegbar ?? '?'}/${w.welpen_gesamt} verfügbar</span>` : ''}
|
||||
${w.preis_spanne ? `<span>${UI.icon('currency-eur')} ${_esc(w.preis_spanne)}</span>` : ''}
|
||||
${w.preis_spanne ? `<span>${UI.icon('currency-eur')} ${UI.escape(w.preis_spanne)}</span>` : ''}
|
||||
</div>
|
||||
${w.beschreibung ? `<p style="margin:var(--space-2) 0 0;font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">${_esc(w.beschreibung)}</p>` : ''}
|
||||
${w.beschreibung ? `<p style="margin:var(--space-2) 0 0;font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">${UI.escape(w.beschreibung)}</p>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -340,11 +338,11 @@ window.Page_breeder = (() => {
|
|||
return `
|
||||
<div>
|
||||
<p style="margin:0 0 var(--space-2);font-size:var(--text-xs);font-weight:700;
|
||||
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em">${_esc(label)}</p>
|
||||
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em">${UI.escape(label)}</p>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
|
||||
${stats.map(r => `
|
||||
<div style="display:flex;align-items:center;gap:6px;font-size:var(--text-sm)">
|
||||
<span style="font-weight:700">${_esc(r.ergebnis || '—')}</span>
|
||||
<span style="font-weight:700">${UI.escape(r.ergebnis || '—')}</span>
|
||||
<span class="text-muted">${r.cnt}×</span>
|
||||
<span style="background:var(--c-border);border-radius:999px;height:6px;
|
||||
width:${Math.round(r.cnt/total*80)+16}px;display:inline-block"></span>
|
||||
|
|
@ -359,8 +357,8 @@ window.Page_breeder = (() => {
|
|||
function _dl(label, value) {
|
||||
if (!value) return '';
|
||||
return `<div style="display:flex;gap:var(--space-2);align-items:baseline">
|
||||
<dt style="color:var(--c-text-secondary);min-width:110px;font-size:var(--text-sm);flex-shrink:0">${_esc(label)}</dt>
|
||||
<dd style="margin:0;font-size:var(--text-sm)">${_esc(String(value))}</dd>
|
||||
<dt style="color:var(--c-text-secondary);min-width:110px;font-size:var(--text-sm);flex-shrink:0">${UI.escape(label)}</dt>
|
||||
<dd style="margin:0;font-size:var(--text-sm)">${UI.escape(String(value))}</dd>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -383,10 +381,10 @@ window.Page_breeder = (() => {
|
|||
</h2>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:var(--space-2)">
|
||||
${photos.map(ph => `
|
||||
<a href="${_esc(ph.url||'')}" target="_blank" rel="noopener noreferrer"
|
||||
<a href="${UI.escape(ph.url||'')}" target="_blank" rel="noopener noreferrer"
|
||||
style="display:block;border-radius:var(--radius-md);overflow:hidden;
|
||||
border:1px solid var(--c-border);aspect-ratio:1">
|
||||
<img src="${_esc(ph.thumbnail_url||ph.url||'')}" alt="${_esc(ph.caption||'')}"
|
||||
<img src="${UI.escape(ph.thumbnail_url||ph.url||'')}" alt="${UI.escape(ph.caption||'')}"
|
||||
loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block"
|
||||
onerror="this.parentElement.style.display='none'">
|
||||
</a>`).join('')}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ window.Page_chat = (() => {
|
|||
el.innerHTML = convs.map(c => {
|
||||
const initials = (c.partner_name || '?')[0].toUpperCase();
|
||||
const preview = c.last_text
|
||||
? _esc(c.last_text.substring(0, 60)) + (c.last_text.length > 60 ? '…' : '')
|
||||
? UI.escape(c.last_text.substring(0, 60)) + (c.last_text.length > 60 ? '…' : '')
|
||||
: '<em style="opacity:0.6">Noch keine Nachrichten</em>';
|
||||
const timeStr = c.last_msg_at ? _fmtTime(c.last_msg_at) : '';
|
||||
const badge = c.unread_count > 0
|
||||
|
|
@ -138,7 +138,7 @@ window.Page_chat = (() => {
|
|||
${onlineDot ? `<span class="online-dot chat-avatar-dot"></span>` : ''}
|
||||
</div>
|
||||
<div class="chat-conv-info">
|
||||
<div class="chat-conv-name">${_esc(c.partner_name)}</div>
|
||||
<div class="chat-conv-name">${UI.escape(c.partner_name)}</div>
|
||||
<div class="chat-conv-preview">${preview}</div>
|
||||
</div>
|
||||
<div class="chat-conv-meta">
|
||||
|
|
@ -332,10 +332,10 @@ window.Page_chat = (() => {
|
|||
}
|
||||
if (m.text) {
|
||||
bubbleContent += (m.media_url ? `<div style="margin-top:var(--space-1)">` : '') +
|
||||
_esc(m.text) +
|
||||
UI.escape(m.text) +
|
||||
(m.media_url ? `</div>` : '');
|
||||
}
|
||||
if (!bubbleContent) bubbleContent = _esc(m.text);
|
||||
if (!bubbleContent) bubbleContent = UI.escape(m.text);
|
||||
|
||||
html += `
|
||||
<div class="chat-bubble-row ${rowClass}">
|
||||
|
|
@ -450,13 +450,6 @@ window.Page_chat = (() => {
|
|||
return d.toLocaleDateString('de-DE', { day: '2-digit', month: 'long', year: 'numeric' });
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')
|
||||
.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Neue Nachricht — Freundesliste als Picker
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ window.Page_dog_profile = (() => {
|
|||
<div style="position:relative;display:inline-block;margin-bottom:var(--space-4);padding:4px">
|
||||
${dog.foto_url
|
||||
? `<div class="dp-avatar-ring">
|
||||
<img src="${dog.foto_url}" alt="${_esc(dog.name)}" class="dp-avatar-img"
|
||||
<img src="${dog.foto_url}" alt="${UI.escape(dog.name)}" class="dp-avatar-img"
|
||||
style="transform:scale(${dog.foto_zoom||1}) translate(${dog.foto_offset_x||0}%,${dog.foto_offset_y||0}%)">
|
||||
</div>`
|
||||
: `<div class="dp-avatar-ring dp-avatar-empty">${UI.icon('dog')}</div>`}
|
||||
|
|
@ -95,9 +95,9 @@ window.Page_dog_profile = (() => {
|
|||
|
||||
<!-- Name + Rasse -->
|
||||
<h2 style="font-size:var(--text-2xl);font-weight:700;
|
||||
color:var(--c-text);margin:0 0 var(--space-1)">${_esc(dog.name)}</h2>
|
||||
color:var(--c-text);margin:0 0 var(--space-1)">${UI.escape(dog.name)}</h2>
|
||||
${dog.rasse
|
||||
? `<p style="color:var(--c-text-secondary);margin:0 0 var(--space-2)">${_esc(dog.rasse)}</p>`
|
||||
? `<p style="color:var(--c-text-secondary);margin:0 0 var(--space-2)">${UI.escape(dog.rasse)}</p>`
|
||||
: `<p style="margin:0 0 var(--space-2)"></p>`}
|
||||
|
||||
<!-- Rassen-Community-Chip (wird async geladen) -->
|
||||
|
|
@ -141,7 +141,7 @@ window.Page_dog_profile = (() => {
|
|||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#wave-sine"></use></svg> Transponder
|
||||
</div>
|
||||
${dog.chip_nr
|
||||
? `<div style="font-size:var(--text-xs);font-weight:500;word-break:break-all">${_esc(dog.chip_nr)}</div>`
|
||||
? `<div style="font-size:var(--text-xs);font-weight:500;word-break:break-all">${UI.escape(dog.chip_nr)}</div>`
|
||||
: `<div class="text-xs-muted">nicht eingetragen
|
||||
<button class="btn btn-link btn-sm" id="dp-chip-edit-btn"
|
||||
style="padding:0 0 0 var(--space-1);font-size:var(--text-xs)">Eintragen</button>
|
||||
|
|
@ -153,7 +153,7 @@ window.Page_dog_profile = (() => {
|
|||
${dog.bio ? `
|
||||
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-5);text-align:left">
|
||||
<p style="margin:0;color:var(--c-text-secondary);font-style:italic;line-height:1.6">
|
||||
"${_esc(dog.bio)}"
|
||||
"${UI.escape(dog.bio)}"
|
||||
</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
|
@ -335,7 +335,7 @@ window.Page_dog_profile = (() => {
|
|||
<svg class="ph-icon" style="width:11px;height:11px" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${isGreen ? 'check' : 'fire'}"></use>
|
||||
</svg>
|
||||
${_esc(skill.exercise_name)}
|
||||
${UI.escape(skill.exercise_name)}
|
||||
</span>`;
|
||||
};
|
||||
|
||||
|
|
@ -413,7 +413,7 @@ window.Page_dog_profile = (() => {
|
|||
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)">
|
||||
<span style="font-size:1.1em">🛁</span>
|
||||
<span style="font-size:var(--text-sm);font-weight:600">
|
||||
Pflegetipps${data.rasse_name ? ` für ${_esc(data.rasse_name)}` : ''}
|
||||
Pflegetipps${data.rasse_name ? ` für ${UI.escape(data.rasse_name)}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -426,24 +426,24 @@ window.Page_dog_profile = (() => {
|
|||
${t.saisonal_aktuell ? '🌸 Aktuell & Saisonal' : '💡 Tipp des Tages'}
|
||||
</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:4px">
|
||||
${kat_icons[t.kategorie]||_ph('paw-print')} ${_esc(t.titel)}
|
||||
${kat_icons[t.kategorie]||_ph('paw-print')} ${UI.escape(t.titel)}
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--c-text-secondary);margin-bottom:8px;
|
||||
line-height:1.5">${_esc(t.beschreibung||'')}</div>
|
||||
line-height:1.5">${UI.escape(t.beschreibung||'')}</div>
|
||||
${t.haeufigkeit ? `<div style="font-size:11px;color:var(--c-text-muted)">
|
||||
🔄 ${_esc(t.haeufigkeit)}</div>` : ''}
|
||||
🔄 ${UI.escape(t.haeufigkeit)}</div>` : ''}
|
||||
${t.materialien ? `<div style="font-size:11px;color:var(--c-text-muted)">
|
||||
🛒 ${_esc(t.materialien)}</div>` : ''}
|
||||
🛒 ${UI.escape(t.materialien)}</div>` : ''}
|
||||
${t.schritte?.length ? `
|
||||
<details style="margin-top:8px">
|
||||
<summary style="font-size:12px;cursor:pointer;color:var(--c-primary);
|
||||
font-weight:600">Anleitung anzeigen</summary>
|
||||
<ol style="margin:8px 0 0 16px;padding:0;font-size:12px;
|
||||
color:var(--c-text);line-height:1.6">
|
||||
${t.schritte.map(s=>`<li style="margin-bottom:3px">${_esc(s)}</li>`).join('')}
|
||||
${t.schritte.map(s=>`<li style="margin-bottom:3px">${UI.escape(s)}</li>`).join('')}
|
||||
</ol>
|
||||
${t.tipp ? `<div style="margin-top:8px;font-size:11px;color:#a78bfa;
|
||||
font-style:italic">💜 ${_esc(t.tipp)}</div>` : ''}
|
||||
font-style:italic">💜 ${UI.escape(t.tipp)}</div>` : ''}
|
||||
</details>` : ''}
|
||||
</div>` : ''}
|
||||
|
||||
|
|
@ -460,26 +460,26 @@ window.Page_dog_profile = (() => {
|
|||
<div class="mb-3">
|
||||
<div style="font-size:11px;font-weight:700;color:var(--c-text-muted);
|
||||
text-transform:uppercase;margin-bottom:8px;display:flex;align-items:center">
|
||||
${kat_icons[kat]||_ph('paw-print')} ${_esc(kat)}${katBadge}</div>
|
||||
${kat_icons[kat]||_ph('paw-print')} ${UI.escape(kat)}${katBadge}</div>
|
||||
${katTipps.map(tip => `
|
||||
<details style="background:var(--c-surface-2);border-radius:8px;
|
||||
padding:10px;margin-bottom:6px">
|
||||
<summary style="font-size:var(--text-sm);font-weight:600;cursor:pointer;
|
||||
list-style:none;display:flex;justify-content:space-between;
|
||||
align-items:center">
|
||||
${_esc(tip.titel)}
|
||||
${UI.escape(tip.titel)}
|
||||
${tip.saisonal_aktuell ? '<span style="font-size:10px;color:#10b981">● Aktuell</span>' : ''}
|
||||
</summary>
|
||||
<div style="margin-top:8px;font-size:12px;color:var(--c-text-secondary);
|
||||
line-height:1.5">${_esc(tip.beschreibung||'')}</div>
|
||||
line-height:1.5">${UI.escape(tip.beschreibung||'')}</div>
|
||||
${tip.haeufigkeit ? `<div style="font-size:11px;color:var(--c-text-muted);
|
||||
margin-top:4px">🔄 ${_esc(tip.haeufigkeit)}</div>` : ''}
|
||||
margin-top:4px">🔄 ${UI.escape(tip.haeufigkeit)}</div>` : ''}
|
||||
${tip.schritte?.length ? `
|
||||
<ol style="margin:8px 0 0 16px;padding:0;font-size:12px;line-height:1.6">
|
||||
${tip.schritte.map(s=>`<li style="margin-bottom:3px">${_esc(s)}</li>`).join('')}
|
||||
${tip.schritte.map(s=>`<li style="margin-bottom:3px">${UI.escape(s)}</li>`).join('')}
|
||||
</ol>` : ''}
|
||||
${tip.tipp ? `<div style="margin-top:6px;font-size:11px;color:#a78bfa;
|
||||
font-style:italic">💜 ${_esc(tip.tipp)}</div>` : ''}
|
||||
font-style:italic">💜 ${UI.escape(tip.tipp)}</div>` : ''}
|
||||
</details>`).join('')}
|
||||
</div>`;
|
||||
}).join('')}
|
||||
|
|
@ -499,12 +499,6 @@ window.Page_dog_profile = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<')
|
||||
.replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// SITTER-ZUGANG
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -527,8 +521,8 @@ window.Page_dog_profile = (() => {
|
|||
<div style="display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);background:var(--c-surface-2);border-radius:var(--radius-md);margin-bottom:var(--space-2)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
|
||||
<div style="flex:1;font-size:var(--text-sm)">
|
||||
<strong>${_esc(s.sitter_name)}</strong>
|
||||
<span class="text-muted"> · bis ${_esc(s.valid_until)}</span>
|
||||
<strong>${UI.escape(s.sitter_name)}</strong>
|
||||
<span class="text-muted"> · bis ${UI.escape(s.valid_until)}</span>
|
||||
</div>
|
||||
<button class="btn btn-link btn-sm sa-revoke-btn" data-sub-id="${s.id}"
|
||||
style="color:var(--c-danger);padding:0">
|
||||
|
|
@ -538,7 +532,7 @@ window.Page_dog_profile = (() => {
|
|||
}
|
||||
|
||||
const friendOptions = friends.length
|
||||
? friends.map(f => `<option value="${f.friend_id}">${_esc(f.friend_name)}</option>`).join('')
|
||||
? friends.map(f => `<option value="${f.friend_id}">${UI.escape(f.friend_name)}</option>`).join('')
|
||||
: '<option value="" disabled>Keine Freunde vorhanden</option>';
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
|
@ -617,7 +611,7 @@ window.Page_dog_profile = (() => {
|
|||
<div class="mb-3">
|
||||
<label class="form-label">Chip-Nummer (15-stellig)</label>
|
||||
<input id="chip-edit-input" class="form-control" type="text"
|
||||
value="${_esc(dog.chip_nr || '')}" placeholder="z.B. 276009200123456" maxlength="20">
|
||||
value="${UI.escape(dog.chip_nr || '')}" placeholder="z.B. 276009200123456" maxlength="20">
|
||||
</div>`,
|
||||
footer: `
|
||||
<div class="w3-btn-stack">
|
||||
|
|
@ -843,15 +837,15 @@ window.Page_dog_profile = (() => {
|
|||
<!-- Header -->
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:18px">
|
||||
${dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" style="width:52px;height:52px;border-radius:50%;object-fit:cover;
|
||||
? `<img src="${UI.escape(dog.foto_url)}" style="width:52px;height:52px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid rgba(196,132,58,0.6);flex-shrink:0">`
|
||||
: `<div style="width:52px;height:52px;border-radius:50%;background:rgba(196,132,58,0.2);
|
||||
display:flex;align-items:center;justify-content:center;font-size:1.6rem;
|
||||
flex-shrink:0;border:2px solid rgba(196,132,58,0.4)">🐾</div>`}
|
||||
<div>
|
||||
<div style="font-size:1.25rem;font-weight:800;color:#fff;line-height:1.2">${_esc(dog.name)}</div>
|
||||
${metaLine ? `<div style="font-size:0.8rem;color:rgba(255,255,255,0.6);margin-top:2px">${_esc(metaLine)}</div>` : ''}
|
||||
${wohnort ? `<div style="font-size:0.75rem;color:rgba(196,132,58,0.9);margin-top:3px">📍 ${_esc(wohnort)}</div>` : ''}
|
||||
<div style="font-size:1.25rem;font-weight:800;color:#fff;line-height:1.2">${UI.escape(dog.name)}</div>
|
||||
${metaLine ? `<div style="font-size:0.8rem;color:rgba(255,255,255,0.6);margin-top:2px">${UI.escape(metaLine)}</div>` : ''}
|
||||
${wohnort ? `<div style="font-size:0.75rem;color:rgba(196,132,58,0.9);margin-top:3px">📍 ${UI.escape(wohnort)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -862,11 +856,11 @@ window.Page_dog_profile = (() => {
|
|||
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:12px">
|
||||
<div class="flex-1-min">
|
||||
${ownerName ? `<div style="font-size:0.7rem;color:rgba(255,255,255,0.4);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">Besitzer</div>
|
||||
<div style="font-size:0.9rem;font-weight:600;color:rgba(255,255,255,0.85)">${_esc(ownerName)}</div>` : ''}
|
||||
<div style="font-size:0.9rem;font-weight:600;color:rgba(255,255,255,0.85)">${UI.escape(ownerName)}</div>` : ''}
|
||||
<div style="font-size:0.65rem;color:rgba(255,255,255,0.35);margin-top:8px">banyaro.app</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;text-align:center">
|
||||
<img id="dp-vcard-qr" src="${_esc(qrUrl)}"
|
||||
<img id="dp-vcard-qr" src="${UI.escape(qrUrl)}"
|
||||
style="width:80px;height:80px;border-radius:10px;display:block"
|
||||
alt="QR-Code">
|
||||
<div style="font-size:0.6rem;color:rgba(255,255,255,0.35);margin-top:4px">Profil öffnen</div>
|
||||
|
|
@ -880,7 +874,7 @@ window.Page_dog_profile = (() => {
|
|||
body: `
|
||||
<div class="mb-4">${cardHtml}</div>
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-secondary);text-align:center;margin-bottom:0">
|
||||
QR-Code auf NFC-Tag oder Anhänger kleben — jeder kann das Profil von ${_esc(dog.name)} sofort öffnen.
|
||||
QR-Code auf NFC-Tag oder Anhänger kleben — jeder kann das Profil von ${UI.escape(dog.name)} sofort öffnen.
|
||||
</p>
|
||||
`,
|
||||
footer: `
|
||||
|
|
@ -935,7 +929,7 @@ window.Page_dog_profile = (() => {
|
|||
|
||||
async function _showShareModal(dog) {
|
||||
UI.modal.open({
|
||||
title: `${_esc(dog.name)} teilen`,
|
||||
title: `${UI.escape(dog.name)} teilen`,
|
||||
body: `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
Erstelle einen Einladungslink, den du per WhatsApp, Signal oder E-Mail teilen kannst.
|
||||
|
|
@ -1009,7 +1003,7 @@ window.Page_dog_profile = (() => {
|
|||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
|
||||
<div style="flex:1;font-size:var(--text-sm)">
|
||||
${s.shared_with_name
|
||||
? `<strong>${_esc(s.shared_with_name)}</strong> · ${s.role}`
|
||||
? `<strong>${UI.escape(s.shared_with_name)}</strong> · ${s.role}`
|
||||
: `<em class="text-muted">Ausstehend</em> · ${s.role}`}
|
||||
</div>
|
||||
<button class="btn btn-link btn-sm share-revoke-btn" data-share-id="${s.id}"
|
||||
|
|
@ -1101,7 +1095,7 @@ window.Page_dog_profile = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Name *</label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
value="${_esc(dog?.name || '')}"
|
||||
value="${UI.escape(dog?.name || '')}"
|
||||
placeholder="z. B. Ban Yaro" required>
|
||||
</div>
|
||||
|
||||
|
|
@ -1113,7 +1107,7 @@ window.Page_dog_profile = (() => {
|
|||
</label>
|
||||
<input class="form-control" type="text" name="rasse"
|
||||
id="dp-rasse-input"
|
||||
value="${_esc(dog?.rasse || '')}"
|
||||
value="${UI.escape(dog?.rasse || '')}"
|
||||
list="dp-rasse-list"
|
||||
autocomplete="off"
|
||||
placeholder="z. B. Mischling, Golden Retriever…">
|
||||
|
|
@ -1167,7 +1161,7 @@ window.Page_dog_profile = (() => {
|
|||
${UI.help('Die 15-stellige Chip-Nummer findest du im Heimtierausweis oder beim Tierarzt.')}
|
||||
</label>
|
||||
<input class="form-control" type="text" name="chip_nr"
|
||||
value="${_esc(dog?.chip_nr || '')}" placeholder="15-stellig">
|
||||
value="${UI.escape(dog?.chip_nr || '')}" placeholder="15-stellig">
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
|
@ -1195,7 +1189,7 @@ window.Page_dog_profile = (() => {
|
|||
<span class="text-secondary">(optional)</span>
|
||||
</label>
|
||||
<textarea class="form-control" name="bio" rows="2"
|
||||
placeholder="Kurze Beschreibung…">${_esc(dog?.bio || '')}</textarea>
|
||||
placeholder="Kurze Beschreibung…">${UI.escape(dog?.bio || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -1477,7 +1471,7 @@ window.Page_dog_profile = (() => {
|
|||
Auf diesem Foto konnte kein Hund erkannt werden.<br>
|
||||
Bitte lade ein deutlicheres Foto hoch.
|
||||
</p>
|
||||
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${_esc(data.hinweis)}</p>` : ''}
|
||||
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${UI.escape(data.hinweis)}</p>` : ''}
|
||||
</div>`,
|
||||
footer: `<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>`,
|
||||
});
|
||||
|
|
@ -1490,24 +1484,24 @@ window.Page_dog_profile = (() => {
|
|||
return `
|
||||
<div class="rasse-result-card${isTop ? ' rasse-result-card--top' : ''}">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||||
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${_esc(r.name)}</div>
|
||||
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${UI.escape(r.name)}</div>
|
||||
<span class="rasse-result-pct${isTop ? '' : ' rasse-result-pct--dim'}">${r.sicherheit}%</span>
|
||||
</div>
|
||||
<div class="rasse-result-bar-wrap">
|
||||
<div class="rasse-result-bar${isTop ? '' : ' rasse-result-bar--dim'}"
|
||||
style="width:${r.sicherheit}%"></div>
|
||||
</div>
|
||||
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''}
|
||||
${r.beschreibung ? `<div class="rasse-result-desc">${UI.escape(r.beschreibung)}</div>` : ''}
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-3);flex-wrap:wrap">
|
||||
${isTop ? `<button class="btn btn-primary btn-sm" data-action="uebernehmen"
|
||||
data-rasse="${_esc(r.name)}" class="flex-1">
|
||||
data-rasse="${UI.escape(r.name)}" class="flex-1">
|
||||
Rasse übernehmen
|
||||
</button>` : `<button class="btn btn-secondary btn-sm" data-action="uebernehmen"
|
||||
data-rasse="${_esc(r.name)}" class="flex-1">
|
||||
data-rasse="${UI.escape(r.name)}" class="flex-1">
|
||||
Diese wählen
|
||||
</button>`}
|
||||
${r.wiki_slug ? `<button class="btn btn-ghost btn-sm" data-action="wiki"
|
||||
data-slug="${_esc(r.wiki_slug)}">
|
||||
data-slug="${UI.escape(r.wiki_slug)}">
|
||||
Im Wiki
|
||||
</button>` : ''}
|
||||
</div>
|
||||
|
|
@ -1521,7 +1515,7 @@ window.Page_dog_profile = (() => {
|
|||
<div style="padding-bottom:var(--space-2)">
|
||||
${data.hinweis ? `<div style="background:var(--c-surface-2);border-radius:var(--radius-md);
|
||||
padding:var(--space-3);margin-bottom:var(--space-3);font-size:var(--text-sm);
|
||||
color:var(--c-text-secondary)">ℹ️ ${_esc(data.hinweis)}</div>` : ''}
|
||||
color:var(--c-text-secondary)">ℹ️ ${UI.escape(data.hinweis)}</div>` : ''}
|
||||
${cardsHtml}
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2);
|
||||
text-align:center">
|
||||
|
|
@ -1582,18 +1576,13 @@ window.Page_dog_profile = (() => {
|
|||
: `${j} Jahr${j !== 1 ? 'e' : ''} alt`;
|
||||
}
|
||||
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// HUNDEPASS
|
||||
// ----------------------------------------------------------
|
||||
async function _showPassportModal(dog) {
|
||||
UI.modal.open({
|
||||
title: `Hundepass — ${_esc(dog.name)}`,
|
||||
title: `Hundepass — ${UI.escape(dog.name)}`,
|
||||
body: `<div id="pp-body" style="min-height:200px">
|
||||
<div style="text-align:center;padding:var(--space-6)">
|
||||
<svg class="ph-icon" style="width:32px;height:32px;color:var(--c-primary)" aria-hidden="true">
|
||||
|
|
@ -1636,7 +1625,7 @@ window.Page_dog_profile = (() => {
|
|||
try {
|
||||
data = await API.get(`/passport/${dog.id}`);
|
||||
} catch (e) {
|
||||
wrap.innerHTML = `<p class="text-danger">Fehler beim Laden: ${_esc(e.message)}</p>`;
|
||||
wrap.innerHTML = `<p class="text-danger">Fehler beim Laden: ${UI.escape(e.message)}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1670,13 +1659,13 @@ window.Page_dog_profile = (() => {
|
|||
<div>
|
||||
<div class="text-xs-secondary">Blutgruppe</div>
|
||||
<div id="pp-meta-blutgruppe" style="font-size:var(--text-sm);font-weight:500">
|
||||
${_esc(meta.blutgruppe) || '<span class="text-muted">nicht eingetragen</span>'}
|
||||
${UI.escape(meta.blutgruppe) || '<span class="text-muted">nicht eingetragen</span>'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs-secondary">Allergien</div>
|
||||
<div id="pp-meta-allergien" class="text-sm">
|
||||
${_esc(meta.allergien) || '<span class="text-muted">keine</span>'}
|
||||
${UI.escape(meta.allergien) || '<span class="text-muted">keine</span>'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1684,7 +1673,7 @@ window.Page_dog_profile = (() => {
|
|||
<div class="mt-3">
|
||||
<div class="text-xs-secondary">Besonderheiten</div>
|
||||
<div id="pp-meta-besonderheiten" class="text-sm">
|
||||
${_esc(meta.besonderheiten)}
|
||||
${UI.escape(meta.besonderheiten)}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
|
@ -1709,12 +1698,12 @@ window.Page_dog_profile = (() => {
|
|||
<div class="pp-vacc-row" data-id="${v.id}"
|
||||
class="pp-data-row">
|
||||
<div class="flex-1">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(v.krankheit)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(v.krankheit)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
|
||||
Gegeben: ${_fmt(v.datum)}
|
||||
${v.naechste ? ` · Nächste: ${_fmt(v.naechste)}` : ''}
|
||||
${v.tierarzt ? ` · ${_esc(v.tierarzt)}` : ''}
|
||||
${v.charge_nr ? ` · Charge: ${_esc(v.charge_nr)}` : ''}
|
||||
${v.tierarzt ? ` · ${UI.escape(v.tierarzt)}` : ''}
|
||||
${v.charge_nr ? ` · Charge: ${UI.escape(v.charge_nr)}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-link btn-sm pp-vacc-del" data-id="${v.id}"
|
||||
|
|
@ -1746,12 +1735,12 @@ window.Page_dog_profile = (() => {
|
|||
<div class="pp-med-row" data-id="${m.id}"
|
||||
class="pp-data-row">
|
||||
<div class="flex-1">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(m.name)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(m.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
|
||||
${m.dosierung ? `${_esc(m.dosierung)} · ` : ''}
|
||||
${m.dosierung ? `${UI.escape(m.dosierung)} · ` : ''}
|
||||
${m.von ? `Von ${_fmt(m.von)}` : ''}
|
||||
${m.bis ? ` bis ${_fmt(m.bis)}` : m.von ? ' · dauerhaft' : ''}
|
||||
${m.notiz ? ` · ${_esc(m.notiz)}` : ''}
|
||||
${m.notiz ? ` · ${UI.escape(m.notiz)}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-link btn-sm pp-med-del" data-id="${m.id}"
|
||||
|
|
@ -1813,17 +1802,17 @@ window.Page_dog_profile = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Blutgruppe</label>
|
||||
<input id="pp-meta-bg" class="form-control" type="text"
|
||||
value="${_esc(current.blutgruppe || '')}" placeholder="z. B. DEA 1.1 positiv">
|
||||
value="${UI.escape(current.blutgruppe || '')}" placeholder="z. B. DEA 1.1 positiv">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Allergien</label>
|
||||
<textarea id="pp-meta-al" class="form-control" rows="2"
|
||||
placeholder="z. B. Hühnchen, Flohspeichel">${_esc(current.allergien || '')}</textarea>
|
||||
placeholder="z. B. Hühnchen, Flohspeichel">${UI.escape(current.allergien || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Besonderheiten</label>
|
||||
<textarea id="pp-meta-be" class="form-control" rows="2"
|
||||
placeholder="z. B. Herzprobleme, Angstpatient">${_esc(current.besonderheiten || '')}</textarea>
|
||||
placeholder="z. B. Herzprobleme, Angstpatient">${UI.escape(current.besonderheiten || '')}</textarea>
|
||||
</div>`,
|
||||
footer: `
|
||||
<div style="display:flex;gap:var(--space-2);justify-content:flex-end">
|
||||
|
|
@ -2001,7 +1990,7 @@ window.Page_dog_profile = (() => {
|
|||
</p>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center">
|
||||
<input id="pp-sharelink-input" class="form-control" type="text" readonly
|
||||
value="${_esc(url)}" class="text-xs">
|
||||
value="${UI.escape(url)}" class="text-xs">
|
||||
<button class="btn btn-secondary btn-sm" id="pp-sharelink-copy" style="flex-shrink:0">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg>
|
||||
</button>
|
||||
|
|
@ -2037,7 +2026,7 @@ window.Page_dog_profile = (() => {
|
|||
return;
|
||||
}
|
||||
|
||||
const name = _esc(data.dog_name);
|
||||
const name = UI.escape(data.dog_name);
|
||||
const km = data.gesamt_km || 0;
|
||||
const konfetti = km > 100;
|
||||
|
||||
|
|
@ -2079,8 +2068,8 @@ window.Page_dog_profile = (() => {
|
|||
<div style="font-size:1rem;color:#d0c8b8;font-weight:600">Tagebucheinträge</div>
|
||||
${data.fotos_gesamt > 0 ? `<div style="font-size:1.1rem;color:#a0c890;font-weight:700;margin-top:4px">📷 ${data.fotos_gesamt} Fotos</div>` : ''}
|
||||
${data.gassi_tage > 0 ? `<div style="font-size:0.9rem;color:#888;margin-top:4px">🐾 ${data.gassi_tage} aktive Tage</div>` : ''}
|
||||
${data.lieblings_monat ? `<div style="font-size:0.85rem;color:#b89a6a;margin-top:4px">Meiste Einträge: ${_esc(data.lieblings_monat)}</div>` : ''}
|
||||
${aktivitaet ? `<div style="font-size:0.85rem;color:#888">Lieblingsaktivität: ${_esc(aktivitaet)}</div>` : ''}
|
||||
${data.lieblings_monat ? `<div style="font-size:0.85rem;color:#b89a6a;margin-top:4px">Meiste Einträge: ${UI.escape(data.lieblings_monat)}</div>` : ''}
|
||||
${aktivitaet ? `<div style="font-size:0.85rem;color:#888">Lieblingsaktivität: ${UI.escape(aktivitaet)}</div>` : ''}
|
||||
`),
|
||||
_card(`
|
||||
<div style="font-size:2rem">🌡️</div>
|
||||
|
|
@ -2299,7 +2288,7 @@ window.Page_dog_profile = (() => {
|
|||
// ----------------------------------------------------------
|
||||
async function _showTimelineModal(dog) {
|
||||
UI.modal.open({
|
||||
title: `Lebens-Timeline — ${_esc(dog.name)}`,
|
||||
title: `Lebens-Timeline — ${UI.escape(dog.name)}`,
|
||||
body: `<div id="dp-timeline-body" style="min-height:200px;text-align:center;padding:var(--space-6)">
|
||||
<svg class="ph-icon" style="width:32px;height:32px;color:var(--c-primary)" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#spinner-gap"></use>
|
||||
|
|
@ -2314,7 +2303,7 @@ window.Page_dog_profile = (() => {
|
|||
data = await API.get(`/dogs/${dog.id}/timeline`);
|
||||
} catch (e) {
|
||||
const b = document.getElementById('dp-timeline-body');
|
||||
if (b) b.innerHTML = `<p class="text-danger">Fehler: ${_esc(e.message)}</p>`;
|
||||
if (b) b.innerHTML = `<p class="text-danger">Fehler: ${UI.escape(e.message)}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2351,14 +2340,14 @@ window.Page_dog_profile = (() => {
|
|||
for (const ev of events) {
|
||||
const year = ev.datum ? ev.datum.substring(0, 4) : null;
|
||||
if (year && year !== lastYear) {
|
||||
html += `<div class="tl-year">${_esc(year)}</div>`;
|
||||
html += `<div class="tl-year">${UI.escape(year)}</div>`;
|
||||
lastYear = year;
|
||||
}
|
||||
|
||||
const kat = _KAT[ev.kategorie] || _KAT.tagebuch;
|
||||
const big = ev.is_milestone;
|
||||
|
||||
let label = _esc(ev.titel);
|
||||
let label = UI.escape(ev.titel);
|
||||
if (ev.is_first && ev.kategorie === 'tagebuch') label = `🎉 Erster Tagebucheintrag — ${label}`;
|
||||
if (ev.is_first && ev.kategorie === 'route') label = `🎉 Erste Route — ${label}`;
|
||||
if (ev.is_first && ev.kategorie === 'training') label = `🎉 Erstes Training — ${label}`;
|
||||
|
|
@ -2376,13 +2365,13 @@ window.Page_dog_profile = (() => {
|
|||
box-shadow:${big ? `0 0 0 4px ${kat.color}22` : 'none'}"></div>
|
||||
<div class="tl-card">
|
||||
${big && ev.foto_url ? `
|
||||
<div class="tl-foto" style="background-image:url(${_esc(ev.foto_url)})"></div>` : ''}
|
||||
<div class="tl-foto" style="background-image:url(${UI.escape(ev.foto_url)})"></div>` : ''}
|
||||
<div class="tl-meta">
|
||||
<span class="tl-badge" style="background:${kat.color}22;color:${kat.color}">
|
||||
<svg class="ph-icon" style="width:11px;height:11px" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${kat.icon}"></use>
|
||||
</svg>
|
||||
${_esc(kat.label)}
|
||||
${UI.escape(kat.label)}
|
||||
</span>
|
||||
<span class="tl-date">${_fmtDate(ev.datum)}</span>
|
||||
</div>
|
||||
|
|
@ -2451,8 +2440,8 @@ window.Page_dog_profile = (() => {
|
|||
if (!data || data.count === 0) return;
|
||||
const hauptRasse = data.rassen[0]?.rasse || '';
|
||||
const label = data.count === 1
|
||||
? `1 anderer ${_esc(hauptRasse)}-Halter in der App`
|
||||
: `${data.count} andere ${_esc(hauptRasse)}-Halter in der App`;
|
||||
? `1 anderer ${UI.escape(hauptRasse)}-Halter in der App`
|
||||
: `${data.count} andere ${UI.escape(hauptRasse)}-Halter in der App`;
|
||||
|
||||
el.innerHTML = `
|
||||
<button class="breed-community-chip" id="dp-breed-chip-btn">
|
||||
|
|
|
|||
|
|
@ -21,14 +21,6 @@ window.Page_ernaehrung = (() => {
|
|||
// ------------------------------------------------------------------
|
||||
// Escape helper
|
||||
// ------------------------------------------------------------------
|
||||
function _esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// LIFECYCLE
|
||||
|
|
@ -156,12 +148,12 @@ window.Page_ernaehrung = (() => {
|
|||
<div class="ern-field">
|
||||
<label>⚖️ Gewicht (kg)</label>
|
||||
<input id="ern-gewicht" type="number" step="0.1" min="0.5" max="100"
|
||||
value="${_esc(gewichtDefault)}" placeholder="15">
|
||||
value="${UI.escape(gewichtDefault)}" placeholder="15">
|
||||
</div>
|
||||
<div class="ern-field">
|
||||
<label>🎂 Alter (Jahre)</label>
|
||||
<input id="ern-alter" type="number" step="0.5" min="0" max="25"
|
||||
value="${_esc(alterDefault)}" placeholder="3">
|
||||
value="${UI.escape(alterDefault)}" placeholder="3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -209,7 +201,7 @@ window.Page_ernaehrung = (() => {
|
|||
<div class="by-form-group" style="margin:0">
|
||||
<label class="by-label">Marke / Produkt</label>
|
||||
<input id="ern-prof-marke" type="text" class="by-input"
|
||||
value="${_esc(_profil.marke)}" placeholder="z. B. Royal Canin">
|
||||
value="${UI.escape(_profil.marke)}" placeholder="z. B. Royal Canin">
|
||||
</div>
|
||||
<div class="by-form-group" style="margin:0">
|
||||
<label class="by-label">Portionen pro Tag</label>
|
||||
|
|
@ -219,7 +211,7 @@ window.Page_ernaehrung = (() => {
|
|||
<div class="by-form-group" style="margin:0">
|
||||
<label class="by-label">Notizen</label>
|
||||
<textarea id="ern-prof-notizen" class="by-input" rows="2"
|
||||
placeholder="Besonderheiten, Allergien...">${_esc(_profil.notizen)}</textarea>
|
||||
placeholder="Besonderheiten, Allergien...">${UI.escape(_profil.notizen)}</textarea>
|
||||
</div>
|
||||
<button class="btn btn-secondary" id="ern-prof-save-btn">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg>
|
||||
|
|
@ -482,8 +474,8 @@ window.Page_ernaehrung = (() => {
|
|||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<span style="font-size:1.4rem">${item.emoji}</span>
|
||||
<div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm);color:var(--c-text)">${_esc(item.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-danger)">${_esc(item.grund)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm);color:var(--c-text)">${UI.escape(item.name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-danger)">${UI.escape(item.grund)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -511,7 +503,7 @@ window.Page_ernaehrung = (() => {
|
|||
border:1px solid var(--c-border)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#robot"></use></svg>
|
||||
Der KI-Futterberater beantwortet Ernährungsfragen für
|
||||
<strong>${_esc(dog?.name || 'deinen Hund')}</strong>.
|
||||
<strong>${UI.escape(dog?.name || 'deinen Hund')}</strong>.
|
||||
Bei Gesundheitsfragen immer den Tierarzt zurate ziehen.
|
||||
</div>
|
||||
|
||||
|
|
@ -524,8 +516,8 @@ window.Page_ernaehrung = (() => {
|
|||
'Welche Leckerlis sind gesund?',
|
||||
].map(q => `
|
||||
<button class="btn btn-sm btn-secondary ern-ki-vorschlag"
|
||||
data-q="${_esc(q)}"
|
||||
class="text-xs">${_esc(q)}</button>
|
||||
data-q="${UI.escape(q)}"
|
||||
class="text-xs">${UI.escape(q)}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
|
|
@ -577,7 +569,7 @@ window.Page_ernaehrung = (() => {
|
|||
<div style="display:flex;justify-content:flex-end;margin-bottom:var(--space-2)">
|
||||
<div style="background:var(--c-primary);color:#fff;border-radius:var(--radius-md);
|
||||
padding:var(--space-2) var(--space-3);max-width:80%;font-size:var(--text-sm)">
|
||||
${_esc(frage)}
|
||||
${UI.escape(frage)}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
|
@ -617,7 +609,7 @@ window.Page_ernaehrung = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
const antwortHtml = _esc(antwort)
|
||||
const antwortHtml = UI.escape(antwort)
|
||||
.replace(/\n\n/g, '</p><p style="margin:var(--space-1) 0">')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
|
|
@ -746,7 +738,7 @@ window.Page_ernaehrung = (() => {
|
|||
const dl = document.getElementById('vert-futter-datalist');
|
||||
if (!dl) return;
|
||||
const names = [...new Set((list || []).map(e => e.futter_name))];
|
||||
dl.innerHTML = names.map(n => `<option value="${_esc(n)}">`).join('');
|
||||
dl.innerHTML = names.map(n => `<option value="${UI.escape(n)}">`).join('');
|
||||
}).catch(() => {});
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
@ -950,7 +942,7 @@ window.Page_ernaehrung = (() => {
|
|||
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0;color:var(--c-warning,#f59e0b);margin-top:1px">
|
||||
<use href="/icons/phosphor.svg#warning-circle"></use>
|
||||
</svg>
|
||||
<span>${_esc(data.hinweis)}</span>
|
||||
<span>${UI.escape(data.hinweis)}</span>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
|
|
@ -978,7 +970,7 @@ window.Page_ernaehrung = (() => {
|
|||
return `<span style="font-size:10px;font-weight:600;padding:2px 6px;
|
||||
border-radius:999px;border:1px solid ${chipColor};
|
||||
color:${chipColor};white-space:nowrap">
|
||||
${_esc(KAT_LABELS[kat] || kat)} ×${cnt}
|
||||
${UI.escape(KAT_LABELS[kat] || kat)} ×${cnt}
|
||||
</span>`;
|
||||
}).join('');
|
||||
return `
|
||||
|
|
@ -988,17 +980,17 @@ window.Page_ernaehrung = (() => {
|
|||
<div style="min-width:0;flex:1">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);color:var(--c-text);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(f.name)}
|
||||
${UI.escape(f.name)}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
${_esc(TYP_LABELS[f.typ] || f.typ)} · ${f.mahlzeiten} Mahlzeit${f.mahlzeiten !== 1 ? 'en' : ''}
|
||||
${UI.escape(TYP_LABELS[f.typ] || f.typ)} · ${f.mahlzeiten} Mahlzeit${f.mahlzeiten !== 1 ? 'en' : ''}
|
||||
${f.status !== 'neu' ? `· <span style="color:var(--c-success,#22c55e)">+${f.positiv}</span> / <span style="color:var(--c-danger,#ef4444)">-${f.negativ}</span>` : ''}
|
||||
</div>
|
||||
${katChips ? `<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">${katChips}</div>` : ''}
|
||||
</div>
|
||||
<span style="flex-shrink:0;font-size:var(--text-xs);font-weight:700;
|
||||
color:${cfg.color};white-space:nowrap">
|
||||
${_esc(cfg.label)}
|
||||
${UI.escape(cfg.label)}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -1085,9 +1077,9 @@ window.Page_ernaehrung = (() => {
|
|||
<use href="/icons/phosphor.svg#bowl-food"></use>
|
||||
</svg>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(item.futter_name)}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(item.futter_name)}</div>
|
||||
<div class="text-xs-muted">
|
||||
${_esc(item.datum)} ${_esc(item.uhrzeit)}
|
||||
${UI.escape(item.datum)} ${UI.escape(item.uhrzeit)}
|
||||
${item.menge_g ? ` · ${item.menge_g} g` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1112,11 +1104,11 @@ window.Page_ernaehrung = (() => {
|
|||
</svg>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);color:${col}">
|
||||
${_esc(REAK_LABELS[item.reaktion_typ] || item.reaktion_typ)}
|
||||
${UI.escape(REAK_LABELS[item.reaktion_typ] || item.reaktion_typ)}
|
||||
<span style="font-weight:400;color:var(--c-text-muted)">(${item.intensitaet}/5)</span>
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
${_esc(item.datum)} ${_esc(item.uhrzeit)}
|
||||
${UI.escape(item.datum)} ${UI.escape(item.uhrzeit)}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-icon vert-del-reaktion" data-id="${item.id}"
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ window.Page_erste_hilfe = (() => {
|
|||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ window.Page_expenses = (() => {
|
|||
if (dogs.length < 2) return '';
|
||||
const pills = [{ id: null, name: 'Alle' }, ...dogs].map(d => `
|
||||
<button class="exp-dog-pill${_selectedDogId === d.id ? ' active' : ''}" data-dog="${d.id ?? ''}">
|
||||
${d.id ? UI.icon('paw-print') : ''} ${_esc(d.name)}
|
||||
${d.id ? UI.icon('paw-print') : ''} ${UI.escape(d.name)}
|
||||
</button>`).join('');
|
||||
return `<div class="exp-dog-selector" id="exp-dog-selector">${pills}</div>`;
|
||||
}
|
||||
|
|
@ -283,10 +283,10 @@ window.Page_expenses = (() => {
|
|||
const datum = new Date(e.datum + 'T00:00:00')
|
||||
.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
|
||||
const dogBadge = e.dog_name
|
||||
? `<span>${UI.icon('paw-print')} ${_esc(e.dog_name)}</span>`
|
||||
? `<span>${UI.icon('paw-print')} ${UI.escape(e.dog_name)}</span>`
|
||||
: '';
|
||||
const notiz = e.notiz
|
||||
? `<div class="list-item-text">${_esc(e.notiz)}</div>`
|
||||
? `<div class="list-item-text">${UI.escape(e.notiz)}</div>`
|
||||
: '';
|
||||
return `
|
||||
<div class="list-item-card list-item-card--clickable exp-entry" data-id="${e.id}">
|
||||
|
|
@ -376,11 +376,11 @@ window.Page_expenses = (() => {
|
|||
<div class="list-item-meta-badge" style="--meta-color:${k.color}">${UI.icon(k.icon)}</div>
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${k.label}</div>
|
||||
${r.notiz ? `<div class="list-item-text">${_esc(r.notiz)}</div>` : ''}
|
||||
${r.notiz ? `<div class="list-item-text">${UI.escape(r.notiz)}</div>` : ''}
|
||||
<div class="list-item-meta-row">
|
||||
<span>${HAEUFIGKEIT_LABEL[r.haeufigkeit] || r.haeufigkeit}</span>
|
||||
· <span>${UI.icon('calendar')} ${naechste}</span>
|
||||
${r.dog_name ? `· <span>${UI.icon('paw-print')} ${_esc(r.dog_name)}</span>` : ''}
|
||||
${r.dog_name ? `· <span>${UI.icon('paw-print')} ${UI.escape(r.dog_name)}</span>` : ''}
|
||||
${!r.aktiv ? '· <span>Pausiert</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -444,7 +444,7 @@ window.Page_expenses = (() => {
|
|||
].map(k => `<option value="${k.id}" ${r?.kategorie === k.id ? 'selected' : ''}>${k.label}</option>`).join('');
|
||||
|
||||
const dogOptions = (_appState.dogs || []).map(d =>
|
||||
`<option value="${d.id}" ${r?.dog_id === d.id ? 'selected' : ''}>${_esc(d.name)}</option>`
|
||||
`<option value="${d.id}" ${r?.dog_id === d.id ? 'selected' : ''}>${UI.escape(d.name)}</option>`
|
||||
).join('');
|
||||
|
||||
const body = `
|
||||
|
|
@ -480,7 +480,7 @@ window.Page_expenses = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Bezeichnung <span class="text-muted">(optional)</span></label>
|
||||
<input class="form-control" type="text" name="notiz"
|
||||
value="${_esc(r?.notiz || '')}" placeholder="z.B. Haftpflicht Allianz">
|
||||
value="${UI.escape(r?.notiz || '')}" placeholder="z.B. Haftpflicht Allianz">
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
|
|
@ -683,7 +683,7 @@ window.Page_expenses = (() => {
|
|||
|
||||
const defaultDogId = entry?.dog_id ?? _selectedDogId;
|
||||
const dogOptions = (_appState.dogs || []).map(d =>
|
||||
`<option value="${d.id}"${defaultDogId === d.id ? ' selected' : ''}>${_esc(d.name)}</option>`
|
||||
`<option value="${d.id}"${defaultDogId === d.id ? ' selected' : ''}>${UI.escape(d.name)}</option>`
|
||||
).join('');
|
||||
|
||||
// Kategorie-Kacheln statt Dropdown
|
||||
|
|
@ -725,7 +725,7 @@ window.Page_expenses = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Notiz <span class="form-label-hint">(optional)</span></label>
|
||||
<input type="text" name="notiz" class="form-control"
|
||||
value="${_esc(entry?.notiz || '')}"
|
||||
value="${UI.escape(entry?.notiz || '')}"
|
||||
placeholder="z.B. Hundesteuer 2026, Allianz Haftpflicht …">
|
||||
</div>
|
||||
|
||||
|
|
@ -852,14 +852,5 @@ window.Page_expenses = (() => {
|
|||
return Math.round(val) + ' €';
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
return { init, refresh };
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -39,12 +39,7 @@ window.Page_forum = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Helpers
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<')
|
||||
.replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '—';
|
||||
const d = new Date(iso);
|
||||
const now = new Date();
|
||||
|
|
@ -108,7 +103,7 @@ window.Page_forum = (() => {
|
|||
<div class="forum-category-tabs by-tabs" id="forum-tabs">
|
||||
${KATEGORIEN.map(k => `
|
||||
<button class="by-tab ${k.key === _aktivKat ? 'active' : ''}"
|
||||
data-kat="${k.key}"><span class="by-tab-text">${_esc(k.label)}</span></button>
|
||||
data-kat="${k.key}"><span class="by-tab-text">${UI.escape(k.label)}</span></button>
|
||||
`).join('')}
|
||||
<button class="by-tab ${_activeSection === 'map' ? 'active' : ''}"
|
||||
data-section="map"><span class="by-tab-text">${UI.icon('users')} Mitgliederkarte</span></button>
|
||||
|
|
@ -217,7 +212,7 @@ window.Page_forum = (() => {
|
|||
.format(new Date(+year, +month - 1, 1));
|
||||
const top = data.top?.[0];
|
||||
const winnerLine = top
|
||||
? `🥇 ${_esc(top.name)}${top.rasse ? ` · ${_esc(top.rasse)}` : ''}`
|
||||
? `🥇 ${UI.escape(top.name)}${top.rasse ? ` · ${UI.escape(top.rasse)}` : ''}`
|
||||
: 'Noch keine Stimmen';
|
||||
const metaLine = top
|
||||
? `${top.stimmen} Stimme${top.stimmen !== 1 ? 'n' : ''}`
|
||||
|
|
@ -227,7 +222,7 @@ window.Page_forum = (() => {
|
|||
<div class="forum-hdm-tile" id="forum-hdm-tile">
|
||||
<div class="forum-hdm-tile-trophy">🏆</div>
|
||||
<div class="forum-hdm-tile-body">
|
||||
<div class="forum-hdm-tile-title">Hund des Monats · ${_esc(monthName)}</div>
|
||||
<div class="forum-hdm-tile-title">Hund des Monats · ${UI.escape(monthName)}</div>
|
||||
<div class="forum-hdm-tile-winner">${winnerLine}</div>
|
||||
<div class="forum-hdm-tile-meta">${metaLine}</div>
|
||||
</div>
|
||||
|
|
@ -251,16 +246,16 @@ window.Page_forum = (() => {
|
|||
? data.top.slice(0, 5).map((dog, i) => {
|
||||
const medal = ['🥇','🥈','🥉','4️⃣','5️⃣'][i];
|
||||
const av = dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-top-av-img">`
|
||||
: `<span class="hdm-top-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : '';
|
||||
? `<img src="${UI.escape(dog.foto_url)}" alt="${UI.escape(dog.name)}" class="hdm-top-av-img">`
|
||||
: `<span class="hdm-top-av-placeholder">${UI.escape(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? UI.escape(dog.besitzer_name.split(' ')[0]) : '';
|
||||
return `
|
||||
<div class="hdm-top-entry">
|
||||
<span class="hdm-top-medal">${medal}</span>
|
||||
<div class="hdm-top-av">${av}</div>
|
||||
<div class="hdm-top-info">
|
||||
<div class="hdm-top-name">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-top-rasse">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div class="hdm-top-name">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-top-rasse">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
${vorname ? `<div class="hdm-top-besitzer">von ${vorname}</div>` : ''}
|
||||
</div>
|
||||
<div class="hdm-top-stimmen">${dog.stimmen} ${UI.icon('star')}</div>
|
||||
|
|
@ -291,7 +286,7 @@ window.Page_forum = (() => {
|
|||
<div class="hdm-header">
|
||||
<div class="hdm-trophy">🏆</div>
|
||||
<h2 class="hdm-title">Hund des Monats</h2>
|
||||
<div class="hdm-monat">${_esc(monthName)}</div>
|
||||
<div class="hdm-monat">${UI.escape(monthName)}</div>
|
||||
</div>
|
||||
${voteHint}
|
||||
<div class="hdm-section">
|
||||
|
|
@ -320,14 +315,14 @@ window.Page_forum = (() => {
|
|||
grid.innerHTML = list.map(dog => {
|
||||
const isVoted = data.user_vote === dog.id;
|
||||
const av = dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-vote-av-img">`
|
||||
: `<span class="hdm-vote-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : '';
|
||||
? `<img src="${UI.escape(dog.foto_url)}" alt="${UI.escape(dog.name)}" class="hdm-vote-av-img">`
|
||||
: `<span class="hdm-vote-av-placeholder">${UI.escape(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? UI.escape(dog.besitzer_name.split(' ')[0]) : '';
|
||||
return `
|
||||
<div class="hdm-vote-card${isVoted ? ' hdm-vote-card--voted' : ''}">
|
||||
<div class="hdm-vote-av">${av}</div>
|
||||
<div class="hdm-vote-name">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-vote-rasse">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div class="hdm-vote-name">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-vote-rasse">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
${vorname ? `<div class="hdm-vote-besitzer text-xs-muted">von ${vorname}</div>` : ''}
|
||||
${dog.stimmen > 0 ? `<div class="text-xs-muted">${dog.stimmen} ${UI.icon('star')}</div>` : ''}
|
||||
<button class="btn btn-sm ${isVoted ? 'btn-primary' : 'btn-secondary'} hdm-vote-btn"
|
||||
|
|
@ -443,31 +438,31 @@ window.Page_forum = (() => {
|
|||
|
||||
function _threadCardHTML(t) {
|
||||
const preview = t.text_preview
|
||||
? _esc(t.text_preview.slice(0, 120)) + (t.text_preview.length >= 120 ? '…' : '')
|
||||
? UI.escape(t.text_preview.slice(0, 120)) + (t.text_preview.length >= 120 ? '…' : '')
|
||||
: '';
|
||||
const pinBadge = t.is_pinned ? `<span class="forum-pin-badge" title="Angepinnt">${UI.icon('push-pin')}</span>` : '';
|
||||
const lockBadge = t.is_locked ? `<span class="forum-lock-badge" title="Gesperrt">${UI.icon('lock')}</span>` : '';
|
||||
const fotoHtml = t.foto_preview
|
||||
? /\.(mp4|mov|webm|m4v|avi)$/i.test(t.foto_preview)
|
||||
? `<div class="forum-card-thumb forum-card-thumb--video" style="display:flex;align-items:center;justify-content:center;background:var(--c-surface-2)">${UI.icon('video-camera')}</div>`
|
||||
: `<img class="forum-card-thumb" src="${_esc(t.foto_preview_url || t.foto_preview)}"
|
||||
${(t.foto_preview_url && t.foto_preview) ? `srcset="${_esc(t.foto_preview_url)} 800w" sizes="120px"` : ''}
|
||||
: `<img class="forum-card-thumb" src="${UI.escape(t.foto_preview_url || t.foto_preview)}"
|
||||
${(t.foto_preview_url && t.foto_preview) ? `srcset="${UI.escape(t.foto_preview_url)} 800w" sizes="120px"` : ''}
|
||||
alt="" loading="lazy"
|
||||
onerror="this.src='${_esc(t.foto_preview)}'">`
|
||||
onerror="this.src='${UI.escape(t.foto_preview)}'">`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="forum-thread-card" data-id="${t.id}">
|
||||
<div class="forum-card-top">
|
||||
<span class="forum-category-badge forum-category-badge--${_esc(t.kategorie)}">${_esc(t.kategorie)}</span>
|
||||
<span class="forum-category-badge forum-category-badge--${UI.escape(t.kategorie)}">${UI.escape(t.kategorie)}</span>
|
||||
${pinBadge}${lockBadge}
|
||||
</div>
|
||||
<div class="forum-card-content">
|
||||
<div class="forum-card-main">
|
||||
<div class="forum-card-title">${_esc(t.titel)}</div>
|
||||
<div class="forum-card-title">${UI.escape(t.titel)}</div>
|
||||
${preview ? `<div class="forum-card-preview">${preview}</div>` : ''}
|
||||
<div class="forum-card-meta">
|
||||
<span>${UI.icon('user')} ${_esc(t.autor_name || 'Unbekannt')}</span>
|
||||
<span>${UI.icon('user')} ${UI.escape(t.autor_name || 'Unbekannt')}</span>
|
||||
<span>${UI.icon('calendar-dots')} ${_fmtDate(t.created_at)}</span>
|
||||
<span>${UI.icon('chat-circle-dots')} ${t.antworten || 0}</span>
|
||||
<span class="${t.user_liked ? 'forum-liked' : ''}">${UI.icon('heart')} ${t.likes || 0}</span>
|
||||
|
|
@ -493,7 +488,7 @@ window.Page_forum = (() => {
|
|||
document.getElementById('forum-main').innerHTML = `
|
||||
<div style="text-align:center;padding:var(--space-8)">
|
||||
<div style="font-size:2rem;margin-bottom:var(--space-2)">${UI.icon('magnifying-glass')}</div>
|
||||
<p class="text-secondary">Keine Ergebnisse für „${_esc(q)}"</p>
|
||||
<p class="text-secondary">Keine Ergebnisse für „${UI.escape(q)}"</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
|
@ -538,14 +533,14 @@ window.Page_forum = (() => {
|
|||
|
||||
const _forumMediaHtml = (u) => {
|
||||
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>`;
|
||||
return `<a href="${UI.escape(u)}" target="_blank" rel="noopener" class="forum-pdf-card">
|
||||
${UI.icon('file-text')} <span>${UI.escape(u.split('/').pop())}</span></a>`;
|
||||
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
|
||||
return `<video src="${UI.escape(u)}" poster="${UI.escape(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">`;
|
||||
return `<img src="${UI.escape(u)}" class="forum-foto-img" data-src="${UI.escape(u)}" alt="" loading="lazy">`;
|
||||
};
|
||||
const fotoGallery = (thread.foto_urls?.length)
|
||||
? `<div class="forum-foto-grid">${thread.foto_urls.map(_forumMediaHtml).join('')}</div>`
|
||||
|
|
@ -578,20 +573,20 @@ window.Page_forum = (() => {
|
|||
<div class="forum-thread-detail">
|
||||
${modToolbar}
|
||||
<div class="forum-thread-header-row">
|
||||
<span class="forum-category-badge forum-category-badge--${_esc(thread.kategorie)}">${_esc(thread.kategorie)}</span>
|
||||
<span class="forum-category-badge forum-category-badge--${UI.escape(thread.kategorie)}">${UI.escape(thread.kategorie)}</span>
|
||||
<span style="color:var(--c-text-muted);font-size:0.8rem">${_fmtDate(thread.created_at)}</span>
|
||||
${thread.is_pinned ? `<span>${UI.icon('push-pin')}</span>` : ''}
|
||||
${thread.is_locked ? `<span>${UI.icon('lock')}</span>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="forum-thread-body">
|
||||
<p style="white-space:pre-wrap;word-break:break-word">${_esc(thread.text)}</p>
|
||||
<p style="white-space:pre-wrap;word-break:break-word">${UI.escape(thread.text)}</p>
|
||||
${fotoGallery}
|
||||
</div>
|
||||
|
||||
<div class="forum-thread-author-row">
|
||||
<div class="forum-avatar">${_esc(_initial(thread.autor_name))}</div>
|
||||
<span style="font-size:0.85rem;color:var(--c-text-secondary)">${_esc(thread.autor_name || 'Unbekannt')}</span>
|
||||
<div class="forum-avatar">${UI.escape(_initial(thread.autor_name))}</div>
|
||||
<span style="font-size:0.85rem;color:var(--c-text-secondary)">${UI.escape(thread.autor_name || 'Unbekannt')}</span>
|
||||
${thread.autor_founder_number ? `<span style="font-size:10px;font-weight:700;background:#7c3aed;color:#fff;padding:1px 5px;border-radius:4px">Gründer #${thread.autor_founder_number}</span>` : ''}
|
||||
<div style="margin-left:auto;display:flex;gap:var(--space-2);align-items:center">
|
||||
<button class="${likeClass}" id="thread-like-btn" data-count="${thread.likes || 0}">
|
||||
|
|
@ -623,7 +618,7 @@ window.Page_forum = (() => {
|
|||
</div>
|
||||
` : `<button type="button" class="btn btn-primary w-full" id="ft-close">Schließen</button>`;
|
||||
|
||||
UI.modal.open({ title: `${UI.icon('chat-circle-dots')} ${_esc(thread.titel)}`, body, footer });
|
||||
UI.modal.open({ title: `${UI.icon('chat-circle-dots')} ${UI.escape(thread.titel)}`, body, footer });
|
||||
|
||||
// Close
|
||||
document.getElementById('ft-close')?.addEventListener('click', UI.modal.close);
|
||||
|
|
@ -778,7 +773,7 @@ window.Page_forum = (() => {
|
|||
const isOwn = uid && uid === p.user_id;
|
||||
const fotoHtml = (p.foto_urls?.length)
|
||||
? `<div class="forum-foto-grid">${p.foto_urls.map(u =>
|
||||
`<img src="${_esc(u)}" class="forum-foto-img" data-src="${_esc(u)}" alt="" loading="lazy">`
|
||||
`<img src="${UI.escape(u)}" class="forum-foto-img" data-src="${UI.escape(u)}" alt="" loading="lazy">`
|
||||
).join('')}</div>`
|
||||
: '';
|
||||
|
||||
|
|
@ -788,13 +783,13 @@ window.Page_forum = (() => {
|
|||
return `
|
||||
<div class="forum-post" data-post-id="${p.id}" data-user-id="${p.user_id || ''}">
|
||||
<div class="forum-post-header">
|
||||
<div class="forum-avatar forum-avatar--sm">${_esc(_initial(p.autor_name))}</div>
|
||||
<span class="forum-post-author">${_esc(p.autor_name || 'Unbekannt')}</span>
|
||||
<div class="forum-avatar forum-avatar--sm">${UI.escape(_initial(p.autor_name))}</div>
|
||||
<span class="forum-post-author">${UI.escape(p.autor_name || 'Unbekannt')}</span>
|
||||
${p.autor_founder_number ? `<span style="font-size:10px;font-weight:700;background:#7c3aed;color:#fff;padding:1px 5px;border-radius:4px">Gründer #${p.autor_founder_number}</span>` : ''}
|
||||
<span class="forum-post-date">${_fmtDate(p.created_at)}</span>
|
||||
</div>
|
||||
<div class="forum-post-body">
|
||||
<div class="forum-post-text">${_esc(p.text)}</div>
|
||||
<div class="forum-post-text">${UI.escape(p.text)}</div>
|
||||
${fotoHtml}
|
||||
</div>
|
||||
<div class="forum-post-actions">
|
||||
|
|
@ -803,7 +798,7 @@ window.Page_forum = (() => {
|
|||
</button>
|
||||
${(!isOwn && uid) ? `<button class="forum-icon-btn forum-post-report" data-post-id="${p.id}" title="Melden">${UI.icon('flag')}</button>` : ''}
|
||||
<div style="margin-left:auto;display:flex;gap:4px">
|
||||
${isOwn ? `<button class="forum-icon-btn forum-post-edit" data-post-id="${p.id}" data-text="${_esc(p.text || '')}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>` : ''}
|
||||
${isOwn ? `<button class="forum-icon-btn forum-post-edit" data-post-id="${p.id}" data-text="${UI.escape(p.text || '')}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>` : ''}
|
||||
${canDelete ? `<button class="forum-icon-btn forum-icon-btn--danger forum-post-delete" data-post-id="${p.id}" title="Löschen">${UI.icon('trash')}</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -991,7 +986,7 @@ window.Page_forum = (() => {
|
|||
// ----------------------------------------------------------
|
||||
function _showCreateForm() {
|
||||
const katOptions = KATEGORIEN.filter(k => k.key !== 'alle').map(k =>
|
||||
`<option value="${k.key}" ${k.key === _aktivKat ? 'selected' : ''}>${_esc(k.label)}</option>`
|
||||
`<option value="${k.key}" ${k.key === _aktivKat ? 'selected' : ''}>${UI.escape(k.label)}</option>`
|
||||
).join('');
|
||||
|
||||
const body = `
|
||||
|
|
@ -1241,12 +1236,12 @@ window.Page_forum = (() => {
|
|||
background:var(--c-primary);color:#fff;font-size:13px;font-weight:700;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
box-shadow:0 2px 5px rgba(0,0,0,0.35);
|
||||
border:2px solid rgba(255,255,255,0.8)">${_esc((m.vorname||'?')[0].toUpperCase())}</div>`,
|
||||
border:2px solid rgba(255,255,255,0.8)">${UI.escape((m.vorname||'?')[0].toUpperCase())}</div>`,
|
||||
iconSize: [32, 32], iconAnchor: [16, 16],
|
||||
});
|
||||
_clusterGroup.addLayer(
|
||||
L.marker([m.lat, m.lon], { icon })
|
||||
.bindPopup(`<strong>${_esc(m.vorname || '?')}</strong>`)
|
||||
.bindPopup(`<strong>${UI.escape(m.vorname || '?')}</strong>`)
|
||||
);
|
||||
});
|
||||
_map.addLayer(_clusterGroup);
|
||||
|
|
@ -1296,11 +1291,11 @@ window.Page_forum = (() => {
|
|||
${reports.map(r => `
|
||||
<div class="forum-mod-report-item" data-id="${r.id}">
|
||||
<div class="text-sm">
|
||||
<strong>${_esc(r.target_type)} #${r.target_id}</strong>
|
||||
— ${_esc(r.grund)}
|
||||
<strong>${UI.escape(r.target_type)} #${r.target_id}</strong>
|
||||
— ${UI.escape(r.grund)}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
von ${_esc(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
|
||||
von ${UI.escape(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" class="mt-2">
|
||||
${UI.icon('check')} Erledigt
|
||||
|
|
@ -1334,7 +1329,7 @@ window.Page_forum = (() => {
|
|||
title: 'Antwort bearbeiten',
|
||||
body: `<form id="${id}">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" name="text" rows="5" required>${_esc(currentText)}</textarea>
|
||||
<textarea class="form-control" name="text" rows="5" required>${UI.escape(currentText)}</textarea>
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
|
|
@ -1373,11 +1368,11 @@ window.Page_forum = (() => {
|
|||
body: `<form id="${id}">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titel</label>
|
||||
<input class="form-control" name="titel" value="${_esc(thread.titel || '')}" required>
|
||||
<input class="form-control" name="titel" value="${UI.escape(thread.titel || '')}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Text</label>
|
||||
<textarea class="form-control" name="text" rows="5">${_esc(thread.text || '')}</textarea>
|
||||
<textarea class="form-control" name="text" rows="5">${UI.escape(thread.text || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Standort <span class="text-secondary">(optional)</span></label>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ window.Page_friends = (() => {
|
|||
background:var(--c-surface-2);border-radius:var(--radius-md);
|
||||
font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
banyaro.app/#friends?suche=${_esc(encodeURIComponent(myName))}
|
||||
banyaro.app/#friends?suche=${UI.escape(encodeURIComponent(myName))}
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm" id="fr-copy-btn" title="Link kopieren">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#link"></use></svg>
|
||||
|
|
@ -82,7 +82,7 @@ window.Page_friends = (() => {
|
|||
</svg>
|
||||
<input id="fr-search" type="search" autocomplete="off"
|
||||
placeholder="Namen eines Hundebesitzers suchen…"
|
||||
value="${_esc(prefill || '')}"
|
||||
value="${UI.escape(prefill || '')}"
|
||||
style="width:100%;box-sizing:border-box;
|
||||
padding:var(--space-3) var(--space-3) var(--space-3) 2.5rem;
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-lg);
|
||||
|
|
@ -278,19 +278,19 @@ window.Page_friends = (() => {
|
|||
const text = item.text || '';
|
||||
const page = _ACTIVITY_PAGE[item.type] || '';
|
||||
const dogLabel = item.dog_name
|
||||
? `<span class="fr-activity-dog">${_esc(item.dog_name)}</span>`
|
||||
? `<span class="fr-activity-dog">${UI.escape(item.dog_name)}</span>`
|
||||
: '';
|
||||
|
||||
const avatar = item.dog_foto
|
||||
? `<img src="${_esc(item.dog_foto)}" alt="${_esc(item.dog_name || '')}"
|
||||
? `<img src="${UI.escape(item.dog_foto)}" alt="${UI.escape(item.dog_name || '')}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
class="fr-activity-avatar">`
|
||||
: item.avatar_url
|
||||
? `<img src="${_esc(item.avatar_url)}" alt="${_esc(item.user_name)}"
|
||||
? `<img src="${UI.escape(item.avatar_url)}" alt="${UI.escape(item.user_name)}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
class="fr-activity-avatar">`
|
||||
: `<div class="fr-activity-avatar fr-activity-avatar--initial">
|
||||
${_esc((item.user_name || '?')[0].toUpperCase())}
|
||||
${UI.escape((item.user_name || '?')[0].toUpperCase())}
|
||||
</div>`;
|
||||
|
||||
const tag = page ? `button type="button"` : `div`;
|
||||
|
|
@ -303,17 +303,17 @@ window.Page_friends = (() => {
|
|||
${avatar}
|
||||
<div class="fr-activity-icon-badge">
|
||||
<svg class="ph-icon" style="width:10px;height:10px" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${_esc(item.icon)}"></use>
|
||||
<use href="/icons/phosphor.svg#${UI.escape(item.icon)}"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fr-activity-body">
|
||||
<div class="fr-activity-meta">
|
||||
<span class="fr-activity-user">${_esc(item.user_name)}</span>
|
||||
<span class="fr-activity-user">${UI.escape(item.user_name)}</span>
|
||||
${dogLabel}
|
||||
</div>
|
||||
${text ? `<div class="fr-activity-text">${_esc(text)}</div>` : ''}
|
||||
<div class="fr-activity-time">${_esc(ago)}</div>
|
||||
${text ? `<div class="fr-activity-text">${UI.escape(text)}</div>` : ''}
|
||||
<div class="fr-activity-time">${UI.escape(ago)}</div>
|
||||
</div>
|
||||
</${page ? 'button' : 'div'}>
|
||||
`;
|
||||
|
|
@ -353,7 +353,7 @@ window.Page_friends = (() => {
|
|||
<div style="flex:1;min-width:120px">
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text);
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
${_esc(r.requester_name)}
|
||||
${UI.escape(r.requester_name)}
|
||||
</div>
|
||||
${_dogPills(r.dogs, 2)}
|
||||
</div>
|
||||
|
|
@ -392,11 +392,11 @@ window.Page_friends = (() => {
|
|||
display:flex;align-items:center;justify-content:center;
|
||||
font-weight:var(--weight-bold);color:var(--c-text-secondary);
|
||||
font-size:var(--text-sm);flex-shrink:0">
|
||||
${_esc((r.addressee_name || '?')[0].toUpperCase())}
|
||||
${UI.escape((r.addressee_name || '?')[0].toUpperCase())}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text)">${_esc(r.addressee_name)}</div>
|
||||
color:var(--c-text)">${UI.escape(r.addressee_name)}</div>
|
||||
<div class="text-xs-muted">Anfrage ausstehend</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm"
|
||||
|
|
@ -473,9 +473,9 @@ window.Page_friends = (() => {
|
|||
<div class="card fr-card" style="padding:var(--space-4);margin-bottom:var(--space-3);cursor:pointer;
|
||||
transition:box-shadow 0.15s"
|
||||
data-friend-id="${f.friend_id}"
|
||||
data-friend-name="${_esc(f.friend_name)}"
|
||||
data-dogs="${_esc(JSON.stringify(dogs))}"
|
||||
data-profile="${_esc(JSON.stringify(profile))}">
|
||||
data-friend-name="${UI.escape(f.friend_name)}"
|
||||
data-dogs="${UI.escape(JSON.stringify(dogs))}"
|
||||
data-profile="${UI.escape(JSON.stringify(profile))}">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
|
||||
<!-- Avatar (User-Avatar, erstes Hunde-Foto oder Initiale) -->
|
||||
|
|
@ -486,7 +486,7 @@ window.Page_friends = (() => {
|
|||
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;
|
||||
margin-bottom:var(--space-1)">
|
||||
<span style="font-weight:var(--weight-semibold);color:var(--c-text)">
|
||||
${_esc(f.friend_name)}
|
||||
${UI.escape(f.friend_name)}
|
||||
</span>
|
||||
${_erfahrungSpan(f.erfahrung)}
|
||||
</div>
|
||||
|
|
@ -506,7 +506,7 @@ window.Page_friends = (() => {
|
|||
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
|
||||
<button class="btn btn-ghost btn-sm fr-note-btn"
|
||||
data-fr-note-id="${f.friend_id}"
|
||||
data-fr-note-name="${_esc(f.friend_name)}"
|
||||
data-fr-note-name="${UI.escape(f.friend_name)}"
|
||||
title="Notiz"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
|
||||
|
|
@ -539,13 +539,13 @@ window.Page_friends = (() => {
|
|||
padding-top:var(--space-3);border-top:1px solid var(--c-border)">
|
||||
${withPhotos.slice(0, 4).map(d => `
|
||||
<div class="text-center">
|
||||
<img src="${_esc(d.foto_url)}" alt="${_esc(d.name)}"
|
||||
<img src="${UI.escape(d.foto_url)}" alt="${UI.escape(d.name)}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
style="width:44px;height:44px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid var(--c-surface)">
|
||||
<div style="font-size:10px;color:var(--c-text-muted);margin-top:2px;
|
||||
max-width:44px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
${_esc(d.name)}
|
||||
${UI.escape(d.name)}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
|
@ -563,7 +563,7 @@ window.Page_friends = (() => {
|
|||
${dogs.map(d => `
|
||||
<div class="text-center">
|
||||
${d.foto_url
|
||||
? `<img src="${_esc(d.foto_url)}" alt="${_esc(d.name)}"
|
||||
? `<img src="${UI.escape(d.foto_url)}" alt="${UI.escape(d.name)}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
style="width:72px;height:72px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid var(--c-primary);margin-bottom:var(--space-2)">`
|
||||
|
|
@ -573,9 +573,9 @@ window.Page_friends = (() => {
|
|||
font-size:1.75rem;margin:0 auto var(--space-2)">🐕</div>`
|
||||
}
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text)">${_esc(d.name)}</div>
|
||||
color:var(--c-text)">${UI.escape(d.name)}</div>
|
||||
${d.rasse
|
||||
? `<div class="text-xs-secondary">${_esc(d.rasse)}</div>`
|
||||
? `<div class="text-xs-secondary">${UI.escape(d.rasse)}</div>`
|
||||
: ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
|
|
@ -589,7 +589,7 @@ window.Page_friends = (() => {
|
|||
if (profile.wohnort) {
|
||||
parts.push(`<div style="display:flex;align-items:center;gap:var(--space-2);
|
||||
font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
📍 ${_esc(profile.wohnort)}
|
||||
📍 ${UI.escape(profile.wohnort)}
|
||||
</div>`);
|
||||
}
|
||||
if (profile.erfahrung && _erfahrungBadge[profile.erfahrung]) {
|
||||
|
|
@ -600,13 +600,13 @@ window.Page_friends = (() => {
|
|||
if (profile.bio && profile.profil_sichtbarkeit !== 'private') {
|
||||
parts.push(`<div style="font-size:var(--text-sm);color:var(--c-text);
|
||||
line-height:1.5;padding-top:var(--space-2)">
|
||||
${_esc(profile.bio)}
|
||||
${UI.escape(profile.bio)}
|
||||
</div>`);
|
||||
}
|
||||
if (profile.social_link) {
|
||||
parts.push(`<div style="font-size:var(--text-xs);word-break:break-all">
|
||||
<a href="${_esc(profile.social_link)}" target="_blank" rel="noopener noreferrer"
|
||||
class="text-primary">${_esc(profile.social_link)}</a>
|
||||
<a href="${UI.escape(profile.social_link)}" target="_blank" rel="noopener noreferrer"
|
||||
class="text-primary">${UI.escape(profile.social_link)}</a>
|
||||
</div>`);
|
||||
}
|
||||
if (!parts.length) return '';
|
||||
|
|
@ -627,7 +627,7 @@ window.Page_friends = (() => {
|
|||
</div>` : '';
|
||||
|
||||
UI.modal.open({
|
||||
title: _esc(friendName),
|
||||
title: UI.escape(friendName),
|
||||
body: `
|
||||
<div>
|
||||
${badgesHTML}
|
||||
|
|
@ -687,7 +687,7 @@ window.Page_friends = (() => {
|
|||
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:4px;
|
||||
margin-bottom:2px">
|
||||
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text)">${_esc(u.name)}</span>
|
||||
color:var(--c-text)">${UI.escape(u.name)}</span>
|
||||
${u.is_founder ? `<span style="font-size:10px;font-weight:700;background:#7c3aed;color:#fff;padding:1px 5px;border-radius:4px">${u.founder_number ? `Gründer #${u.founder_number}` : 'Gründer'}</span>` : ''}
|
||||
${u.is_partner ? `<span style="font-size:10px;font-weight:700;background:#0ea5e9;color:#fff;padding:1px 5px;border-radius:4px">Partner</span>` : ''}
|
||||
${_erfahrungSpan(u.erfahrung)}
|
||||
|
|
@ -697,12 +697,12 @@ window.Page_friends = (() => {
|
|||
${u.dogs?.length
|
||||
? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
margin-top:2px">
|
||||
${u.dogs.map(d => _esc(d.name) + (d.rasse ? ` · ${_esc(d.rasse)}` : '')).join(' | ')}
|
||||
${u.dogs.map(d => UI.escape(d.name) + (d.rasse ? ` · ${UI.escape(d.rasse)}` : '')).join(' | ')}
|
||||
</div>`
|
||||
: ''}
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm fr-add-btn" title="Anfrage senden"
|
||||
data-user-id="${u.id}" data-user-name="${_esc(u.name)}">
|
||||
data-user-id="${u.id}" data-user-name="${UI.escape(u.name)}">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#user-plus"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -786,13 +786,13 @@ window.Page_friends = (() => {
|
|||
// ----------------------------------------------------------
|
||||
function _userAvatar(name, firstDog, avatarUrl) {
|
||||
if (avatarUrl) {
|
||||
return `<img src="${_esc(avatarUrl)}" alt="${_esc(name)}"
|
||||
return `<img src="${UI.escape(avatarUrl)}" alt="${UI.escape(name)}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
style="width:44px;height:44px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid var(--c-primary);flex-shrink:0">`;
|
||||
}
|
||||
if (firstDog?.foto_url) {
|
||||
return `<img src="${_esc(firstDog.foto_url)}" alt="${_esc(firstDog.name)}"
|
||||
return `<img src="${UI.escape(firstDog.foto_url)}" alt="${UI.escape(firstDog.name)}"
|
||||
loading="lazy" decoding="async" onerror="this.style.display='none'"
|
||||
style="width:44px;height:44px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid var(--c-primary);flex-shrink:0">`;
|
||||
|
|
@ -803,7 +803,7 @@ window.Page_friends = (() => {
|
|||
border:2px solid var(--c-primary);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-weight:var(--weight-bold);color:var(--c-primary)">
|
||||
${_esc((name || '?')[0].toUpperCase())}
|
||||
${UI.escape((name || '?')[0].toUpperCase())}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -823,7 +823,7 @@ window.Page_friends = (() => {
|
|||
|
||||
function _wohnortLine(wohnort) {
|
||||
if (!wohnort) return '';
|
||||
return `<span class="text-xs-muted">📍 ${_esc(wohnort)}</span>`;
|
||||
return `<span class="text-xs-muted">📍 ${UI.escape(wohnort)}</span>`;
|
||||
}
|
||||
|
||||
function _bioLine(bio, sichtbarkeit) {
|
||||
|
|
@ -832,7 +832,7 @@ window.Page_friends = (() => {
|
|||
return `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
margin-top:var(--space-1);line-height:1.4;
|
||||
overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;
|
||||
-webkit-box-orient:vertical">${_esc(text)}</div>`;
|
||||
-webkit-box-orient:vertical">${UI.escape(text)}</div>`;
|
||||
}
|
||||
|
||||
function _dogPills(dogs, max) {
|
||||
|
|
@ -844,7 +844,7 @@ window.Page_friends = (() => {
|
|||
${visible.map(d => `
|
||||
<span style="font-size:10px;padding:1px 6px;border-radius:var(--radius-full);
|
||||
background:var(--c-surface-2);color:var(--c-text-secondary)">
|
||||
🐕 ${_esc(d.name)}${d.rasse ? ` · ${_esc(d.rasse)}` : ''}
|
||||
🐕 ${UI.escape(d.name)}${d.rasse ? ` · ${UI.escape(d.rasse)}` : ''}
|
||||
</span>
|
||||
`).join('')}
|
||||
${rest > 0 ? `<span style="font-size:10px;color:var(--c-text-muted)">+${rest}</span>` : ''}
|
||||
|
|
@ -852,11 +852,6 @@ window.Page_friends = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
return `<div class="empty-state">
|
||||
<svg class="ph-icon empty-state-icon" aria-hidden="true">
|
||||
|
|
@ -886,7 +881,7 @@ window.Page_friends = (() => {
|
|||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ window.Page_gruender = (() => {
|
|||
background:${i === 0 ? 'linear-gradient(135deg,#fef9c3,#fef3c7)' : 'var(--c-surface-2)'}">
|
||||
<div style="font-size:22px;min-width:32px;text-align:center">${medal}</div>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:700;font-size:var(--text-sm)">${_esc(p.label)}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-sm)">${UI.escape(p.label)}</div>
|
||||
<div style="background:var(--c-surface-3,rgba(0,0,0,.08));border-radius:var(--radius-full);
|
||||
height:6px;margin-top:var(--space-1);overflow:hidden">
|
||||
<div style="background:#7c3aed;width:${barPct}%;height:100%;
|
||||
|
|
@ -120,7 +120,7 @@ window.Page_gruender = (() => {
|
|||
background:var(--c-surface-2);display:flex;align-items:center;gap:var(--space-2)">
|
||||
<span style="font-size:var(--text-xs);font-weight:800;color:#7c3aed;min-width:28px">#${f.founder_number}</span>
|
||||
<span style="font-size:var(--text-sm);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
${_esc(f.name)}
|
||||
${UI.escape(f.name)}
|
||||
</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
|
@ -144,10 +144,6 @@ window.Page_gruender = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ window.Page_health = (() => {
|
|||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#wave-sine"></use></svg>
|
||||
<span class="health-transponder-label">Transponder:</span>
|
||||
<span class="health-transponder-nr" id="health-transponder-nr">
|
||||
${dog?.chip_nr ? `<strong>${_esc(dog.chip_nr)}</strong>` : '<em class="text-muted">nicht eingetragen</em>'}
|
||||
${dog?.chip_nr ? `<strong>${UI.escape(dog.chip_nr)}</strong>` : '<em class="text-muted">nicht eingetragen</em>'}
|
||||
</span>
|
||||
<button class="btn btn-link btn-sm health-transponder-edit" id="health-transponder-edit"
|
||||
style="padding:0;font-size:var(--text-xs);color:var(--c-text-muted)">
|
||||
|
|
@ -197,7 +197,7 @@ window.Page_health = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-medium);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(e.bezeichnung)}
|
||||
${UI.escape(e.bezeichnung)}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
${ageLabel} · ${dateStr}
|
||||
|
|
@ -379,19 +379,19 @@ window.Page_health = (() => {
|
|||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="health-card-ampel ampel-${ampel.color}" title="${ampel.label}"></div>
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-title">${UI.escape(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.charge_nr ? ` · Ch.-Nr: ${_esc(e.charge_nr)}` : ''}
|
||||
${e.charge_nr ? ` · Ch.-Nr: ${UI.escape(e.charge_nr)}` : ''}
|
||||
</div>
|
||||
${vetName ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-top:var(--space-1)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(vetName)}</div>` : ''}
|
||||
${vetName ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-top:var(--space-1)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${UI.escape(vetName)}</div>` : ''}
|
||||
${e.naechstes ? `<div class="health-card-next ampel-text-${ampel.color}">
|
||||
Nächste Impfung: ${UI.time.format(e.naechstes + 'T00:00:00')} ${ampel.icon}
|
||||
</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
data-label="${UI.escape(e.bezeichnung)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -426,7 +426,7 @@ window.Page_health = (() => {
|
|||
return `
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-title">${UI.escape(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.kosten != null ? ` · ${Number(e.kosten).toFixed(2)} €` : ''}
|
||||
|
|
@ -434,13 +434,13 @@ window.Page_health = (() => {
|
|||
${praxisName ? `
|
||||
<div style="display:flex;align-items:center;gap:var(--space-1);
|
||||
margin-top:var(--space-1);font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(praxisName)}${praxisOrt ? ` · ${_esc(praxisOrt)}` : ''}
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${UI.escape(praxisName)}${praxisOrt ? ` · ${UI.escape(praxisOrt)}` : ''}
|
||||
</div>` : ''}
|
||||
${e.diagnose ? `<div class="list-item-text"><b>Diagnose:</b> ${_esc(e.diagnose)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.diagnose ? `<div class="list-item-text"><b>Diagnose:</b> ${UI.escape(e.diagnose)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
data-label="${UI.escape(e.bezeichnung)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -489,10 +489,10 @@ window.Page_health = (() => {
|
|||
${e.wert} <span class="text-sm-secondary">${e.einheit || 'kg'}</span>
|
||||
</span>
|
||||
</div>
|
||||
${e.notiz ? `<div class="list-item-text" style="padding-top:var(--space-1)">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text" style="padding-top:var(--space-1)">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="Gewicht ${_esc(e.datum)}"
|
||||
data-label="Gewicht ${UI.escape(e.datum)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -727,10 +727,10 @@ window.Page_health = (() => {
|
|||
${e.wert ? `Dauer: ${e.wert} Tage` : 'Dauer nicht angegeben'}
|
||||
${interval ? ` · Abstand zur Vorherigen: ${interval} Tage` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="Läufigkeit ${_esc(e.datum)}"
|
||||
data-label="Läufigkeit ${UI.escape(e.datum)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -759,16 +759,16 @@ window.Page_health = (() => {
|
|||
${items.map(e => `
|
||||
<div class="list-item-card list-item-card--clickable health-card${e.aktiv ? '' : ' list-item-card--inactive health-card--inactive'}" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-title">${UI.escape(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${e.dosierung ? _esc(e.dosierung) : ''}
|
||||
${e.haeufigkeit ? ` · ${_esc(e.haeufigkeit)}` : ''}
|
||||
${e.dosierung ? UI.escape(e.dosierung) : ''}
|
||||
${e.haeufigkeit ? ` · ${UI.escape(e.haeufigkeit)}` : ''}
|
||||
${e.bis_datum ? ` · bis ${UI.time.format(e.bis_datum + 'T00:00:00')}` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
data-label="${UI.escape(e.bezeichnung)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -799,17 +799,17 @@ window.Page_health = (() => {
|
|||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">
|
||||
${e.schweregrad ? SCHWEREGRAD[e.schweregrad] || '' : ''} ${_esc(e.bezeichnung)}
|
||||
${e.schweregrad ? SCHWEREGRAD[e.schweregrad] || '' : ''} ${UI.escape(e.bezeichnung)}
|
||||
</div>
|
||||
<div class="list-item-meta-row">
|
||||
Erstmals: ${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${e.schweregrad ? ` · Schweregrad: ${_esc(e.schweregrad)}` : ''}
|
||||
${e.schweregrad ? ` · Schweregrad: ${UI.escape(e.schweregrad)}` : ''}
|
||||
</div>
|
||||
${e.reaktion ? `<div class="list-item-text"><b>Reaktion:</b> ${_esc(e.reaktion)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.reaktion ? `<div class="list-item-text"><b>Reaktion:</b> ${UI.escape(e.reaktion)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
data-label="${UI.escape(e.bezeichnung)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -840,29 +840,29 @@ window.Page_health = (() => {
|
|||
return `
|
||||
<div class="list-item-card list-item-card--clickable health-card" data-id="${e.id}" data-action="open-entry">
|
||||
${firstImg
|
||||
? `<img src="${_esc(firstImg.url)}" class="list-item-thumb health-doc-thumb" alt="Vorschau">`
|
||||
? `<img src="${UI.escape(firstImg.url)}" class="list-item-thumb health-doc-thumb" alt="Vorschau">`
|
||||
: `<div style="width:48px;height:48px;display:flex;align-items:center;justify-content:center;
|
||||
font-size:2rem;flex-shrink:0"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg></div>`}
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">${_esc(e.bezeichnung)}</div>
|
||||
<div class="list-item-title">${UI.escape(e.bezeichnung)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${UI.time.format(e.datum + 'T00:00:00')}
|
||||
${count > 1 ? ` · ${count} Dateien` : ''}
|
||||
</div>
|
||||
${e.notiz ? `<div class="list-item-text">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div class="list-item-text">${UI.escape(e.notiz)}</div>` : ''}
|
||||
<button class="btn btn-ghost btn-xs" style="margin-top:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 6px"
|
||||
data-action="open-note" data-entry-id="${e.id}"
|
||||
data-label="${_esc(e.bezeichnung)}"
|
||||
data-label="${UI.escape(e.bezeichnung)}"
|
||||
onclick="event.stopPropagation()"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
|
||||
${count
|
||||
? `<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);align-items:center;flex-wrap:wrap">
|
||||
${mediaList.slice(0, 3).map(m => m.media_type === 'pdf'
|
||||
? `<a href="${_esc(m.url)}" target="_blank" rel="noopener"
|
||||
? `<a href="${UI.escape(m.url)}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm" style="display:inline-flex"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg> PDF
|
||||
</a>`
|
||||
: `<a href="${_esc(m.url)}" target="_blank" rel="noopener"
|
||||
: `<a href="${UI.escape(m.url)}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm" style="display:inline-flex"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#image"></use></svg> Bild
|
||||
|
|
@ -992,12 +992,12 @@ window.Page_health = (() => {
|
|||
const mediaHtml = mediaItems.length
|
||||
? `<div class="health-media-gallery mt-4">
|
||||
${mediaItems.map(m => m.media_type === 'pdf'
|
||||
? `<a href="${_esc(m.url)}" target="_blank" rel="noopener"
|
||||
? `<a href="${UI.escape(m.url)}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm health-media-gallery-pdf">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg> PDF öffnen
|
||||
</a>`
|
||||
: `<a href="${_esc(m.url)}" target="_blank" rel="noopener" class="health-media-gallery-img">
|
||||
<img src="${_esc(m.url)}" alt="Bild" loading="lazy">
|
||||
: `<a href="${UI.escape(m.url)}" target="_blank" rel="noopener" class="health-media-gallery-img">
|
||||
<img src="${UI.escape(m.url)}" alt="Bild" loading="lazy">
|
||||
</a>`
|
||||
).join('')}
|
||||
</div>`
|
||||
|
|
@ -1015,7 +1015,7 @@ window.Page_health = (() => {
|
|||
? `${tabInfo.icon} ${entry.wert} ${entry.einheit || 'kg'}`
|
||||
: entry.typ === 'laeufigkeit'
|
||||
? `${tabInfo.icon} Läufigkeit ${UI.time.format(entry.datum + 'T00:00:00')}`
|
||||
: `${tabInfo.icon} ${_esc(entry.bezeichnung)}`;
|
||||
: `${tabInfo.icon} ${UI.escape(entry.bezeichnung)}`;
|
||||
UI.modal.open({ title: modalTitle, body });
|
||||
|
||||
document.getElementById('health-detail-edit')?.addEventListener('click', () => {
|
||||
|
|
@ -1041,23 +1041,23 @@ window.Page_health = (() => {
|
|||
const praxis = _praxen.find(p => p.id === e.tierarzt_id);
|
||||
if (praxis) {
|
||||
const adresse = [praxis.strasse, [praxis.plz, praxis.ort].filter(Boolean).join(' ')].filter(Boolean).join(', ');
|
||||
const tel = praxis.telefon ? ` · <a href="tel:${_esc(praxis.telefon)}">${_esc(praxis.telefon)}</a>` : '';
|
||||
const oh = praxis.opening_hours ? `<br><small class="text-secondary">🕐 ${_esc(_fmtOeffnungszeiten(praxis.opening_hours))}</small>` : '';
|
||||
rows.push(['Praxis', `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(praxis.name)}${adresse ? `<br><small class="text-secondary">${_esc(adresse)}${tel}</small>` : tel}${oh}`]);
|
||||
const tel = praxis.telefon ? ` · <a href="tel:${UI.escape(praxis.telefon)}">${UI.escape(praxis.telefon)}</a>` : '';
|
||||
const oh = praxis.opening_hours ? `<br><small class="text-secondary">🕐 ${UI.escape(_fmtOeffnungszeiten(praxis.opening_hours))}</small>` : '';
|
||||
rows.push(['Praxis', `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${UI.escape(praxis.name)}${adresse ? `<br><small class="text-secondary">${UI.escape(adresse)}${tel}</small>` : tel}${oh}`]);
|
||||
}
|
||||
} else if (e.tierarzt_name) {
|
||||
rows.push(['Tierarzt', _esc(e.tierarzt_name)]);
|
||||
rows.push(['Tierarzt', UI.escape(e.tierarzt_name)]);
|
||||
}
|
||||
if (e.charge_nr) rows.push(['Charge-Nr.', _esc(e.charge_nr)]);
|
||||
if (e.charge_nr) rows.push(['Charge-Nr.', UI.escape(e.charge_nr)]);
|
||||
if (e.kosten != null) rows.push(['Kosten', `${Number(e.kosten).toFixed(2)} €`]);
|
||||
if (e.diagnose) rows.push(['Diagnose', _esc(e.diagnose)]);
|
||||
if (e.diagnose) rows.push(['Diagnose', UI.escape(e.diagnose)]);
|
||||
if (e.wert) rows.push(['Gewicht', `${e.wert} ${e.einheit || 'kg'}`]);
|
||||
if (e.dosierung) rows.push(['Dosierung', _esc(e.dosierung)]);
|
||||
if (e.haeufigkeit) rows.push(['Häufigkeit', _esc(e.haeufigkeit)]);
|
||||
if (e.dosierung) rows.push(['Dosierung', UI.escape(e.dosierung)]);
|
||||
if (e.haeufigkeit) rows.push(['Häufigkeit', UI.escape(e.haeufigkeit)]);
|
||||
if (e.bis_datum) rows.push(['Bis', UI.time.format(e.bis_datum + 'T00:00:00')]);
|
||||
if (e.schweregrad) rows.push(['Schweregrad',_esc(e.schweregrad)]);
|
||||
if (e.reaktion) rows.push(['Reaktion', _esc(e.reaktion)]);
|
||||
if (e.notiz) rows.push(['Notiz', _esc(e.notiz)]);
|
||||
if (e.schweregrad) rows.push(['Schweregrad',UI.escape(e.schweregrad)]);
|
||||
if (e.reaktion) rows.push(['Reaktion', UI.escape(e.reaktion)]);
|
||||
if (e.notiz) rows.push(['Notiz', UI.escape(e.notiz)]);
|
||||
|
||||
return `<dl class="health-detail-dl">${
|
||||
rows.map(([k, v]) => `<dt>${k}</dt><dd>${v}</dd>`).join('')
|
||||
|
|
@ -1077,7 +1077,7 @@ window.Page_health = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Bezeichnung *</label>
|
||||
<input class="form-control" type="text" name="bezeichnung"
|
||||
value="${_esc(entry?.bezeichnung || '')}" required
|
||||
value="${UI.escape(entry?.bezeichnung || '')}" required
|
||||
placeholder="${_formPlaceholder(t)}">
|
||||
</div>` : ''}
|
||||
<div class="form-group">
|
||||
|
|
@ -1091,7 +1091,7 @@ window.Page_health = (() => {
|
|||
const notizField = `
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notiz</label>
|
||||
<textarea class="form-control" name="notiz" rows="3">${_esc(entry?.notiz || '')}</textarea>
|
||||
<textarea class="form-control" name="notiz" rows="3">${UI.escape(entry?.notiz || '')}</textarea>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -1110,7 +1110,7 @@ window.Page_health = (() => {
|
|||
: '';
|
||||
return `<div class="health-media-thumb" data-media-id="${m.id || ''}">
|
||||
${isImg
|
||||
? `<img src="${_esc(m.url)}" alt="Vorschau">`
|
||||
? `<img src="${UI.escape(m.url)}" alt="Vorschau">`
|
||||
: `<div class="health-media-thumb-pdf"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg><span>PDF</span></div>`}
|
||||
${removeBtn}
|
||||
</div>`;
|
||||
|
|
@ -1174,7 +1174,7 @@ window.Page_health = (() => {
|
|||
const thumb = document.createElement('div');
|
||||
thumb.className = 'health-media-thumb health-media-thumb--pending';
|
||||
if (isPdf) {
|
||||
thumb.innerHTML = `<div class="health-media-thumb-pdf"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg><span>PDF</span></div><small>${_esc(f.name.slice(0, 18))}</small>`;
|
||||
thumb.innerHTML = `<div class="health-media-thumb-pdf"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg><span>PDF</span></div><small>${UI.escape(f.name.slice(0, 18))}</small>`;
|
||||
} else {
|
||||
const img = document.createElement('img');
|
||||
img.src = URL.createObjectURL(f);
|
||||
|
|
@ -1331,7 +1331,7 @@ window.Page_health = (() => {
|
|||
<option value="">– optional –</option>
|
||||
${aktivePraxen.map(p => `
|
||||
<option value="${p.id}" ${entry?.tierarzt_id === p.id ? 'selected' : ''}>
|
||||
${_esc(p.name)}${p.ort ? ` · ${_esc(p.ort)}` : ''}
|
||||
${UI.escape(p.name)}${p.ort ? ` · ${UI.escape(p.ort)}` : ''}
|
||||
</option>`).join('')}
|
||||
</select>
|
||||
</div>`;
|
||||
|
|
@ -1350,7 +1350,7 @@ window.Page_health = (() => {
|
|||
${_praxisSelectField(entry)}
|
||||
<div class="form-group">
|
||||
<label class="form-label">Chargen-Nr.</label>
|
||||
<input class="form-control" type="text" name="charge_nr" value="${_esc(entry?.charge_nr || '')}">
|
||||
<input class="form-control" type="text" name="charge_nr" value="${UI.escape(entry?.charge_nr || '')}">
|
||||
</div>
|
||||
`;
|
||||
case 'entwurmung': return `
|
||||
|
|
@ -1373,7 +1373,7 @@ window.Page_health = (() => {
|
|||
${aktivePraxen.map(p => `
|
||||
<option value="${p.id}"
|
||||
${entry?.tierarzt_id === p.id ? 'selected' : ''}>
|
||||
${_esc(p.name)}${p.ort ? ` · ${_esc(p.ort)}` : ''}
|
||||
${UI.escape(p.name)}${p.ort ? ` · ${UI.escape(p.ort)}` : ''}
|
||||
</option>`).join('')}
|
||||
</select>
|
||||
</div>`
|
||||
|
|
@ -1386,13 +1386,13 @@ window.Page_health = (() => {
|
|||
</div>
|
||||
<label class="form-label mt-2">Tierarzt / Praxis (Freitext)</label>
|
||||
<input class="form-control" name="tierarzt_name"
|
||||
value="${_esc(entry?.tierarzt_name || '')}" placeholder="Dr. Muster">
|
||||
value="${UI.escape(entry?.tierarzt_name || '')}" placeholder="Dr. Muster">
|
||||
</div>`;
|
||||
return `
|
||||
${praxisField}
|
||||
<div class="form-group">
|
||||
<label class="form-label">Diagnose</label>
|
||||
<textarea class="form-control" name="diagnose" rows="2">${_esc(entry?.diagnose || '')}</textarea>
|
||||
<textarea class="form-control" name="diagnose" rows="2">${UI.escape(entry?.diagnose || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Kosten (€)</label>
|
||||
|
|
@ -1416,12 +1416,12 @@ window.Page_health = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Dosierung</label>
|
||||
<input class="form-control" type="text" name="dosierung"
|
||||
value="${_esc(entry?.dosierung || '')}" placeholder="z.B. 1 Tablette">
|
||||
value="${UI.escape(entry?.dosierung || '')}" placeholder="z.B. 1 Tablette">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Häufigkeit</label>
|
||||
<input class="form-control" type="text" name="haeufigkeit"
|
||||
value="${_esc(entry?.haeufigkeit || '')}" placeholder="z.B. täglich, 2x wöchentlich">
|
||||
value="${UI.escape(entry?.haeufigkeit || '')}" placeholder="z.B. täglich, 2x wöchentlich">
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
|
|
@ -1450,7 +1450,7 @@ window.Page_health = (() => {
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Reaktion / Symptome</label>
|
||||
<textarea class="form-control" name="reaktion" rows="2">${_esc(entry?.reaktion || '')}</textarea>
|
||||
<textarea class="form-control" name="reaktion" rows="2">${UI.escape(entry?.reaktion || '')}</textarea>
|
||||
</div>
|
||||
`;
|
||||
case 'laeufigkeit': {
|
||||
|
|
@ -1632,7 +1632,7 @@ window.Page_health = (() => {
|
|||
<div style="font-size:1.5rem"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${p.ist_notfallpraxis ? 'warning' : 'first-aid'}"></use></svg></div>
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-title">
|
||||
${_esc(p.name)}
|
||||
${UI.escape(p.name)}
|
||||
${!p.aktiv ? '<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:400"> · Ehemalig</span>' : ''}
|
||||
</div>
|
||||
${(p.strasse || p.plz || p.ort) ? `
|
||||
|
|
@ -1642,17 +1642,17 @@ window.Page_health = (() => {
|
|||
${p.opening_hours ? `
|
||||
<div class="list-item-meta-row" style="margin-top:var(--space-1)">
|
||||
<svg class="ph-icon" aria-hidden="true" style="font-size:0.9em"><use href="/icons/phosphor.svg#clock"></use></svg>
|
||||
${_esc(_fmtOeffnungszeiten(p.opening_hours))}
|
||||
${UI.escape(_fmtOeffnungszeiten(p.opening_hours))}
|
||||
</div>` : ''}
|
||||
${ratingHtml}
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
|
||||
${p.telefon ? `
|
||||
<a href="tel:${_esc(p.telefon)}" class="btn btn-secondary btn-sm"
|
||||
<a href="tel:${UI.escape(p.telefon)}" class="btn btn-secondary btn-sm"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg> Anrufen
|
||||
</a>` : ''}
|
||||
${p.notfall_telefon ? `
|
||||
<a href="tel:${_esc(p.notfall_telefon)}" class="btn btn-danger btn-sm"
|
||||
<a href="tel:${UI.escape(p.notfall_telefon)}" class="btn btn-danger btn-sm"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg> Notfall
|
||||
</a>` : ''}
|
||||
|
|
@ -1749,7 +1749,7 @@ window.Page_health = (() => {
|
|||
async function _showPraxisDetail(praxis) {
|
||||
// Erst mit Lade-Spinner öffnen, dann Daten laden
|
||||
UI.modal.open({
|
||||
title: _esc(praxis.name),
|
||||
title: UI.escape(praxis.name),
|
||||
body: `<div style="text-align:center;padding:var(--space-6)">
|
||||
<svg class="ph-icon spin" aria-hidden="true" style="font-size:2rem">
|
||||
<use href="/icons/phosphor.svg#spinner-gap"></use>
|
||||
|
|
@ -1804,7 +1804,7 @@ window.Page_health = (() => {
|
|||
${k.freundlichkeit ? `<span>Freundlichkeit: ${_renderStarsReadonly(k.freundlichkeit)}</span>` : ''}
|
||||
${k.kompetenz ? `<span>Kompetenz: ${_renderStarsReadonly(k.kompetenz)}</span>` : ''}
|
||||
</div>` : ''}
|
||||
<p style="margin:0;font-size:var(--text-sm)">${_esc(k.text || '')}</p>
|
||||
<p style="margin:0;font-size:var(--text-sm)">${UI.escape(k.text || '')}</p>
|
||||
</div>`).join('')
|
||||
: `<p class="text-sm-muted">Noch keine Kommentare.</p>`;
|
||||
|
||||
|
|
@ -1863,13 +1863,13 @@ window.Page_health = (() => {
|
|||
<div class="form-group mt-3">
|
||||
<label class="form-label">Kommentar <span style="font-weight:400;color:var(--c-text-muted)">(optional, anonym)</span></label>
|
||||
<textarea class="form-control" name="text" maxlength="500" rows="3"
|
||||
placeholder="Deine Erfahrungen mit dieser Praxis…">${_esc(cur.text || '')}</textarea>
|
||||
placeholder="Deine Erfahrungen mit dieser Praxis…">${UI.escape(cur.text || '')}</textarea>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);text-align:right">max. 500 Zeichen</div>
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${_esc(praxis.name)} bewerten`,
|
||||
title: `${UI.escape(praxis.name)} bewerten`,
|
||||
body,
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
|
||||
|
|
@ -1948,41 +1948,41 @@ window.Page_health = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Name der Praxis *</label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
value="${_esc(praxis?.name || '')}" placeholder="Dr. Muster Tierarztpraxis" required>
|
||||
value="${UI.escape(praxis?.name || '')}" placeholder="Dr. Muster Tierarztpraxis" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Straße & Hausnummer</label>
|
||||
<input class="form-control" type="text" name="strasse"
|
||||
value="${_esc(praxis?.strasse || '')}" placeholder="Musterstraße 1">
|
||||
value="${UI.escape(praxis?.strasse || '')}" placeholder="Musterstraße 1">
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:120px 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">PLZ</label>
|
||||
<input class="form-control" type="text" name="plz" inputmode="numeric"
|
||||
value="${_esc(praxis?.plz || '')}" placeholder="12345">
|
||||
value="${UI.escape(praxis?.plz || '')}" placeholder="12345">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ort</label>
|
||||
<input class="form-control" type="text" name="ort"
|
||||
value="${_esc(praxis?.ort || '')}" placeholder="Musterstadt">
|
||||
value="${UI.escape(praxis?.ort || '')}" placeholder="Musterstadt">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Telefon</label>
|
||||
<input class="form-control" type="tel" name="telefon"
|
||||
value="${_esc(praxis?.telefon || '')}" placeholder="089 123456">
|
||||
value="${UI.escape(praxis?.telefon || '')}" placeholder="089 123456">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notfall-Telefon</label>
|
||||
<input class="form-control" type="tel" name="notfall_telefon"
|
||||
value="${_esc(praxis?.notfall_telefon || '')}" placeholder="089 999999">
|
||||
value="${UI.escape(praxis?.notfall_telefon || '')}" placeholder="089 999999">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input class="form-control" type="email" name="email"
|
||||
value="${_esc(praxis?.email || '')}" placeholder="praxis@beispiel.de">
|
||||
value="${UI.escape(praxis?.email || '')}" placeholder="praxis@beispiel.de">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
|
|
@ -1994,14 +1994,14 @@ window.Page_health = (() => {
|
|||
</label>
|
||||
<input class="form-control" type="text" name="opening_hours"
|
||||
id="praxis-opening-hours"
|
||||
value="${_esc(praxis?.opening_hours || '')}"
|
||||
value="${UI.escape(praxis?.opening_hours || '')}"
|
||||
placeholder="z.B. Mo–Fr 08:00–18:00 · Sa 09:00–13:00">
|
||||
<div id="praxis-osm-results" style="display:none;margin-top:var(--space-2)"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notizen</label>
|
||||
<textarea class="form-control" name="notizen" rows="2"
|
||||
placeholder="Besonderheiten, interne Hinweise…">${_esc(praxis?.notizen || '')}</textarea>
|
||||
placeholder="Besonderheiten, interne Hinweise…">${UI.escape(praxis?.notizen || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
|
|
@ -2043,20 +2043,20 @@ window.Page_health = (() => {
|
|||
resultsEl.innerHTML = hits.map(h => `
|
||||
<div class="health-card mb-2">
|
||||
<div style="cursor:pointer;flex:1"
|
||||
data-osm-id="${_esc(h.osm_id)}"
|
||||
data-name="${_esc(h.name)}"
|
||||
data-oh="${_esc(h.opening_hours || '')}"
|
||||
data-phone="${_esc(h.phone || '')}"
|
||||
data-osm-id="${UI.escape(h.osm_id)}"
|
||||
data-name="${UI.escape(h.name)}"
|
||||
data-oh="${UI.escape(h.opening_hours || '')}"
|
||||
data-phone="${UI.escape(h.phone || '')}"
|
||||
data-action="pick-osm">
|
||||
<div style="font-weight:600">${_esc(h.name)}</div>
|
||||
${h.opening_hours_fmt ? `<div class="text-sm-secondary">${_esc(h.opening_hours_fmt)}</div>` : '<div class="text-sm-muted">Öffnungszeiten unbekannt</div>'}
|
||||
<div style="font-weight:600">${UI.escape(h.name)}</div>
|
||||
${h.opening_hours_fmt ? `<div class="text-sm-secondary">${UI.escape(h.opening_hours_fmt)}</div>` : '<div class="text-sm-muted">Öffnungszeiten unbekannt</div>'}
|
||||
<div class="text-xs-secondary">${h.distanz_km} km entfernt</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm" style="flex-shrink:0;align-self:flex-start"
|
||||
data-action="korrigieren"
|
||||
data-osm-id="${_esc(h.osm_id)}"
|
||||
data-poi-name="${_esc(h.name)}"
|
||||
data-current-oh="${_esc(h.opening_hours || '')}">
|
||||
data-osm-id="${UI.escape(h.osm_id)}"
|
||||
data-poi-name="${UI.escape(h.name)}"
|
||||
data-current-oh="${UI.escape(h.opening_hours || '')}">
|
||||
✏️
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -2199,11 +2199,11 @@ window.Page_health = (() => {
|
|||
tierarzt_sofort:{ badgeClass: 'badge-danger', icon: 'first-aid-kit', label: 'Sofort zum Tierarzt!' },
|
||||
notfall: { badgeClass: 'badge-danger', icon: 'first-aid-kit', label: 'Notfall — sofort zum Tierarzt!' },
|
||||
};
|
||||
const d = DRINGLICHKEIT[result.dringlichkeit] || { badgeClass: 'badge-primary', icon: 'info', label: _esc(result.dringlichkeit) };
|
||||
const d = DRINGLICHKEIT[result.dringlichkeit] || { badgeClass: 'badge-primary', icon: 'info', label: UI.escape(result.dringlichkeit) };
|
||||
|
||||
const hinweiseHtml = (result.hinweise || []).length
|
||||
? `<ul style="margin:var(--space-2) 0 0;padding-left:var(--space-5);font-size:var(--text-sm)">
|
||||
${result.hinweise.map(h => `<li style="margin-bottom:var(--space-1)">${_esc(h)}</li>`).join('')}
|
||||
${result.hinweise.map(h => `<li style="margin-bottom:var(--space-1)">${UI.escape(h)}</li>`).join('')}
|
||||
</ul>`
|
||||
: '';
|
||||
|
||||
|
|
@ -2211,7 +2211,7 @@ window.Page_health = (() => {
|
|||
? `<div style="margin-top:var(--space-3);padding:var(--space-3);
|
||||
background:var(--c-surface-alt,var(--c-surface));
|
||||
border-radius:var(--radius-md);font-size:var(--text-sm)">
|
||||
<strong>Zum Tierarzt wenn:</strong> ${_esc(result.zum_tierarzt_wenn)}
|
||||
<strong>Zum Tierarzt wenn:</strong> ${UI.escape(result.zum_tierarzt_wenn)}
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
|
|
@ -2223,7 +2223,7 @@ window.Page_health = (() => {
|
|||
</span>
|
||||
</div>
|
||||
${result.einschaetzung
|
||||
? `<p style="font-size:var(--text-sm);line-height:1.6;margin:0">${_esc(result.einschaetzung)}</p>`
|
||||
? `<p style="font-size:var(--text-sm);line-height:1.6;margin:0">${UI.escape(result.einschaetzung)}</p>`
|
||||
: ''}
|
||||
${hinweiseHtml}
|
||||
${zumTierarztHtml}
|
||||
|
|
@ -2244,7 +2244,7 @@ window.Page_health = (() => {
|
|||
<div class="mb-3">
|
||||
<label class="form-label">Chip-Nummer (15-stellig)</label>
|
||||
<input id="transponder-input" class="form-control" type="text"
|
||||
value="${_esc(currentNr)}" placeholder="z.B. 276009200123456" maxlength="20">
|
||||
value="${UI.escape(currentNr)}" placeholder="z.B. 276009200123456" maxlength="20">
|
||||
</div>`,
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
|
||||
|
|
@ -2260,7 +2260,7 @@ window.Page_health = (() => {
|
|||
UI.modal.close();
|
||||
const nrEl = _container.querySelector('#health-transponder-nr');
|
||||
if (nrEl) nrEl.innerHTML = nr
|
||||
? `<strong>${_esc(nr)}</strong>`
|
||||
? `<strong>${UI.escape(nr)}</strong>`
|
||||
: '<em class="text-muted">nicht eingetragen</em>';
|
||||
} catch (e) {
|
||||
UI.setLoading(btn, false);
|
||||
|
|
@ -2286,8 +2286,8 @@ window.Page_health = (() => {
|
|||
? new Date(neuester.erstellt_at).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
: '';
|
||||
const preview = neuester.bericht.length > 180
|
||||
? _esc(neuester.bericht.slice(0, 180)) + '…'
|
||||
: _esc(neuester.bericht);
|
||||
? UI.escape(neuester.bericht.slice(0, 180)) + '…'
|
||||
: UI.escape(neuester.bericht);
|
||||
el.innerHTML = `
|
||||
<div class="health-ki-bericht-banner" style="
|
||||
background:var(--c-surface-2,#f7f2eb);
|
||||
|
|
@ -2327,7 +2327,7 @@ window.Page_health = (() => {
|
|||
title: `${UI.icon('star')} KI-Gesundheitsberichte`,
|
||||
body: `${nav}
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);text-align:center;margin-bottom:8px">${fmtDate(b)}</div>
|
||||
<div style="white-space:pre-wrap;line-height:1.7;font-size:var(--text-sm)">${_esc(b.bericht)}</div>`,
|
||||
<div style="white-space:pre-wrap;line-height:1.7;font-size:var(--text-sm)">${UI.escape(b.bericht)}</div>`,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2370,8 +2370,8 @@ window.Page_health = (() => {
|
|||
return `
|
||||
<div class="health-card" style="flex-direction:row;align-items:center;gap:var(--space-3)">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(v.bezeichnung)}</div>
|
||||
<div class="text-xs-secondary">${_esc(v.label)}${v.praxis_name ? ' · ' + _esc(v.praxis_name) : ''}</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(v.bezeichnung)}</div>
|
||||
<div class="text-xs-secondary">${UI.escape(v.label)}${v.praxis_name ? ' · ' + UI.escape(v.praxis_name) : ''}</div>
|
||||
${badge}
|
||||
</div>
|
||||
<div style="text-align:right;flex-shrink:0">
|
||||
|
|
@ -2380,7 +2380,7 @@ window.Page_health = (() => {
|
|||
<div class="text-xs-secondary">${v.uhrzeit_vorschlag} Uhr</div>
|
||||
<button class="btn btn-primary btn-sm" style="margin-top:var(--space-1)"
|
||||
data-action="termin-anlegen"
|
||||
data-v='${_esc(JSON.stringify(v))}'>
|
||||
data-v='${UI.escape(JSON.stringify(v))}'>
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-plus"></use></svg> In Kalender
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -2417,21 +2417,21 @@ window.Page_health = (() => {
|
|||
<form id="termin-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Bezeichnung</label>
|
||||
<input class="form-control" type="text" name="titel" value="${_esc(titel)}" required>
|
||||
<input class="form-control" type="text" name="titel" value="${UI.escape(titel)}" required>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Datum</label>
|
||||
<input class="form-control" type="date" name="datum" value="${_esc(v.datum_vorschlag)}" required>
|
||||
<input class="form-control" type="date" name="datum" value="${UI.escape(v.datum_vorschlag)}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Uhrzeit</label>
|
||||
<input class="form-control" type="time" name="uhrzeit" value="${_esc(v.uhrzeit_vorschlag)}">
|
||||
<input class="form-control" type="time" name="uhrzeit" value="${UI.escape(v.uhrzeit_vorschlag)}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notiz</label>
|
||||
<input class="form-control" type="text" name="beschreibung" value="${_esc(beschreibung)}">
|
||||
<input class="form-control" type="text" name="beschreibung" value="${UI.escape(beschreibung)}">
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
|
|
@ -2490,20 +2490,20 @@ window.Page_health = (() => {
|
|||
</div>
|
||||
<div class="list-item-body flex-1-min">
|
||||
${vet ? `
|
||||
<div class="list-item-title">${_esc(vet.name)}</div>
|
||||
${adresse ? `<div class="list-item-meta-row">${_esc(adresse)}</div>` : ''}
|
||||
<div class="list-item-title">${UI.escape(vet.name)}</div>
|
||||
${adresse ? `<div class="list-item-meta-row">${UI.escape(adresse)}</div>` : ''}
|
||||
${vet.telefon ? `
|
||||
<div class="mt-2">
|
||||
<a href="tel:${_esc(vet.telefon)}" class="btn btn-secondary btn-sm"
|
||||
<a href="tel:${UI.escape(vet.telefon)}" class="btn btn-secondary btn-sm"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg> ${_esc(vet.telefon)}
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg> ${UI.escape(vet.telefon)}
|
||||
</a>
|
||||
</div>` : ''}
|
||||
${vet.notfall_telefon ? `
|
||||
<div style="margin-top:var(--space-1)">
|
||||
<a href="tel:${_esc(vet.notfall_telefon)}" class="btn btn-danger btn-sm"
|
||||
<a href="tel:${UI.escape(vet.notfall_telefon)}" class="btn btn-danger btn-sm"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg> Notfall: ${_esc(vet.notfall_telefon)}
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg> Notfall: ${UI.escape(vet.notfall_telefon)}
|
||||
</a>
|
||||
</div>` : ''}
|
||||
` : `
|
||||
|
|
@ -2581,17 +2581,17 @@ window.Page_health = (() => {
|
|||
return `
|
||||
<div class="list-item-card health-card" style="align-items:flex-start">
|
||||
<div style="font-size:1.4rem;flex-shrink:0;color:var(--c-primary)">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${_esc(icon)}"></use></svg>
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${UI.escape(icon)}"></use></svg>
|
||||
</div>
|
||||
<div class="list-item-body flex-1-min">
|
||||
<div class="list-item-title">${_esc(doc.titel)}</div>
|
||||
<div class="list-item-title">${UI.escape(doc.titel)}</div>
|
||||
<div class="list-item-meta-row">
|
||||
${_esc(label)}${datum ? ' · ' + datum : ''}
|
||||
${doc.vet_name ? ' · <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ' + _esc(doc.vet_name) : ''}
|
||||
${UI.escape(label)}${datum ? ' · ' + datum : ''}
|
||||
${doc.vet_name ? ' · <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ' + UI.escape(doc.vet_name) : ''}
|
||||
</div>
|
||||
${doc.beschreibung ? `<div class="list-item-text">${_esc(doc.beschreibung)}</div>` : ''}
|
||||
${doc.beschreibung ? `<div class="list-item-text">${UI.escape(doc.beschreibung)}</div>` : ''}
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
|
||||
<a href="${_esc(doc.file_path)}" target="_blank" rel="noopener"
|
||||
<a href="${UI.escape(doc.file_path)}" target="_blank" rel="noopener"
|
||||
class="btn btn-secondary btn-sm" onclick="event.stopPropagation()">
|
||||
${isImg
|
||||
? '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#image"></use></svg> Bild öffnen'
|
||||
|
|
@ -2680,7 +2680,7 @@ window.Page_health = (() => {
|
|||
<select class="form-control" name="vet_id">
|
||||
<option value="">– optional –</option>
|
||||
${aktivePraxen.map(p =>
|
||||
`<option value="${p.id}">${_esc(p.name)}${p.ort ? ' · ' + _esc(p.ort) : ''}</option>`
|
||||
`<option value="${p.id}">${UI.escape(p.name)}${p.ort ? ' · ' + UI.escape(p.ort) : ''}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>` : ''}
|
||||
|
|
@ -2776,7 +2776,7 @@ window.Page_health = (() => {
|
|||
else if (res.saved_count !== undefined) UI.toast.info(`${res.saved_count} Bericht(e) in DB`, { duration: 8000 });
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('star')} KI-Gesundheitsbericht`,
|
||||
body: `<div style="white-space:pre-wrap;line-height:1.7;font-size:var(--text-sm)">${_esc(zusammenfassung)}</div>`,
|
||||
body: `<div style="white-space:pre-wrap;line-height:1.7;font-size:var(--text-sm)">${UI.escape(zusammenfassung)}</div>`,
|
||||
});
|
||||
// Berichte-Liste nach Generierung frisch laden (Cache-Buster)
|
||||
_loadKiBerichte(_appState.activeDog.id, true);
|
||||
|
|
@ -2796,32 +2796,25 @@ window.Page_health = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _showPoiKorrekturModal(osmId, poiName, currentOh) {
|
||||
function _showPoiKorrekturModal(osmId, poiName, currentOh) {
|
||||
UI.modal.open({
|
||||
title: 'Öffnungszeiten korrigieren',
|
||||
body: `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-3)">
|
||||
Korrektur für <strong>${_esc(poiName)}</strong>.<br>
|
||||
Korrektur für <strong>${UI.escape(poiName)}</strong>.<br>
|
||||
Dein Vorschlag wird von einem Moderator geprüft und dann für alle übernommen.
|
||||
</p>
|
||||
<form id="poi-korrektur-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Aktuelle Angabe</label>
|
||||
<input class="form-control" type="text" value="${_esc(currentOh)}" disabled
|
||||
<input class="form-control" type="text" value="${UI.escape(currentOh)}" disabled
|
||||
class="text-muted">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Korrekte Öffnungszeiten *</label>
|
||||
<input class="form-control" type="text" name="new_value" required
|
||||
placeholder="z.B. Mo–Fr 08:00–18:00 · Sa 09:00–13:00"
|
||||
value="${_esc(currentOh)}">
|
||||
value="${UI.escape(currentOh)}">
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
|
|
@ -2872,7 +2865,7 @@ window.Page_health = (() => {
|
|||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
|
|
@ -2956,7 +2949,7 @@ window.Page_health = (() => {
|
|||
</p>
|
||||
<div class="form-group">
|
||||
<textarea id="ki-tierarzt-symptom" class="form-control" rows="4"
|
||||
placeholder="${_esc(placeholder)}"></textarea>
|
||||
placeholder="${UI.escape(placeholder)}"></textarea>
|
||||
</div>
|
||||
<div id="ki-tierarzt-result" class="hidden"></div>
|
||||
<div style="margin-top:var(--space-3);padding:var(--space-3);
|
||||
|
|
@ -3018,7 +3011,7 @@ window.Page_health = (() => {
|
|||
return;
|
||||
}
|
||||
|
||||
const antwortHtml = _esc(result.antwort)
|
||||
const antwortHtml = UI.escape(result.antwort)
|
||||
.replace(/\n\n/g, '</p><p style="margin:var(--space-2) 0">')
|
||||
.replace(/\n/g, '<br>');
|
||||
const restHtml = result.limit - result.anfragen_heute > 0
|
||||
|
|
@ -3092,7 +3085,7 @@ window.Page_health = (() => {
|
|||
<use href="/icons/phosphor.svg#bell-ringing"></use>
|
||||
</svg>
|
||||
<div class="flex-1-min">
|
||||
<span style="font-weight:600;font-size:var(--text-sm);color:var(--c-text)">${_esc(r.bezeichnung)}</span>
|
||||
<span style="font-weight:600;font-size:var(--text-sm);color:var(--c-text)">${UI.escape(r.bezeichnung)}</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-left:var(--space-1)">${TYPE_LABEL[r.typ] || r.typ}</span>
|
||||
</div>
|
||||
<span style="font-size:var(--text-xs);font-weight:600;color:${color};white-space:nowrap">${label}</span>
|
||||
|
|
@ -3122,8 +3115,8 @@ window.Page_health = (() => {
|
|||
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)" data-ins-id="${p.id}">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-2)">
|
||||
<div>
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${_esc(p.anbieter)}</div>
|
||||
${p.police_nr ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">Police: ${_esc(p.police_nr)}</div>` : ''}
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${UI.escape(p.anbieter)}</div>
|
||||
${p.police_nr ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">Police: ${UI.escape(p.police_nr)}</div>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-1)">
|
||||
<button class="btn btn-ghost btn-sm ins-edit-btn" data-id="${p.id}" style="padding:4px 8px">
|
||||
|
|
@ -3137,8 +3130,8 @@ window.Page_health = (() => {
|
|||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);margin-top:var(--space-3);font-size:var(--text-sm)">
|
||||
<div><span class="text-secondary">Jahresbeitrag</span><br><strong>${_fmtEur(p.jahresbeitrag)}</strong></div>
|
||||
<div><span class="text-secondary">Läuft ab</span><br><strong>${_fmtDate(p.ablaufdatum)}</strong></div>
|
||||
${p.kontakt ? `<div style="grid-column:1/-1"><span class="text-secondary">Kontakt</span><br>${_esc(p.kontakt)}</div>` : ''}
|
||||
${p.notizen ? `<div style="grid-column:1/-1"><span class="text-secondary">Notizen</span><br>${_esc(p.notizen)}</div>` : ''}
|
||||
${p.kontakt ? `<div style="grid-column:1/-1"><span class="text-secondary">Kontakt</span><br>${UI.escape(p.kontakt)}</div>` : ''}
|
||||
${p.notizen ? `<div style="grid-column:1/-1"><span class="text-secondary">Notizen</span><br>${UI.escape(p.notizen)}</div>` : ''}
|
||||
</div>
|
||||
</div>`).join('') : `
|
||||
<div style="text-align:center;padding:var(--space-6);color:var(--c-text-muted)">
|
||||
|
|
@ -3172,10 +3165,10 @@ window.Page_health = (() => {
|
|||
const id = `ins-form-${Date.now()}`;
|
||||
const body = `<form id="${id}">
|
||||
<div class="by-form-group"><label class="by-label">Anbieter *</label>
|
||||
<input type="text" name="anbieter" class="form-control by-input" value="${_esc(existing?.anbieter||'')}" required placeholder="z. B. HUK-Coburg">
|
||||
<input type="text" name="anbieter" class="form-control by-input" value="${UI.escape(existing?.anbieter||'')}" required placeholder="z. B. HUK-Coburg">
|
||||
</div>
|
||||
<div class="by-form-group"><label class="by-label">Police-Nr.</label>
|
||||
<input type="text" name="police_nr" class="form-control by-input" value="${_esc(existing?.police_nr||'')}" placeholder="optional">
|
||||
<input type="text" name="police_nr" class="form-control by-input" value="${UI.escape(existing?.police_nr||'')}" placeholder="optional">
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="by-form-group"><label class="by-label">Jahresbeitrag (€)</label>
|
||||
|
|
@ -3186,10 +3179,10 @@ window.Page_health = (() => {
|
|||
</div>
|
||||
</div>
|
||||
<div class="by-form-group"><label class="by-label">Kontakt / Telefon</label>
|
||||
<input type="text" name="kontakt" class="form-control by-input" value="${_esc(existing?.kontakt||'')}" placeholder="optional">
|
||||
<input type="text" name="kontakt" class="form-control by-input" value="${UI.escape(existing?.kontakt||'')}" placeholder="optional">
|
||||
</div>
|
||||
<div class="by-form-group"><label class="by-label">Notizen</label>
|
||||
<textarea name="notizen" class="form-control by-input" rows="2">${_esc(existing?.notizen||'')}</textarea>
|
||||
<textarea name="notizen" class="form-control by-input" rows="2">${UI.escape(existing?.notizen||'')}</textarea>
|
||||
</div>
|
||||
</form>`;
|
||||
const footer = `
|
||||
|
|
@ -3271,12 +3264,12 @@ window.Page_health = (() => {
|
|||
<div style="width:3px;border-radius:2px;background:${color};align-self:stretch;flex-shrink:0"></div>
|
||||
<div class="flex-1-min">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
|
||||
<span style="font-weight:700;font-size:var(--text-sm);color:${color}">${_esc(katLabel)}</span>
|
||||
${trigLabel ? `<span style="font-size:var(--text-xs);background:var(--c-surface-2);padding:1px 6px;border-radius:100px;color:var(--c-text-secondary)">${_esc(trigLabel)}</span>` : ''}
|
||||
<span style="font-weight:700;font-size:var(--text-sm);color:${color}">${UI.escape(katLabel)}</span>
|
||||
${trigLabel ? `<span style="font-size:var(--text-xs);background:var(--c-surface-2);padding:1px 6px;border-radius:100px;color:var(--c-text-secondary)">${UI.escape(trigLabel)}</span>` : ''}
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);margin-left:auto">${fmtDate(e.datum)}${e.uhrzeit ? ' ' + e.uhrzeit : ''}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:3px;margin-top:4px">${dots}</div>
|
||||
${e.notiz ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">${_esc(e.notiz)}</div>` : ''}
|
||||
${e.notiz ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">${UI.escape(e.notiz)}</div>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm beh-del-btn" data-id="${e.id}" style="padding:4px 6px;color:var(--c-danger);flex-shrink:0">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ window.Page_hilfe = (() => {
|
|||
</p>
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin:0">
|
||||
${_search
|
||||
? `Zu "${_esc(_search)}" wurde nichts gefunden.`
|
||||
? `Zu "${UI.escape(_search)}" wurde nichts gefunden.`
|
||||
: 'Noch keine FAQ-Artikel vorhanden.'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -136,7 +136,7 @@ window.Page_hilfe = (() => {
|
|||
color:var(--c-text-secondary);text-transform:uppercase;
|
||||
letter-spacing:0.08em;padding:var(--space-1) 0 var(--space-2);
|
||||
margin-bottom:var(--space-1)">
|
||||
${_esc(label)}
|
||||
${UI.escape(label)}
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-1)">
|
||||
`;
|
||||
|
|
@ -148,12 +148,12 @@ window.Page_hilfe = (() => {
|
|||
// Highlight Suchtreffer in der Frage
|
||||
const frageHtml = _search
|
||||
? _highlight(a.frage, _search)
|
||||
: _esc(a.frage);
|
||||
: UI.escape(a.frage);
|
||||
|
||||
// Antwort: Zeilenumbrüche in <br> wandeln
|
||||
const antwortHtml = _search
|
||||
? _highlight(a.antwort, _search).replace(/\n/g, '<br>')
|
||||
: _esc(a.antwort).replace(/\n/g, '<br>');
|
||||
: UI.escape(a.antwort).replace(/\n/g, '<br>');
|
||||
|
||||
// Bei aktiver Suche: Antwort gleich aufgeklappt
|
||||
const openByDefault = !!_search;
|
||||
|
|
@ -222,20 +222,12 @@ window.Page_hilfe = (() => {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _highlight(text, term) {
|
||||
if (!term) return text;
|
||||
const safe = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const re = new RegExp(`(${safe})`, 'gi');
|
||||
return _esc(text).replace(re,
|
||||
return UI.escape(text).replace(re,
|
||||
'<mark style="background:var(--c-warning-bg,#fef3c7);color:inherit;border-radius:2px">$1</mark>'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ window.Page_jobs = (() => {
|
|||
let _container = null;
|
||||
let _appState = null;
|
||||
|
||||
const _esc = s => UI.escape(s ?? '');
|
||||
const _ph = (name, size = 22) =>
|
||||
`<svg class="ph-icon" aria-hidden="true" style="width:${size}px;height:${size}px;flex-shrink:0;color:var(--c-primary)"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
|
||||
|
||||
|
|
@ -130,7 +129,7 @@ window.Page_jobs = (() => {
|
|||
</div>
|
||||
${app.admin_note ? `<div style="margin-top:var(--space-3);background:var(--c-surface-2);
|
||||
border-radius:var(--radius-md);padding:var(--space-3);font-size:var(--text-sm);
|
||||
color:var(--c-text-secondary);text-align:left">${_esc(app.admin_note)}</div>` : ''}
|
||||
color:var(--c-text-secondary);text-align:left">${UI.escape(app.admin_note)}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -147,13 +146,13 @@ window.Page_jobs = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Dein Name *</label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
value="${u ? _esc(u.name) : ''}" placeholder="Vorname oder Nickname" required>
|
||||
value="${u ? UI.escape(u.name) : ''}" placeholder="Vorname oder Nickname" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">E-Mail *</label>
|
||||
<input class="form-control" type="email" name="email"
|
||||
value="${u ? _esc(u.email || '') : ''}" placeholder="deine@email.de" required>
|
||||
value="${u ? UI.escape(u.email || '') : ''}" placeholder="deine@email.de" required>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:var(--space-3)">
|
||||
|
|
|
|||
|
|
@ -135,11 +135,11 @@ window.Page_knigge = (() => {
|
|||
const cards = BEGEGNUNGEN.map((b, i) => `
|
||||
<div class="knigge-accordion" id="acc-${i}">
|
||||
<button class="knigge-accordion-head" data-acc="${i}" aria-expanded="false">
|
||||
<span>${b.icon} <strong>${_esc(b.titel)}</strong></span>
|
||||
<span>${b.icon} <strong>${UI.escape(b.titel)}</strong></span>
|
||||
<span class="knigge-accordion-arrow">${UI.icon('caret-down')}</span>
|
||||
</button>
|
||||
<div class="knigge-accordion-body" id="acc-body-${i}" hidden>
|
||||
<p style="color:var(--c-text);line-height:1.6">${_esc(b.tipps)}</p>
|
||||
<p style="color:var(--c-text);line-height:1.6">${UI.escape(b.tipps)}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
|
@ -175,14 +175,14 @@ window.Page_knigge = (() => {
|
|||
const cards = SZENARIEN.map(s => `
|
||||
<div class="card mb-4" id="sz-${s.id}">
|
||||
<p style="font-weight:var(--weight-semibold);margin:0;padding:var(--space-5) var(--space-5) var(--space-3);line-height:1.5">
|
||||
${_esc(s.frage)}
|
||||
${UI.escape(s.frage)}
|
||||
</p>
|
||||
<div class="knigge-vote-options" id="opts-${s.id}" style="padding:0 var(--space-5) var(--space-5)">
|
||||
${s.antworten.map(a => `
|
||||
<button class="knigge-vote-btn btn btn-secondary"
|
||||
data-sz="${s.id}" data-key="${a.key}"
|
||||
style="width:100%;margin-bottom:var(--space-2);justify-content:flex-start;text-align:left">
|
||||
${_esc(a.text)}
|
||||
${UI.escape(a.text)}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
|
@ -263,7 +263,7 @@ window.Page_knigge = (() => {
|
|||
<div class="mb-3">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:var(--text-sm)">
|
||||
<span style="color:${isU ? 'var(--c-text)' : 'var(--c-text-secondary)'};font-weight:${isU ? 'var(--weight-semibold)' : 'normal'}">
|
||||
${isU ? UI.icon('arrow-right') + ' ' : ''}${_esc(a.text)}${isR ? ' ' + UI.icon('check') : ''}
|
||||
${isU ? UI.icon('arrow-right') + ' ' : ''}${UI.escape(a.text)}${isR ? ' ' + UI.icon('check') : ''}
|
||||
</span>
|
||||
<span class="text-secondary">${pct}% (${cnt})</span>
|
||||
</div>
|
||||
|
|
@ -282,7 +282,7 @@ window.Page_knigge = (() => {
|
|||
<div style="margin-bottom:var(--space-4);padding:0 var(--space-5)">${bars}</div>
|
||||
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-3) var(--space-5);font-size:var(--text-sm);line-height:1.5">
|
||||
${badge}
|
||||
<span class="text-secondary">${_esc(szenario.erklaerung)}</span>
|
||||
<span class="text-secondary">${UI.escape(szenario.erklaerung)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -336,7 +336,7 @@ window.Page_knigge = (() => {
|
|||
padding:var(--space-4);line-height:1.6;color:var(--c-text)">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text-secondary);margin-bottom:var(--space-2)">${UI.icon('robot')} KI-Rat</div>
|
||||
${_esc(data.rat)}
|
||||
${UI.escape(data.rat)}
|
||||
</div>
|
||||
`;
|
||||
result.style.display = 'block';
|
||||
|
|
@ -347,7 +347,7 @@ window.Page_knigge = (() => {
|
|||
padding:var(--space-4);color:var(--c-text-secondary);font-size:var(--text-sm)">
|
||||
${is402
|
||||
? 'Für KI-Rat wird Ban Yaro Plus oder ein laufender KI-Server benötigt.'
|
||||
: _esc(err.message || 'Fehler beim KI-Abruf.')}
|
||||
: UI.escape(err.message || 'Fehler beim KI-Abruf.')}
|
||||
</div>
|
||||
`;
|
||||
result.style.display = 'block';
|
||||
|
|
@ -400,16 +400,7 @@ window.Page_knigge = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
return { init, refresh };
|
||||
|
|
|
|||
|
|
@ -19,15 +19,11 @@ window.Page_litters = (() => {
|
|||
return `
|
||||
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
|
||||
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon(icon)}</div>
|
||||
<h3 style="margin:0 0 var(--space-2)">${_esc(title)}</h3>
|
||||
<p style="color:var(--c-text-secondary);margin:0">${_esc(text)}</p>
|
||||
<h3 style="margin:0 0 var(--space-2)">${UI.escape(title)}</h3>
|
||||
<p style="color:var(--c-text-secondary);margin:0">${UI.escape(text)}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return UI.escape ? UI.escape(s || '') : (s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
function _statusBadge(status) {
|
||||
const map = {
|
||||
|
|
@ -37,7 +33,7 @@ window.Page_litters = (() => {
|
|||
abgeschlossen: { label: 'Abgeschlossen', cls: 'badge-muted' },
|
||||
};
|
||||
const s = map[status] || { label: status, cls: 'badge-muted' };
|
||||
return `<span class="badge ${s.cls}">${_esc(s.label)}</span>`;
|
||||
return `<span class="badge ${s.cls}">${UI.escape(s.label)}</span>`;
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
|
|
@ -59,7 +55,7 @@ window.Page_litters = (() => {
|
|||
abgegeben: { label: 'Abgegeben', cls: 'badge-muted' },
|
||||
};
|
||||
const s = map[status] || { label: status, cls: 'badge-muted' };
|
||||
return `<span class="badge badge-sm ${s.cls}">${_esc(s.label)}</span>`;
|
||||
return `<span class="badge badge-sm ${s.cls}">${UI.escape(s.label)}</span>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -101,7 +97,7 @@ window.Page_litters = (() => {
|
|||
const zwinger = _breederInfo?.zwingername || 'Mein Zwinger';
|
||||
const logoUrl = _breederInfo?.logo_url || null;
|
||||
const logoHtml = logoUrl
|
||||
? `<img src="${_esc(logoUrl)}" alt="Logo"
|
||||
? `<img src="${UI.escape(logoUrl)}" alt="Logo"
|
||||
style="width:48px;height:48px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid rgba(196,132,58,.5);flex-shrink:0"
|
||||
onerror="this.style.display='none'">`
|
||||
|
|
@ -121,7 +117,7 @@ window.Page_litters = (() => {
|
|||
<div class="flex-1-min">
|
||||
<h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
|
||||
color:var(--c-text);white-space:nowrap;overflow:hidden;
|
||||
text-overflow:ellipsis;line-height:1.2">${_esc(zwinger)}</h2>
|
||||
text-overflow:ellipsis;line-height:1.2">${UI.escape(zwinger)}</h2>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<svg style="width:11px;height:11px;color:var(--c-primary);flex-shrink:0" viewBox="0 0 256 256">
|
||||
<use href="/icons/phosphor.svg#lock-key"></use>
|
||||
|
|
@ -315,7 +311,7 @@ window.Page_litters = (() => {
|
|||
function _litterCardHTML(l) {
|
||||
const verfuegbar = l.welpen_verfuegbar != null ? l.welpen_verfuegbar : '?';
|
||||
const gesamt = l.welpen_gesamt != null ? l.welpen_gesamt : '?';
|
||||
const elternLabel = [l.vater_name, l.mutter_name].filter(Boolean).map(n => _esc(n)).join(' × ') || '—';
|
||||
const elternLabel = [l.vater_name, l.mutter_name].filter(Boolean).map(n => UI.escape(n)).join(' × ') || '—';
|
||||
|
||||
// Datum + Countdown
|
||||
let datumChip = '';
|
||||
|
|
@ -341,7 +337,7 @@ window.Page_litters = (() => {
|
|||
const welpenChip = `<span style="display:inline-flex;align-items:center;gap:3px;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.icon('dog')} ${verfuegbar}/${gesamt} verfügbar</span>`;
|
||||
|
||||
const preisChip = l.preis_spanne
|
||||
? `<span style="display:inline-flex;align-items:center;gap:3px;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.icon('currency-eur')} ${_esc(l.preis_spanne)}</span>`
|
||||
? `<span style="display:inline-flex;align-items:center;gap:3px;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.icon('currency-eur')} ${UI.escape(l.preis_spanne)}</span>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
|
|
@ -355,8 +351,8 @@ window.Page_litters = (() => {
|
|||
<div style="min-width:0">
|
||||
${(l.wurf_rang || l.wurf_name) ? `
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
|
||||
${l.wurf_rang ? `<span style="background:var(--c-primary);color:white;border-radius:999px;padding:1px 10px;font-size:var(--text-xs);font-weight:700">${_esc(l.wurf_rang)}-Wurf</span>` : ''}
|
||||
${l.wurf_name ? `<span style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${_esc(l.wurf_name)}</span>` : ''}
|
||||
${l.wurf_rang ? `<span style="background:var(--c-primary);color:white;border-radius:999px;padding:1px 10px;font-size:var(--text-xs);font-weight:700">${UI.escape(l.wurf_rang)}-Wurf</span>` : ''}
|
||||
${l.wurf_name ? `<span style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${UI.escape(l.wurf_name)}</span>` : ''}
|
||||
</div>` : ''}
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-2)">
|
||||
<span class="text-sm-secondary">${elternLabel}</span>
|
||||
|
|
@ -395,7 +391,7 @@ window.Page_litters = (() => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
${l.beschreibung ? `<p style="margin-top:var(--space-2);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.5">${_esc(l.beschreibung)}</p>` : ''}
|
||||
${l.beschreibung ? `<p style="margin-top:var(--space-2);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.5">${UI.escape(l.beschreibung)}</p>` : ''}
|
||||
</div>
|
||||
|
||||
<!-- Welpen-Bereich -->
|
||||
|
|
@ -455,7 +451,7 @@ window.Page_litters = (() => {
|
|||
const puppies = await API.litters.puppies(litterId);
|
||||
_renderPuppies(inner, litterId, puppies);
|
||||
} catch (err) {
|
||||
inner.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${_esc(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
inner.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${UI.escape(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,8 +465,8 @@ window.Page_litters = (() => {
|
|||
<div class="litters-puppy-row" data-puppy-id="${p.id}">
|
||||
<div class="litters-puppy-info">
|
||||
${_genderIcon(p.geschlecht)}
|
||||
<span class="litters-puppy-name">${p.name ? _esc(p.name) : '<em class="text-muted">Unbenannt</em>'}</span>
|
||||
${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(p.farbe)}</span>` : ''}
|
||||
<span class="litters-puppy-name">${p.name ? UI.escape(p.name) : '<em class="text-muted">Unbenannt</em>'}</span>
|
||||
${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(p.farbe)}</span>` : ''}
|
||||
${_puppyStatusBadge(p.status)}
|
||||
<span class="litters-puppy-last-weight" id="puppy-last-weight-${p.id}" class="text-xs-secondary"></span>
|
||||
</div>
|
||||
|
|
@ -565,7 +561,7 @@ window.Page_litters = (() => {
|
|||
`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('scales')} Gewichtsverlauf — ${_esc(puppyLabel)}`,
|
||||
title: `${UI.icon('scales')} Gewichtsverlauf — ${UI.escape(puppyLabel)}`,
|
||||
body,
|
||||
footer,
|
||||
});
|
||||
|
|
@ -695,7 +691,7 @@ window.Page_litters = (() => {
|
|||
</tbody>
|
||||
</table>`;
|
||||
} catch (err) {
|
||||
el.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${_esc(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
el.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${UI.escape(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -737,7 +733,7 @@ window.Page_litters = (() => {
|
|||
btn.innerHTML = `${UI.icon('list-bullets')} Warteliste${active ? ` <span style="background:var(--c-primary);color:white;border-radius:999px;padding:0 6px;font-size:10px;font-weight:700">${active}</span>` : ''}`;
|
||||
}
|
||||
} catch (err) {
|
||||
inner.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${_esc(err.message || 'Fehler.')}</p>`;
|
||||
inner.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${UI.escape(err.message || 'Fehler.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -776,18 +772,18 @@ window.Page_litters = (() => {
|
|||
<div style="background:var(--c-primary);color:white;border-radius:50%;width:1.6rem;height:1.6rem;display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:700;flex-shrink:0;margin-top:2px">${i + 1}</div>
|
||||
<div class="flex-1-min">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
|
||||
<span style="font-weight:600;font-size:var(--text-sm)">${_esc(e.name)}</span>
|
||||
<span style="font-weight:600;font-size:var(--text-sm)">${UI.escape(e.name)}</span>
|
||||
${_wlStatusBadge(e.status)}
|
||||
${e.wunsch_geschlecht && e.wunsch_geschlecht !== 'egal' ? `<span class="text-xs-secondary">${e.wunsch_geschlecht === 'maennlich' ? '♂ Rüde' : '♀ Hündin'}</span>` : ''}
|
||||
${e.wunsch_farbe ? `<span class="text-xs-secondary">${_esc(e.wunsch_farbe)}</span>` : ''}
|
||||
${e.wunsch_farbe ? `<span class="text-xs-secondary">${UI.escape(e.wunsch_farbe)}</span>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
${e.email ? `<span>${UI.icon('envelope')} ${_esc(e.email)}</span>` : ''}
|
||||
${e.telefon ? `<span>${UI.icon('phone')} ${_esc(e.telefon)}</span>` : ''}
|
||||
${e.email ? `<span>${UI.icon('envelope')} ${UI.escape(e.email)}</span>` : ''}
|
||||
${e.telefon ? `<span>${UI.icon('phone')} ${UI.escape(e.telefon)}</span>` : ''}
|
||||
<span>${UI.icon('calendar-dots')} ${e.created_at ? e.created_at.slice(0, 10) : '—'}</span>
|
||||
</div>
|
||||
${e.nachricht ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-secondary);font-style:italic">"${_esc(e.nachricht)}"</div>` : ''}
|
||||
${e.notiz ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);background:var(--c-warning-bg,#fffbeb);color:#92400e;border-radius:4px;padding:2px 6px">${UI.icon('note-pencil')} ${_esc(e.notiz)}</div>` : ''}
|
||||
${e.nachricht ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-secondary);font-style:italic">"${UI.escape(e.nachricht)}"</div>` : ''}
|
||||
${e.notiz ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);background:var(--c-warning-bg,#fffbeb);color:#92400e;border-radius:4px;padding:2px 6px">${UI.icon('note-pencil')} ${UI.escape(e.notiz)}</div>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
|
||||
<button class="btn btn-ghost btn-xs wl-edit-btn" data-entry-id="${e.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>
|
||||
|
|
@ -823,16 +819,16 @@ window.Page_litters = (() => {
|
|||
<form id="wl-form" class="flex-col-gap-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name *</label>
|
||||
<input class="form-control" name="name" required value="${_esc(v.name || '')}">
|
||||
<input class="form-control" name="name" required value="${UI.escape(v.name || '')}">
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input class="form-control" type="email" name="email" value="${_esc(v.email || '')}">
|
||||
<input class="form-control" type="email" name="email" value="${UI.escape(v.email || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Telefon</label>
|
||||
<input class="form-control" name="telefon" value="${_esc(v.telefon || '')}">
|
||||
<input class="form-control" name="telefon" value="${UI.escape(v.telefon || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
|
|
@ -846,12 +842,12 @@ window.Page_litters = (() => {
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Wunsch Farbe</label>
|
||||
<input class="form-control" name="wunsch_farbe" placeholder="z.B. schwarz-weiß" value="${_esc(v.wunsch_farbe || '')}">
|
||||
<input class="form-control" name="wunsch_farbe" placeholder="z.B. schwarz-weiß" value="${UI.escape(v.wunsch_farbe || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Nachricht des Interessenten</label>
|
||||
<textarea class="form-control" name="nachricht" rows="2" placeholder="Was hat der Interessent geschrieben?">${_esc(v.nachricht || '')}</textarea>
|
||||
<textarea class="form-control" name="nachricht" rows="2" placeholder="Was hat der Interessent geschrieben?">${UI.escape(v.nachricht || '')}</textarea>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
|
|
@ -867,7 +863,7 @@ window.Page_litters = (() => {
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Interne Notiz</label>
|
||||
<input class="form-control" name="notiz" placeholder="Nur für dich sichtbar" value="${_esc(v.notiz || '')}">
|
||||
<input class="form-control" name="notiz" placeholder="Nur für dich sichtbar" value="${UI.escape(v.notiz || '')}">
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
|
|
@ -919,7 +915,7 @@ window.Page_litters = (() => {
|
|||
const buildSelect = (name, idName, list, currentId, currentName, placeholder) => {
|
||||
const opts = list.map(h => {
|
||||
const label = h.name + (h.rufname ? ` (${h.rufname})` : '') + (h.zuchtbuchnummer ? ` · ${h.zuchtbuchnummer}` : '');
|
||||
return `<option value="${h.id}" data-name="${_esc(h.name)}" ${currentId == h.id ? 'selected' : ''}>${_esc(label)}</option>`;
|
||||
return `<option value="${h.id}" data-name="${UI.escape(h.name)}" ${currentId == h.id ? 'selected' : ''}>${UI.escape(label)}</option>`;
|
||||
}).join('');
|
||||
return `
|
||||
<select class="form-control" name="${idName}" id="${idName}-sel" class="mb-2">
|
||||
|
|
@ -927,7 +923,7 @@ window.Page_litters = (() => {
|
|||
${opts}
|
||||
</select>
|
||||
<input class="form-control" type="text" name="${name}" id="${name}-txt"
|
||||
value="${_esc(currentName || '')}" placeholder="oder Namen frei eingeben">`;
|
||||
value="${UI.escape(currentName || '')}" placeholder="oder Namen frei eingeben">`;
|
||||
};
|
||||
|
||||
const rangOpts = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(l =>
|
||||
|
|
@ -949,7 +945,7 @@ window.Page_litters = (() => {
|
|||
<label class="form-label">Wurf-Name <span style="font-weight:normal;color:var(--c-text-muted)">(optional)</span></label>
|
||||
<input class="form-control" type="text" name="wurf_name"
|
||||
placeholder="z.B. Vatertags-Wurf, Frühlings-Wurf …"
|
||||
value="${_esc(v.wurf_name || '')}">
|
||||
value="${UI.escape(v.wurf_name || '')}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -968,13 +964,13 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Erwarteter Geburtstermin <span style="font-weight:normal;color:var(--c-text-muted)">(geplant)</span></label>
|
||||
<input class="form-control" type="date" name="erwartetes_datum"
|
||||
value="${_esc(v.erwartetes_datum || '')}">
|
||||
value="${UI.escape(v.erwartetes_datum || '')}">
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:4px 0 0">Für geplante Würfe / laufende Trächtigkeit</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Geburtsdatum <span style="font-weight:normal;color:var(--c-text-muted)">(tatsächlich)</span></label>
|
||||
<input class="form-control" type="date" name="geburt_datum"
|
||||
value="${_esc(v.geburt_datum || '')}">
|
||||
value="${UI.escape(v.geburt_datum || '')}">
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:4px 0 0">Wenn die Welpen bereits geboren sind</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1005,19 +1001,19 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Preisspanne</label>
|
||||
<input class="form-control" type="text" name="preis_spanne"
|
||||
value="${_esc(v.preis_spanne || '')}" placeholder="z. B. 1.500 – 2.000 €">
|
||||
value="${UI.escape(v.preis_spanne || '')}" placeholder="z. B. 1.500 – 2.000 €">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung <span class="text-secondary">(optional)</span></label>
|
||||
<textarea class="form-control" name="beschreibung" rows="3"
|
||||
placeholder="Elternlinie, Besonderheiten, Charakter…">${_esc(v.beschreibung || '')}</textarea>
|
||||
placeholder="Elternlinie, Besonderheiten, Charakter…">${UI.escape(v.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Gesundheitstests <span class="text-secondary">(optional)</span></label>
|
||||
<textarea class="form-control" name="gesundheitstests" rows="2"
|
||||
placeholder="HD, ED, Gentest, Augenkontrolle…">${_esc(v.gesundheitstests || '')}</textarea>
|
||||
placeholder="HD, ED, Gentest, Augenkontrolle…">${UI.escape(v.gesundheitstests || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -1030,7 +1026,7 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Sichtbar bis <span class="text-secondary">(optional)</span></label>
|
||||
<input class="form-control" type="date" name="sichtbar_bis"
|
||||
value="${_esc(v.sichtbar_bis || '')}">
|
||||
value="${UI.escape(v.sichtbar_bis || '')}">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -1138,7 +1134,7 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Name <span class="text-secondary">(optional)</span></label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
value="${_esc(v.name || '')}" placeholder="z. B. Max">
|
||||
value="${UI.escape(v.name || '')}" placeholder="z. B. Max">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Geschlecht</label>
|
||||
|
|
@ -1153,7 +1149,7 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Farbe / Fellzeichnung</label>
|
||||
<input class="form-control" type="text" name="farbe"
|
||||
value="${_esc(v.farbe || '')}" placeholder="z. B. schwarz-braun">
|
||||
value="${UI.escape(v.farbe || '')}" placeholder="z. B. schwarz-braun">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -1169,7 +1165,7 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Chip-Nr.</label>
|
||||
<input class="form-control" type="text" name="chip_nr"
|
||||
value="${_esc(v.chip_nr || '')}" placeholder="15-stellig">
|
||||
value="${UI.escape(v.chip_nr || '')}" placeholder="15-stellig">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Geburtsgewicht (g)</label>
|
||||
|
|
@ -1188,7 +1184,7 @@ window.Page_litters = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Notiz <span class="text-secondary">(intern)</span></label>
|
||||
<textarea class="form-control" name="notiz" rows="2"
|
||||
placeholder="Interne Notizen…">${_esc(v.notiz || '')}</textarea>
|
||||
placeholder="Interne Notizen…">${UI.escape(v.notiz || '')}</textarea>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -1279,7 +1275,7 @@ window.Page_litters = (() => {
|
|||
`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('file-text')} Kaufvertrag — ${_esc(puppyLabel)}`,
|
||||
title: `${UI.icon('file-text')} Kaufvertrag — ${UI.escape(puppyLabel)}`,
|
||||
body,
|
||||
footer,
|
||||
});
|
||||
|
|
@ -1336,7 +1332,7 @@ window.Page_litters = (() => {
|
|||
`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('images')} Fotos — ${_esc(label)}`,
|
||||
title: `${UI.icon('images')} Fotos — ${UI.escape(label)}`,
|
||||
body,
|
||||
footer,
|
||||
});
|
||||
|
|
@ -1358,21 +1354,21 @@ window.Page_litters = (() => {
|
|||
const vis = visLabels[ph.visibility] || visLabels.private;
|
||||
return `
|
||||
<div style="position:relative;border-radius:var(--radius-md);overflow:hidden;border:1px solid var(--c-border);aspect-ratio:1">
|
||||
<a href="${_esc(ph.url || '')}" target="_blank" rel="noopener noreferrer">
|
||||
<img src="${_esc(thumb)}" alt="${_esc(ph.caption || '')}"
|
||||
<a href="${UI.escape(ph.url || '')}" target="_blank" rel="noopener noreferrer">
|
||||
<img src="${UI.escape(thumb)}" alt="${UI.escape(ph.caption || '')}"
|
||||
loading="lazy"
|
||||
style="width:100%;height:100%;object-fit:cover;display:block"
|
||||
onerror="this.src='/static/img/placeholder.webp'">
|
||||
</a>
|
||||
<button class="photos-vis-btn"
|
||||
data-photo-id="${ph.id}"
|
||||
data-vis="${_esc(ph.visibility)}"
|
||||
data-vis="${UI.escape(ph.visibility)}"
|
||||
title="Sichtbarkeit ändern"
|
||||
style="position:absolute;bottom:0;left:0;right:0;
|
||||
background:${vis.color};color:#fff;
|
||||
border:none;cursor:pointer;font-size:10px;padding:2px 4px;
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(vis.text)}
|
||||
${UI.escape(vis.text)}
|
||||
</button>
|
||||
<button class="photos-del-btn"
|
||||
data-photo-id="${ph.id}"
|
||||
|
|
@ -1418,7 +1414,7 @@ window.Page_litters = (() => {
|
|||
|
||||
} catch (err) {
|
||||
const el = document.getElementById(galleryId);
|
||||
if (el) el.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${_esc(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
if (el) el.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${UI.escape(err.message || 'Fehler beim Laden.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1464,13 +1460,13 @@ window.Page_litters = (() => {
|
|||
const issueHTML = (welfare.issues || []).map(i => `
|
||||
<div style="display:flex;gap:8px;padding:8px 0;border-bottom:1px solid rgba(0,0,0,.06)">
|
||||
<span style="color:${color};flex-shrink:0">${UI.icon('warning')}</span>
|
||||
<span class="text-sm">${_esc(i.text)}</span>
|
||||
<span class="text-sm">${UI.escape(i.text)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
const okHTML = (welfare.ok_points || []).map(p => `
|
||||
<div style="display:flex;gap:8px;padding:4px 0">
|
||||
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
|
||||
<span class="text-sm-secondary">${_esc(p)}</span>
|
||||
<span class="text-sm-secondary">${UI.escape(p)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
const isProblematic = welfare.level === 'warning' || welfare.level === 'critical';
|
||||
|
|
@ -1540,7 +1536,7 @@ window.Page_litters = (() => {
|
|||
} catch (err) {
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
|
||||
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
body: `<p class="text-danger">${UI.escape(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
|
||||
});
|
||||
return;
|
||||
|
|
@ -1548,7 +1544,7 @@ window.Page_litters = (() => {
|
|||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
|
||||
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
|
||||
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${UI.escape(text)}</div>`,
|
||||
footer: `
|
||||
<button class="btn btn-secondary flex-1" id="ki-announce-copy">
|
||||
${UI.icon('clipboard-text')} Kopieren
|
||||
|
|
|
|||
|
|
@ -286,8 +286,8 @@ window.Page_lost = (() => {
|
|||
const marker = UI.map.svgMarker(r.lat, r.lon, html, { size: 34, anchorY: 17 })
|
||||
.addTo(_map)
|
||||
.bindPopup(`
|
||||
<b>🔍 ${_escape(r.name)}</b><br>
|
||||
${r.rasse ? _escape(r.rasse) + '<br>' : ''}
|
||||
<b>🔍 ${UI.escape(r.name)}</b><br>
|
||||
${r.rasse ? UI.escape(r.rasse) + '<br>' : ''}
|
||||
${distStr ? `<small>📍 ${distStr} entfernt</small><br>` : ''}
|
||||
${r._isPending ? '<small>⏳ Sync ausstehend</small><br>' : ''}
|
||||
<small>📅 ${_fmtDate(r.created_at)}</small>
|
||||
|
|
@ -382,10 +382,10 @@ window.Page_lost = (() => {
|
|||
<div style="display:flex;align-items:center;gap:var(--space-2);
|
||||
margin-bottom:var(--space-1);flex-wrap:wrap">
|
||||
<span style="font-weight:var(--weight-semibold);font-size:var(--text-base)">
|
||||
${_escape(r.name)}
|
||||
${UI.escape(r.name)}
|
||||
</span>
|
||||
${r.rasse
|
||||
? `<span class="badge">${_escape(r.rasse)}</span>`
|
||||
? `<span class="badge">${UI.escape(r.rasse)}</span>`
|
||||
: ''}
|
||||
${isOwn
|
||||
? '<span class="badge badge-warning">Meine Meldung</span>'
|
||||
|
|
@ -399,11 +399,11 @@ window.Page_lost = (() => {
|
|||
</div>
|
||||
<p style="margin:0 0 var(--space-1);font-size:var(--text-sm);
|
||||
color:var(--c-text)">
|
||||
${_escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
|
||||
${UI.escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
|
||||
</p>
|
||||
<div class="text-xs-secondary">
|
||||
Gemeldet ${_fmtDate(r.created_at)}
|
||||
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
|
||||
${r.melder_name ? '· ' + UI.escape(r.melder_name.split(' ')[0]) : ''}
|
||||
</div>
|
||||
${r._isPending
|
||||
? `<div style="margin-top:var(--space-2);display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
|
||||
|
|
@ -418,7 +418,7 @@ window.Page_lost = (() => {
|
|||
: (_appState.user ? `<div class="mt-2">
|
||||
<button class="btn btn-ghost btn-xs lost-note-btn"
|
||||
data-lost-note-id="${r.id}"
|
||||
data-lost-note-name="${_escape(r.name)}"
|
||||
data-lost-note-name="${UI.escape(r.name)}"
|
||||
title="Notiz" onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz
|
||||
</button>
|
||||
|
|
@ -447,19 +447,19 @@ window.Page_lost = (() => {
|
|||
: ''}
|
||||
|
||||
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-3)">
|
||||
<span class="badge badge-danger">🐕 ${_escape(r.name)}</span>
|
||||
${r.rasse ? `<span class="badge">${_escape(r.rasse)}</span>` : ''}
|
||||
<span class="badge badge-danger">🐕 ${UI.escape(r.name)}</span>
|
||||
${r.rasse ? `<span class="badge">${UI.escape(r.rasse)}</span>` : ''}
|
||||
</div>
|
||||
|
||||
<p style="white-space:pre-wrap;margin-bottom:var(--space-3)">
|
||||
${_escape(r.beschreibung)}
|
||||
${UI.escape(r.beschreibung)}
|
||||
</p>
|
||||
|
||||
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
margin-bottom:var(--space-4);line-height:1.8">
|
||||
<div>📍 ${r.lat.toFixed(5)}, ${r.lon.toFixed(5)}${distStr ? ' (' + distStr + ' entfernt)' : ''}</div>
|
||||
<div>📅 Gemeldet: ${_fmtDate(r.created_at)}</div>
|
||||
${r.melder_name ? `<div>👤 Gemeldet von: ${_escape(r.melder_name.split(' ')[0])}</div>` : ''}
|
||||
${r.melder_name ? `<div>👤 Gemeldet von: ${UI.escape(r.melder_name.split(' ')[0])}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
|
||||
|
|
@ -473,7 +473,7 @@ window.Page_lost = (() => {
|
|||
</div>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: `🔍 ${_escape(r.name)} wird vermisst`, body });
|
||||
UI.modal.open({ title: `🔍 ${UI.escape(r.name)} wird vermisst`, body });
|
||||
|
||||
document.getElementById('detail-lost-map')?.addEventListener('click', () => {
|
||||
UI.modal.close();
|
||||
|
|
@ -511,10 +511,10 @@ window.Page_lost = (() => {
|
|||
// ----------------------------------------------------------
|
||||
function _showFoundDialog(r) {
|
||||
UI.modal.open({
|
||||
title: `🎉 ${_escape(r.name)} gefunden?`,
|
||||
title: `🎉 ${UI.escape(r.name)} gefunden?`,
|
||||
body: `
|
||||
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
Wurde ${_escape(r.name)} wiedergefunden? Die Meldung wird als
|
||||
Wurde ${UI.escape(r.name)} wiedergefunden? Die Meldung wird als
|
||||
abgeschlossen markiert und aus der Liste entfernt.
|
||||
</p>
|
||||
`,
|
||||
|
|
@ -555,7 +555,7 @@ window.Page_lost = (() => {
|
|||
const dogs = _appState.dogs || [];
|
||||
const dogOpts = dogs.length > 0
|
||||
? `<option value="">— kein registrierter Hund —</option>` +
|
||||
dogs.map(d => `<option value="${d.id}">${_escape(d.name)}${d.rasse ? ' (' + _escape(d.rasse) + ')' : ''}</option>`).join('')
|
||||
dogs.map(d => `<option value="${d.id}">${UI.escape(d.name)}${d.rasse ? ' (' + UI.escape(d.rasse) + ')' : ''}</option>`).join('')
|
||||
: '';
|
||||
|
||||
const body = `
|
||||
|
|
@ -790,17 +790,7 @@ window.Page_lost = (() => {
|
|||
day: '2-digit', month: '2-digit', year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
function _escape(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
return `<div class="empty-state">
|
||||
<svg class="ph-icon empty-state-icon" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${icon}"></use>
|
||||
|
|
@ -829,7 +819,7 @@ window.Page_lost = (() => {
|
|||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_escape(parentLabel)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1344,15 +1344,15 @@ window.Page_map = (() => {
|
|||
});
|
||||
|
||||
const marker = L.marker([b.location_lat, b.location_lng], { icon, zIndexOffset: t.z ?? 0 })
|
||||
.bindTooltip(_esc(b.zwingername), { direction: 'top', offset: [0, -16] });
|
||||
.bindTooltip(UI.escape(b.zwingername), { direction: 'top', offset: [0, -16] });
|
||||
|
||||
marker.on('click', () => {
|
||||
const rasseText = b.rasse_text ? `<div style="font-size:12px;color:#666;margin-bottom:4px">${_esc(b.rasse_text)}</div>` : '';
|
||||
const stadtText = b.stadt ? `<div style="font-size:12px;color:#888;margin-bottom:8px">${_esc(b.stadt)}</div>` : '';
|
||||
const rasseText = b.rasse_text ? `<div style="font-size:12px;color:#666;margin-bottom:4px">${UI.escape(b.rasse_text)}</div>` : '';
|
||||
const stadtText = b.stadt ? `<div style="font-size:12px;color:#888;margin-bottom:8px">${UI.escape(b.stadt)}</div>` : '';
|
||||
|
||||
marker.bindPopup(`
|
||||
<div style="min-width:170px;max-width:240px">
|
||||
<div style="font-weight:600;margin-bottom:6px">${t.icon} ${_esc(b.zwingername)}</div>
|
||||
<div style="font-weight:600;margin-bottom:6px">${t.icon} ${UI.escape(b.zwingername)}</div>
|
||||
${rasseText}${stadtText}
|
||||
<button class="btn btn-primary btn-sm" id="breeder-profile-btn">Profil ansehen</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -162,17 +162,17 @@ window.Page_moderation = (() => {
|
|||
gap:var(--space-4)">
|
||||
${fotos.map(f => `
|
||||
<div class="card p-4" data-id="${f.id}">
|
||||
<a href="#wiki?rasse=${_esc(f.rasse_slug)}" style="display:block;text-decoration:none">
|
||||
<img src="${_esc(f.foto_url)}" alt=""
|
||||
<a href="#wiki?rasse=${UI.escape(f.rasse_slug)}" style="display:block;text-decoration:none">
|
||||
<img src="${UI.escape(f.foto_url)}" alt=""
|
||||
style="width:100%;height:140px;object-fit:cover;
|
||||
border-radius:var(--radius-md);margin-bottom:var(--space-3)">
|
||||
</a>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">
|
||||
${_esc(f.rasse_name || f.rasse_slug)}
|
||||
${UI.escape(f.rasse_name || f.rasse_slug)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
|
||||
margin-bottom:var(--space-2)">
|
||||
von ${_esc(f.user_name)}
|
||||
von ${UI.escape(f.user_name)}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
${f.rights_confirmed
|
||||
|
|
@ -183,7 +183,7 @@ window.Page_moderation = (() => {
|
|||
</div>
|
||||
${f.aktuell_foto ? `
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:4px">Aktuell:</div>
|
||||
<img src="${_esc(f.aktuell_foto)}" alt="Aktuell"
|
||||
<img src="${UI.escape(f.aktuell_foto)}" alt="Aktuell"
|
||||
style="width:100%;height:70px;object-fit:cover;
|
||||
border-radius:var(--radius-sm);opacity:.5;
|
||||
margin-bottom:var(--space-3)">
|
||||
|
|
@ -299,23 +299,23 @@ window.Page_moderation = (() => {
|
|||
background:var(--c-surface-2);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-weight:var(--weight-bold);color:var(--c-text-secondary)">
|
||||
${_esc(u.name[0].toUpperCase())}
|
||||
${UI.escape(u.name[0].toUpperCase())}
|
||||
</div>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text)">
|
||||
${_esc(u.name)}
|
||||
${UI.escape(u.name)}
|
||||
${u.is_banned ? `<span style="font-size:10px;padding:1px 5px;
|
||||
border-radius:3px;background:var(--c-danger);
|
||||
color:#fff;margin-left:4px">GESPERRT</span>` : ''}
|
||||
</div>
|
||||
<div class="text-xs-muted">
|
||||
${_esc(u.email)} ·
|
||||
${UI.escape(u.email)} ·
|
||||
<span style="color:${
|
||||
u.rolle === 'admin' ? 'var(--c-danger)'
|
||||
: u.rolle === 'moderator' ? '#f59e0b'
|
||||
: 'var(--c-text-muted)'}">
|
||||
${_esc(u.rolle)}
|
||||
${UI.escape(u.rolle)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -323,12 +323,12 @@ window.Page_moderation = (() => {
|
|||
${canAction
|
||||
? (u.is_banned
|
||||
? `<button class="btn btn-sm btn-ghost mod-unban"
|
||||
data-uid="${u.id}" data-name="${_esc(u.name)}"
|
||||
data-uid="${u.id}" data-name="${UI.escape(u.name)}"
|
||||
title="Sperre aufheben" class="text-success">
|
||||
${UI.icon('lock-open')}
|
||||
</button>`
|
||||
: `<button class="btn btn-sm btn-ghost mod-ban"
|
||||
data-uid="${u.id}" data-name="${_esc(u.name)}"
|
||||
data-uid="${u.id}" data-name="${UI.escape(u.name)}"
|
||||
title="Sperren" class="text-danger">
|
||||
${UI.icon('lock')}
|
||||
</button>`)
|
||||
|
|
@ -408,19 +408,19 @@ window.Page_moderation = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
|
||||
margin-bottom:var(--space-1)">
|
||||
${_esc(r.target_type)} #${r.target_id} ·
|
||||
Gemeldet von <strong>${_esc(r.melder_name)}</strong>
|
||||
${UI.escape(r.target_type)} #${r.target_id} ·
|
||||
Gemeldet von <strong>${UI.escape(r.melder_name)}</strong>
|
||||
</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin-bottom:var(--space-1)">
|
||||
Grund: ${_esc(r.grund)}
|
||||
Grund: ${UI.escape(r.grund)}
|
||||
</div>
|
||||
${r.content_preview ? `
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
|
||||
padding:var(--space-2) var(--space-3);
|
||||
background:var(--c-surface-2);
|
||||
border-radius:var(--radius-sm)">
|
||||
${_esc(r.content_preview)}
|
||||
${UI.escape(r.content_preview)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary mod-resolve-btn"
|
||||
|
|
@ -481,9 +481,9 @@ window.Page_moderation = (() => {
|
|||
<div class="card p-4" data-edit-id="${e.id}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-2);flex-wrap:wrap">
|
||||
<div>
|
||||
<div style="font-weight:600">${_esc(e.poi_name)}</div>
|
||||
<div style="font-weight:600">${UI.escape(e.poi_name)}</div>
|
||||
<div class="text-xs-muted">
|
||||
OSM-ID: ${_esc(e.osm_id)} · Feld: ${_esc(e.field)} · von ${_esc(e.einreicher_name)}
|
||||
OSM-ID: ${UI.escape(e.osm_id)} · Feld: ${UI.escape(e.field)} · von ${UI.escape(e.einreicher_name)}
|
||||
· ${new Date(e.created_at).toLocaleDateString('de-DE')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -494,11 +494,11 @@ window.Page_moderation = (() => {
|
|||
<div style="margin-top:var(--space-3);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
|
||||
<div style="background:var(--c-surface-2);border-radius:var(--radius-sm);padding:var(--space-2)">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Aktuell</div>
|
||||
<div class="text-sm">${_esc(e.old_value) || '<em class="text-muted">leer</em>'}</div>
|
||||
<div class="text-sm">${UI.escape(e.old_value) || '<em class="text-muted">leer</em>'}</div>
|
||||
</div>
|
||||
<div style="background:var(--c-surface-2);border-radius:var(--radius-sm);padding:var(--space-2)">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Vorschlag</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:600">${_esc(e.new_value)}</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:600">${UI.escape(e.new_value)}</div>
|
||||
</div>
|
||||
</div>
|
||||
${e.status === 'pending' ? `
|
||||
|
|
@ -532,15 +532,6 @@ window.Page_moderation = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ window.Page_movies = (() => {
|
|||
<div class="movies-search-row">
|
||||
<svg class="ph-icon movies-search-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
|
||||
<input type="search" id="movies-search" class="form-control movies-search-input"
|
||||
placeholder="Film, Serie oder Rasse suchen …" value="${_esc(_search)}" autocomplete="off">
|
||||
placeholder="Film, Serie oder Rasse suchen …" value="${UI.escape(_search)}" autocomplete="off">
|
||||
</div>
|
||||
<div class="movies-filter-row">
|
||||
<button class="movies-filter-btn${_filter === 'alle' ? ' movies-filter-btn--active' : ''}" data-filter="alle">Alle</button>
|
||||
|
|
@ -202,17 +202,17 @@ window.Page_movies = (() => {
|
|||
const _ico = name => `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px;vertical-align:middle"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
|
||||
const typLabel = film.typ === 'serie' ? `${_ico('list')} Serie` : film.typ === 'doku' ? `${_ico('camera')} Doku` : '';
|
||||
const imdb = film.imdb_rating ? `<span class="text-xs-muted">IMDb ${film.imdb_rating}</span>` : '';
|
||||
const streaming = film.streaming ? `<span class="text-xs-muted">${_esc(film.streaming)}</span>` : '';
|
||||
const streaming = film.streaming ? `<span class="text-xs-muted">${UI.escape(film.streaming)}</span>` : '';
|
||||
|
||||
return `
|
||||
<div class="movie-card" data-film-id="${_esc(film.id)}">
|
||||
<div class="movie-card" data-film-id="${UI.escape(film.id)}">
|
||||
<div class="movie-card-emoji">${film.bild_emoji}</div>
|
||||
<div class="movie-card-body">
|
||||
<div class="movie-card-title">${_esc(film.titel)} <span class="movie-card-year">(${film.jahr})</span></div>
|
||||
<div class="movie-card-title">${UI.escape(film.titel)} <span class="movie-card-year">(${film.jahr})</span></div>
|
||||
<div class="movie-card-genre" style="display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap">
|
||||
<span>${_esc(film.genre)}</span>${typLabel ? `<span class="text-xs-muted">${typLabel}</span>` : ''}
|
||||
<span>${UI.escape(film.genre)}</span>${typLabel ? `<span class="text-xs-muted">${typLabel}</span>` : ''}
|
||||
</div>
|
||||
<div class="movie-card-rasse"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> ${_esc(film.hund_rasse)}</div>
|
||||
<div class="movie-card-rasse"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> ${UI.escape(film.hund_rasse)}</div>
|
||||
${tag}
|
||||
<div style="display:flex;gap:var(--space-3);margin-top:var(--space-1)">${imdb}${streaming}</div>
|
||||
<div class="movie-card-stars">${stars}</div>
|
||||
|
|
@ -234,17 +234,17 @@ window.Page_movies = (() => {
|
|||
const body = `
|
||||
<div class="movie-modal-emoji">${film.bild_emoji}</div>
|
||||
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-3)">
|
||||
<span class="badge badge-primary">${_esc(film.genre)}</span>
|
||||
<span class="badge"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> ${_esc(film.hund_rasse)}</span>
|
||||
<span class="badge badge-primary">${UI.escape(film.genre)}</span>
|
||||
<span class="badge"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> ${UI.escape(film.hund_rasse)}</span>
|
||||
<span class="badge">${film.jahr}</span>
|
||||
</div>
|
||||
<div class="${bannerClass}" style="margin-bottom:var(--space-4);font-size:var(--text-base)">${bannerText}</div>
|
||||
<p style="line-height:1.6;color:var(--c-text);margin-bottom:var(--space-5)">${_esc(film.beschreibung)}</p>
|
||||
<p style="line-height:1.6;color:var(--c-text);margin-bottom:var(--space-5)">${UI.escape(film.beschreibung)}</p>
|
||||
<div class="mb-2">
|
||||
<strong>Community-Bewertung:</strong>
|
||||
</div>
|
||||
<div id="modal-stars-${_esc(film.id)}">${stars}</div>
|
||||
<div id="modal-avg-${_esc(film.id)}" style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-top:var(--space-1)">
|
||||
<div id="modal-stars-${UI.escape(film.id)}">${stars}</div>
|
||||
<div id="modal-avg-${UI.escape(film.id)}" style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-top:var(--space-1)">
|
||||
Ø ${film.bewertung_avg} von ${film.bewertung_cnt || 0} Bewertungen
|
||||
</div>
|
||||
${loginHint}
|
||||
|
|
@ -262,9 +262,9 @@ window.Page_movies = (() => {
|
|||
const filled = Math.round(avg);
|
||||
const stars = [1,2,3,4,5].map(i => {
|
||||
const active = i <= (userRating || filled) ? ' movie-star--active' : '';
|
||||
return `<span class="movie-star${active}" data-film-id="${_esc(filmId)}" data-val="${i}"><svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#star"></use></svg></span>`;
|
||||
return `<span class="movie-star${active}" data-film-id="${UI.escape(filmId)}" data-val="${i}"><svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#star"></use></svg></span>`;
|
||||
}).join('');
|
||||
return `<div class="movie-star-rating" data-film-id="${_esc(filmId)}">${stars} <span class="movie-star-avg">${avg}</span></div>`;
|
||||
return `<div class="movie-star-rating" data-film-id="${UI.escape(filmId)}">${stars} <span class="movie-star-avg">${avg}</span></div>`;
|
||||
}
|
||||
|
||||
function _bindStarRatings(container) {
|
||||
|
|
@ -339,9 +339,9 @@ window.Page_movies = (() => {
|
|||
<div class="movie-promi-card">
|
||||
<div class="movie-promi-emoji">${p.emoji}</div>
|
||||
<div class="movie-promi-body">
|
||||
<div class="movie-promi-name">${_esc(p.name)}</div>
|
||||
<div class="movie-promi-rasse">${_esc(p.rasse)}</div>
|
||||
<div class="movie-promi-text">${_esc(p.bekannt_fuer)}</div>
|
||||
<div class="movie-promi-name">${UI.escape(p.name)}</div>
|
||||
<div class="movie-promi-rasse">${UI.escape(p.rasse)}</div>
|
||||
<div class="movie-promi-text">${UI.escape(p.bekannt_fuer)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
|
@ -370,13 +370,13 @@ window.Page_movies = (() => {
|
|||
const voteCards = _appState.dogs.map(dog => {
|
||||
const isVoted = data.user_vote === dog.id;
|
||||
const av = dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-vote-av-img">`
|
||||
: `<span class="hdm-vote-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
? `<img src="${UI.escape(dog.foto_url)}" alt="${UI.escape(dog.name)}" class="hdm-vote-av-img">`
|
||||
: `<span class="hdm-vote-av-placeholder">${UI.escape(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
return `
|
||||
<div class="hdm-vote-card${isVoted ? ' hdm-vote-card--voted' : ''}" data-dog-id="${dog.id}">
|
||||
<div class="hdm-vote-av">${av}</div>
|
||||
<div class="hdm-vote-name">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-vote-rasse">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div class="hdm-vote-name">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-vote-rasse">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
<button class="btn${isVoted ? ' btn-primary' : ' btn-secondary'} hdm-vote-btn" data-dog-id="${dog.id}">
|
||||
${isVoted ? '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Gewählt' : 'Abstimmen'}
|
||||
</button>
|
||||
|
|
@ -405,16 +405,16 @@ window.Page_movies = (() => {
|
|||
? data.top.slice(0, 5).map((dog, i) => {
|
||||
const medal = ['🥇','🥈','🥉','4️⃣','5️⃣'][i] || `${i+1}.`;
|
||||
const av = dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" alt="${_esc(dog.name)}" class="hdm-top-av-img">`
|
||||
: `<span class="hdm-top-av-placeholder">${_esc(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? _esc(dog.besitzer_name.split(' ')[0]) : '';
|
||||
? `<img src="${UI.escape(dog.foto_url)}" alt="${UI.escape(dog.name)}" class="hdm-top-av-img">`
|
||||
: `<span class="hdm-top-av-placeholder">${UI.escape(dog.name.charAt(0).toUpperCase())}</span>`;
|
||||
const vorname = dog.besitzer_name ? UI.escape(dog.besitzer_name.split(' ')[0]) : '';
|
||||
return `
|
||||
<div class="hdm-top-entry">
|
||||
<span class="hdm-top-medal">${medal}</span>
|
||||
<div class="hdm-top-av">${av}</div>
|
||||
<div class="hdm-top-info">
|
||||
<div class="hdm-top-name">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-top-rasse">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div class="hdm-top-name">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="hdm-top-rasse">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
${vorname ? `<div class="hdm-top-besitzer">von ${vorname}</div>` : ''}
|
||||
</div>
|
||||
<div class="hdm-top-stimmen">${dog.stimmen} <svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#star"></use></svg></div>
|
||||
|
|
@ -427,7 +427,7 @@ window.Page_movies = (() => {
|
|||
<div class="hdm-header">
|
||||
<div class="hdm-trophy">🏆</div>
|
||||
<h2 class="hdm-title">Hund des Monats</h2>
|
||||
<div class="hdm-monat">${_esc(monthName)}</div>
|
||||
<div class="hdm-monat">${UI.escape(monthName)}</div>
|
||||
</div>
|
||||
|
||||
${voteSection}
|
||||
|
|
@ -465,16 +465,7 @@ window.Page_movies = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str && str !== 0) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
return { init, refresh };
|
||||
|
|
|
|||
|
|
@ -47,14 +47,6 @@ window.Page_notes = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _formatTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
|
|
@ -125,7 +117,7 @@ window.Page_notes = (() => {
|
|||
.filter(([, items]) => items.length > 0)
|
||||
.map(([label, items]) => `
|
||||
<div class="notes-group">
|
||||
<div class="list-group-header">${_esc(label)}</div>
|
||||
<div class="list-group-header">${UI.escape(label)}</div>
|
||||
${items.map(_noteCard).join('')}
|
||||
</div>
|
||||
`).join('');
|
||||
|
|
@ -166,9 +158,9 @@ window.Page_notes = (() => {
|
|||
<div class="notes-filter-chips">
|
||||
${RUBRIKEN.map(r => `
|
||||
<button class="notes-chip ${_filterType === r.type ? 'notes-chip--active' : ''}"
|
||||
data-type="${_esc(r.type)}"
|
||||
data-type="${UI.escape(r.type)}"
|
||||
style="${_filterType === r.type ? `--chip-color:${r.color}` : ''}">
|
||||
${_esc(r.label)}
|
||||
${UI.escape(r.label)}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
|
@ -178,7 +170,7 @@ window.Page_notes = (() => {
|
|||
<div class="notes-search-wrap">
|
||||
<svg class="ph-icon notes-search-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
|
||||
<input id="notes-search" type="search" class="notes-search-input"
|
||||
placeholder="Suche…" value="${_esc(_searchQ)}">
|
||||
placeholder="Suche…" value="${UI.escape(_searchQ)}">
|
||||
</div>
|
||||
<div class="notes-sort-btns">
|
||||
<button class="notes-sort-btn ${_sortMode === 'newest' ? 'notes-sort-btn--active' : ''}"
|
||||
|
|
@ -292,11 +284,11 @@ window.Page_notes = (() => {
|
|||
<button class="notes-ki-btn" id="notes-ki-analyse-btn" ${_kiLoading ? 'disabled' : ''}>
|
||||
${_kiLoading ? '<svg class="ph-icon" aria-hidden="true" style="animation:spin 1s linear infinite"><use href="/icons/phosphor.svg#spinner-gap"></use></svg> Analysiere…' : 'Analysieren'}
|
||||
</button>
|
||||
${_kiError ? `<div class="notes-ki-error"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning-circle"></use></svg> ${_esc(_kiError)}</div>` : ''}
|
||||
${_kiError ? `<div class="notes-ki-error"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning-circle"></use></svg> ${UI.escape(_kiError)}</div>` : ''}
|
||||
${_kiSuggestions ? `
|
||||
<div class="notes-ki-suggestions">
|
||||
<ul>
|
||||
${_kiSuggestions.map(s => `<li>${_esc(s)}</li>`).join('')}
|
||||
${_kiSuggestions.map(s => `<li>${UI.escape(s)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
|
@ -326,10 +318,10 @@ window.Page_notes = (() => {
|
|||
<div class="notes-card-top">
|
||||
<span class="list-item-chip" style="--chip-color:${rb.color}">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${rb.icon}"></use></svg>
|
||||
${_esc(rb.label)}
|
||||
${UI.escape(rb.label)}
|
||||
</span>
|
||||
${note.parent_label
|
||||
? `<span class="notes-parent-label" title="${_esc(note.parent_label)}">${_esc(note.parent_label)}</span>`
|
||||
? `<span class="notes-parent-label" title="${UI.escape(note.parent_label)}">${UI.escape(note.parent_label)}</span>`
|
||||
: ''
|
||||
}
|
||||
<div class="list-item-actions notes-card-actions">
|
||||
|
|
@ -343,20 +335,20 @@ window.Page_notes = (() => {
|
|||
</div>
|
||||
|
||||
<!-- Notiztext -->
|
||||
<p class="list-item-text notes-card-text">${_esc(_truncate(note.text))}</p>
|
||||
<p class="list-item-text notes-card-text">${UI.escape(_truncate(note.text))}</p>
|
||||
|
||||
<!-- Micro-Badges -->
|
||||
${microBadges.length ? `
|
||||
<div class="list-item-micro-badges">
|
||||
${microBadges.map(b => `<span class="list-item-micro-badge">${_esc(b)}</span>`).join('')}
|
||||
${microBadges.map(b => `<span class="list-item-micro-badge">${UI.escape(b)}</span>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Meta: Zeit + Ort -->
|
||||
<div class="list-item-meta-row">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#clock"></use></svg>
|
||||
${_esc(_formatTime(note.updated_at || note.created_at))}
|
||||
${hasLocation ? `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#map-pin"></use></svg> ${_esc(note.location_name)}` : ''}
|
||||
${UI.escape(_formatTime(note.updated_at || note.created_at))}
|
||||
${hasLocation ? `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#map-pin"></use></svg> ${UI.escape(note.location_name)}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -514,7 +506,7 @@ window.Page_notes = (() => {
|
|||
border-radius:999px;border:1.5px solid ${_selType===r.type ? r.color : 'var(--c-border)'};
|
||||
background:${_selType===r.type ? r.color+'22' : 'var(--c-surface-2)'};
|
||||
color:${_selType===r.type ? r.color : 'var(--c-text-secondary)'};cursor:pointer">
|
||||
${_esc(r.label)}
|
||||
${UI.escape(r.label)}
|
||||
</button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -607,7 +599,7 @@ window.Page_notes = (() => {
|
|||
<span style="display:inline-flex;align-items:center;gap:4px;font-size:var(--text-xs);
|
||||
font-weight:var(--weight-semibold);padding:2px var(--space-2);border-radius:999px;
|
||||
background:${rb.color}22;color:${rb.color}">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${rb.icon}"></use></svg> ${_esc(rb.label)}
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${rb.icon}"></use></svg> ${UI.escape(rb.label)}
|
||||
</span>
|
||||
<h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text);margin:0">
|
||||
Notiz bearbeiten
|
||||
|
|
@ -625,7 +617,7 @@ window.Page_notes = (() => {
|
|||
border-radius:var(--radius-md);font-size:var(--text-sm);
|
||||
font-family:var(--font-sans);background:var(--c-surface);
|
||||
color:var(--c-text);resize:vertical;outline:none;line-height:1.5;
|
||||
box-sizing:border-box">${_esc(note.text)}</textarea>
|
||||
box-sizing:border-box">${UI.escape(note.text)}</textarea>
|
||||
</div>
|
||||
|
||||
${note.parent_type === 'training_session' ? `
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ window.Page_onboarding = (() => {
|
|||
${dogName ? `
|
||||
<p style="font-size:var(--text-base);color:var(--c-text-secondary);
|
||||
line-height:1.6;margin:0 0 var(--space-3)">
|
||||
<strong>${_esc(dogName)}</strong> ist jetzt in Ban Yaro.
|
||||
<strong>${UI.escape(dogName)}</strong> ist jetzt in Ban Yaro.
|
||||
Du kannst jetzt Einträge im Tagebuch anlegen, die Gesundheit pflegen
|
||||
und viele weitere Funktionen nutzen.
|
||||
</p>
|
||||
|
|
@ -416,7 +416,7 @@ window.Page_onboarding = (() => {
|
|||
}
|
||||
App.renderDogSwitcher();
|
||||
|
||||
UI.toast.success(`${_esc(dog.name)} wurde angelegt!`);
|
||||
UI.toast.success(`${UI.escape(dog.name)} wurde angelegt!`);
|
||||
|
||||
_step = 3;
|
||||
_render();
|
||||
|
|
@ -452,9 +452,6 @@ window.Page_onboarding = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
return UI.escape(s || '');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ window.Page_partner_profil = (() => {
|
|||
background:var(--c-surface-2);display:flex;align-items:center;justify-content:center;
|
||||
overflow:hidden;flex-shrink:0">
|
||||
${p.logo_url
|
||||
? `<img src="${_esc(p.logo_url)}" style="width:100%;height:100%;object-fit:contain">`
|
||||
? `<img src="${UI.escape(p.logo_url)}" style="width:100%;height:100%;object-fit:contain">`
|
||||
: `<svg class="ph-icon" style="width:32px;height:32px;opacity:.3"><use href="/icons/phosphor.svg#image"></use></svg>`}
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -102,30 +102,30 @@ window.Page_partner_profil = (() => {
|
|||
<label class="form-label">Anzeigename *</label>
|
||||
<input class="form-control" name="display_name" type="text" maxlength="60" required
|
||||
placeholder="z. B. Hundeblog Musterfrau"
|
||||
value="${_esc(p.display_name || '')}">
|
||||
value="${UI.escape(p.display_name || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Kurzslogan <span style="font-weight:400;color:var(--c-text-muted)">(max. 80 Zeichen)</span></label>
|
||||
<input class="form-control" name="tagline" type="text" maxlength="80"
|
||||
placeholder="z. B. Hundetrainerin · 15.000 Follower auf Instagram"
|
||||
value="${_esc(p.tagline || '')}">
|
||||
value="${UI.escape(p.tagline || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Über dich / euer Kanal</label>
|
||||
<textarea class="form-control" name="bio" rows="4" maxlength="500"
|
||||
placeholder="Wer bist du, was machst du, was verbindet dich mit Hunden?">${_esc(p.bio || p.pp_bio || '')}</textarea>
|
||||
placeholder="Wer bist du, was machst du, was verbindet dich mit Hunden?">${UI.escape(p.bio || p.pp_bio || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Website</label>
|
||||
<input class="form-control" name="website" type="url"
|
||||
placeholder="https://deine-seite.de"
|
||||
value="${_esc(p.website || '')}">
|
||||
value="${UI.escape(p.website || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Instagram</label>
|
||||
<input class="form-control" name="instagram" type="text"
|
||||
placeholder="@deinkanal"
|
||||
value="${_esc(p.instagram || '')}">
|
||||
value="${UI.escape(p.instagram || '')}">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary btn-sm" style="align-self:flex-start">
|
||||
Texte speichern
|
||||
|
|
@ -154,11 +154,11 @@ window.Page_partner_profil = (() => {
|
|||
<div style="position:relative;aspect-ratio:1;border-radius:var(--radius-md);overflow:hidden;
|
||||
background:var(--c-surface-2)">
|
||||
${isVid
|
||||
? `<video src="${_esc(url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
|
||||
? `<video src="${UI.escape(url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
|
||||
onmouseenter="this.play()" onmouseleave="this.pause()"></video>
|
||||
<div style="position:absolute;bottom:4px;left:4px;background:rgba(0,0,0,.55);
|
||||
border-radius:4px;padding:1px 5px;font-size:10px;color:#fff">▶ Video</div>`
|
||||
: `<img src="${_esc(url)}" style="width:100%;height:100%;object-fit:cover">`}
|
||||
: `<img src="${UI.escape(url)}" style="width:100%;height:100%;object-fit:cover">`}
|
||||
<button class="pp-photo-del" data-idx="${i}"
|
||||
style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);
|
||||
border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;
|
||||
|
|
@ -197,7 +197,7 @@ window.Page_partner_profil = (() => {
|
|||
try {
|
||||
const r = await API.upload('/partner/my-profile/logo', fd);
|
||||
el.querySelector('#pp-logo-preview').innerHTML =
|
||||
`<img src="${_esc(r.logo_url)}" style="width:100%;height:100%;object-fit:contain">`;
|
||||
`<img src="${UI.escape(r.logo_url)}" style="width:100%;height:100%;object-fit:contain">`;
|
||||
_profile = { ..._profile, logo_url: r.logo_url };
|
||||
UI.toast.success('Logo gespeichert.');
|
||||
} catch (err) { UI.toast.error(err.message); }
|
||||
|
|
@ -268,10 +268,6 @@ window.Page_partner_profil = (() => {
|
|||
</div>`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ window.Page_partner = (() => {
|
|||
<div style="position:absolute;top:0;left:0;right:0;height:3px;background:${grad}"></div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
${p.logo_url
|
||||
? `<img src="${_esc(p.logo_url)}" alt=""
|
||||
? `<img src="${UI.escape(p.logo_url)}" alt=""
|
||||
style="width:56px;height:56px;border-radius:var(--radius-md);object-fit:contain;flex-shrink:0;background:var(--c-surface-2);padding:4px">`
|
||||
: p.avatar_url
|
||||
? `<img src="${_esc(p.avatar_url)}" alt=""
|
||||
? `<img src="${UI.escape(p.avatar_url)}" alt=""
|
||||
style="width:56px;height:56px;border-radius:50%;object-fit:cover;flex-shrink:0">`
|
||||
: `<div style="width:56px;height:56px;border-radius:50%;flex-shrink:0;
|
||||
background:${grad};display:flex;align-items:center;
|
||||
|
|
@ -89,19 +89,19 @@ window.Page_partner = (() => {
|
|||
</div>`
|
||||
}
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${_esc(p.display_name || p.name)}</div>
|
||||
${p.tagline ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:1px">${_esc(p.tagline)}</div>` : ''}
|
||||
<div style="font-weight:700;font-size:var(--text-base)">${UI.escape(p.display_name || p.name)}</div>
|
||||
${p.tagline ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:1px">${UI.escape(p.tagline)}</div>` : ''}
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-1)">
|
||||
${p.website ? `<a href="${_esc(p.website)}" target="_blank" rel="noopener"
|
||||
${p.website ? `<a href="${UI.escape(p.website)}" target="_blank" rel="noopener"
|
||||
style="font-size:var(--text-xs);color:var(--c-primary)">
|
||||
🌐 ${_esc(p.website.replace(/^https?:\/\//, ''))}</a>` : ''}
|
||||
${p.instagram ? `<span class="text-xs-muted">📸 ${_esc(p.instagram)}</span>` : ''}
|
||||
🌐 ${UI.escape(p.website.replace(/^https?:\/\//, ''))}</a>` : ''}
|
||||
${p.instagram ? `<span class="text-xs-muted">📸 ${UI.escape(p.instagram)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${p.pp_bio || p.bio ? `<p style="margin:var(--space-3) 0 0;font-size:var(--text-sm);
|
||||
color:var(--c-text-secondary);line-height:1.5">
|
||||
${_esc(p.pp_bio || p.bio)}
|
||||
${UI.escape(p.pp_bio || p.bio)}
|
||||
</p>` : ''}
|
||||
${p.photos?.length ? `
|
||||
<div style="display:grid;grid-template-columns:repeat(${Math.min(p.photos.length,3)},1fr);
|
||||
|
|
@ -109,9 +109,9 @@ window.Page_partner = (() => {
|
|||
${p.photos.slice(0,3).map(url => {
|
||||
const isVid = url.endsWith('.mp4') || url.endsWith('.webm');
|
||||
return isVid
|
||||
? `<video src="${_esc(url)}" style="width:100%;aspect-ratio:1;object-fit:cover"
|
||||
? `<video src="${UI.escape(url)}" style="width:100%;aspect-ratio:1;object-fit:cover"
|
||||
muted playsinline loop autoplay></video>`
|
||||
: `<img src="${_esc(url)}" style="width:100%;aspect-ratio:1;object-fit:cover">`;
|
||||
: `<img src="${UI.escape(url)}" style="width:100%;aspect-ratio:1;object-fit:cover">`;
|
||||
}).join('')}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
|
@ -143,10 +143,6 @@ window.Page_partner = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
|
|
|
|||
|
|
@ -15,21 +15,16 @@ window.Page_playdate = (() => {
|
|||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/&/g, '&').replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso.replace(' ', 'T'));
|
||||
return d.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
function _dogAvatar(foto_url, name, size = 48) {
|
||||
const initials = _esc((name || '?').charAt(0).toUpperCase());
|
||||
const initials = UI.escape((name || '?').charAt(0).toUpperCase());
|
||||
if (foto_url) {
|
||||
return `<img src="${_esc(foto_url)}" alt="${initials}"
|
||||
return `<img src="${UI.escape(foto_url)}" alt="${initials}"
|
||||
style="width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;display:block;"
|
||||
onerror="this.outerHTML='<div style=\'width:${size}px;height:${size}px;border-radius:50%;background:var(--c-primary-subtle);display:flex;align-items:center;justify-content:center;font-size:${Math.round(size*0.45)}px;font-weight:700;color:var(--c-primary);\'>${initials}</div>'">`;
|
||||
}
|
||||
|
|
@ -250,29 +245,29 @@ window.Page_playdate = (() => {
|
|||
${_dogAvatar(d.foto_url, d.dog_name, 56)}
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base);
|
||||
color:var(--c-text)">${_esc(d.dog_name)}</div>
|
||||
${d.rasse ? `<div class="text-sm-secondary">${_esc(d.rasse)}</div>` : ''}
|
||||
${d.alter ? `<div class="text-xs-muted">${_esc(d.alter)}</div>` : ''}
|
||||
color:var(--c-text)">${UI.escape(d.dog_name)}</div>
|
||||
${d.rasse ? `<div class="text-sm-secondary">${UI.escape(d.rasse)}</div>` : ''}
|
||||
${d.alter ? `<div class="text-xs-muted">${UI.escape(d.alter)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:var(--space-3);margin-bottom:var(--space-3);flex-wrap:wrap">
|
||||
<span style="display:flex;align-items:center;gap:4px;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
${UI.icon('map-pin')}
|
||||
${d.ort_name ? _esc(d.ort_name) + ' · ' : ''}${d.entfernung_km} km entfernt
|
||||
${d.ort_name ? UI.escape(d.ort_name) + ' · ' : ''}${d.entfernung_km} km entfernt
|
||||
</span>
|
||||
${d.geschlecht ? `<span class="text-xs-muted">${_esc(d.geschlecht)}</span>` : ''}
|
||||
${d.geschlecht ? `<span class="text-xs-muted">${UI.escape(d.geschlecht)}</span>` : ''}
|
||||
</div>
|
||||
|
||||
${d.beschreibung ? `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
margin:0 0 var(--space-3);line-height:1.5">
|
||||
${_esc(d.beschreibung)}
|
||||
${UI.escape(d.beschreibung)}
|
||||
</p>` : ''}
|
||||
|
||||
<button class="btn btn-primary btn-sm playdate-anfrage-btn"
|
||||
data-dog-id="${d.dog_id}"
|
||||
data-dog-name="${_esc(d.dog_name)}">
|
||||
data-dog-name="${UI.escape(d.dog_name)}">
|
||||
${UI.icon('paw-print')} Spielkamerad anfragen
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -393,8 +388,8 @@ window.Page_playdate = (() => {
|
|||
<div style="display:flex;gap:var(--space-3);align-items:center;margin-bottom:var(--space-3)">
|
||||
${_dogAvatar(dog.foto_url, dog.name, 44)}
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="text-xs-secondary">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="text-xs-secondary">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
</div>
|
||||
<span style="font-size:var(--text-xs);font-weight:600;
|
||||
padding:2px 10px;border-radius:999px;
|
||||
|
|
@ -407,12 +402,12 @@ window.Page_playdate = (() => {
|
|||
${isAktiv ? `
|
||||
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-3)">
|
||||
${UI.icon('map-pin')}
|
||||
${listing.ort_name ? _esc(listing.ort_name) + ' · ' : ''}
|
||||
${listing.ort_name ? UI.escape(listing.ort_name) + ' · ' : ''}
|
||||
Radius: ${listing.radius_km} km
|
||||
</div>
|
||||
${listing.beschreibung ? `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
margin:0 0 var(--space-3);line-height:1.5">${_esc(listing.beschreibung)}</p>` : ''}
|
||||
margin:0 0 var(--space-3);line-height:1.5">${UI.escape(listing.beschreibung)}</p>` : ''}
|
||||
` : `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin:0 0 var(--space-3)">
|
||||
Noch kein Inserat — trage dich ein, damit andere dich finden können.
|
||||
|
|
@ -445,7 +440,7 @@ window.Page_playdate = (() => {
|
|||
<div class="flex-gap-2">
|
||||
<input type="text" id="listing-ort" class="form-control"
|
||||
placeholder="z.B. München"
|
||||
value="${_esc(existing?.ort_name || '')}">
|
||||
value="${UI.escape(existing?.ort_name || '')}">
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="listing-gps-btn"
|
||||
title="GPS-Standort ermitteln">
|
||||
${UI.icon('crosshair')}
|
||||
|
|
@ -472,7 +467,7 @@ window.Page_playdate = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung (optional)</label>
|
||||
<textarea id="listing-beschreibung" class="form-control" rows="3" maxlength="400"
|
||||
placeholder="Erzähl etwas über deinen Hund und was ihr sucht…">${_esc(existing?.beschreibung || '')}</textarea>
|
||||
placeholder="Erzähl etwas über deinen Hund und was ihr sucht…">${UI.escape(existing?.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
|
|
@ -635,11 +630,11 @@ window.Page_playdate = (() => {
|
|||
<div style="display:flex;gap:var(--space-3);align-items:flex-start;margin-bottom:var(--space-3)">
|
||||
${_dogAvatar(r.from_dog_foto, r.from_dog_name, 44)}
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(r.from_dog_name)}</div>
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${UI.escape(r.from_dog_name)}</div>
|
||||
<div class="text-xs-secondary">
|
||||
${r.from_dog_rasse ? _esc(r.from_dog_rasse) + ' · ' : ''}
|
||||
${r.alter ? _esc(r.alter) + ' · ' : ''}
|
||||
von ${_esc(r.from_user_name)}
|
||||
${r.from_dog_rasse ? UI.escape(r.from_dog_rasse) + ' · ' : ''}
|
||||
${r.alter ? UI.escape(r.alter) + ' · ' : ''}
|
||||
von ${UI.escape(r.from_user_name)}
|
||||
</div>
|
||||
<div class="text-xs-muted">${_fmtDate(r.created_at)}</div>
|
||||
</div>
|
||||
|
|
@ -651,7 +646,7 @@ window.Page_playdate = (() => {
|
|||
background:var(--c-surface-2);border-radius:var(--radius-md);
|
||||
padding:var(--space-2) var(--space-3);margin-bottom:var(--space-3);
|
||||
line-height:1.5">
|
||||
"${_esc(r.nachricht)}"
|
||||
"${UI.escape(r.nachricht)}"
|
||||
</div>` : ''}
|
||||
|
||||
${isPending ? `
|
||||
|
|
@ -680,10 +675,10 @@ window.Page_playdate = (() => {
|
|||
<div style="display:flex;gap:var(--space-3);align-items:flex-start;margin-bottom:var(--space-3)">
|
||||
${_dogAvatar(r.to_dog_foto, r.to_dog_name, 44)}
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(r.to_dog_name)}</div>
|
||||
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${UI.escape(r.to_dog_name)}</div>
|
||||
<div class="text-xs-secondary">
|
||||
${r.to_dog_rasse ? _esc(r.to_dog_rasse) + ' · ' : ''}
|
||||
von ${_esc(r.to_user_name)}
|
||||
${r.to_dog_rasse ? UI.escape(r.to_dog_rasse) + ' · ' : ''}
|
||||
von ${UI.escape(r.to_user_name)}
|
||||
</div>
|
||||
<div class="text-xs-muted">${_fmtDate(r.created_at)}</div>
|
||||
</div>
|
||||
|
|
@ -692,7 +687,7 @@ window.Page_playdate = (() => {
|
|||
|
||||
${r.nachricht ? `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
|
||||
"${_esc(r.nachricht)}"
|
||||
"${UI.escape(r.nachricht)}"
|
||||
</p>` : ''}
|
||||
|
||||
${r.status === 'accepted' ? `
|
||||
|
|
|
|||
|
|
@ -106,14 +106,6 @@ window.Page_reise = (() => {
|
|||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
function _esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _loadChecked() {
|
||||
try { return JSON.parse(localStorage.getItem(LS_KEY) || '{}'); }
|
||||
|
|
@ -212,16 +204,16 @@ window.Page_reise = (() => {
|
|||
const done = !!checked[key];
|
||||
if (_editMode) {
|
||||
return `<div class="reise-check-row" style="justify-content:space-between">
|
||||
<span style="flex:1;color:var(--c-text)">${_esc(item)}</span>
|
||||
<button class="reise-del-btn" data-hide="${_esc(key)}"
|
||||
<span style="flex:1;color:var(--c-text)">${UI.escape(item)}</span>
|
||||
<button class="reise-del-btn" data-hide="${UI.escape(key)}"
|
||||
style="background:none;border:none;color:#EF4444;cursor:pointer;padding:4px;flex-shrink:0">
|
||||
<svg class="ph-icon" style="width:1rem;height:1rem"><use href="/icons/phosphor.svg#trash"></use></svg>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
return `<label class="reise-check-row${done ? ' done' : ''}">
|
||||
<input type="checkbox" class="reise-cb" data-key="${_esc(key)}" ${done ? 'checked' : ''}>
|
||||
<span>${_esc(item)}</span>
|
||||
<input type="checkbox" class="reise-cb" data-key="${UI.escape(key)}" ${done ? 'checked' : ''}>
|
||||
<span>${UI.escape(item)}</span>
|
||||
</label>`;
|
||||
}).join('');
|
||||
|
||||
|
|
@ -230,27 +222,27 @@ window.Page_reise = (() => {
|
|||
const done = !!checked[key];
|
||||
if (_editMode) {
|
||||
return `<div class="reise-check-row" style="justify-content:space-between">
|
||||
<span style="flex:1;color:var(--c-primary)">${_esc(item)}</span>
|
||||
<button class="reise-del-custom-btn" data-cat="${_esc(cat.key)}" data-idx="${i}"
|
||||
<span style="flex:1;color:var(--c-primary)">${UI.escape(item)}</span>
|
||||
<button class="reise-del-custom-btn" data-cat="${UI.escape(cat.key)}" data-idx="${i}"
|
||||
style="background:none;border:none;color:#EF4444;cursor:pointer;padding:4px;flex-shrink:0">
|
||||
<svg class="ph-icon" style="width:1rem;height:1rem"><use href="/icons/phosphor.svg#trash"></use></svg>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
return `<label class="reise-check-row${done ? ' done' : ''}">
|
||||
<input type="checkbox" class="reise-cb" data-key="${_esc(key)}" ${done ? 'checked' : ''}>
|
||||
<span class="text-primary">${_esc(item)}</span>
|
||||
<input type="checkbox" class="reise-cb" data-key="${UI.escape(key)}" ${done ? 'checked' : ''}>
|
||||
<span class="text-primary">${UI.escape(item)}</span>
|
||||
</label>`;
|
||||
}).join('');
|
||||
|
||||
const addRow = _editMode ? `
|
||||
<div style="padding:var(--space-2) 0;border-top:1px dashed var(--c-border);margin-top:4px">
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<input class="reise-add-input" data-cat="${_esc(cat.key)}"
|
||||
<input class="reise-add-input" data-cat="${UI.escape(cat.key)}"
|
||||
style="flex:1;padding:8px 10px;border-radius:8px;border:1px solid var(--c-border);
|
||||
background:var(--c-bg-card);color:var(--c-text);font-size:var(--text-sm)"
|
||||
placeholder="Eigenes Item hinzufügen…">
|
||||
<button class="reise-add-btn btn btn-primary" data-cat="${_esc(cat.key)}"
|
||||
<button class="reise-add-btn btn btn-primary" data-cat="${UI.escape(cat.key)}"
|
||||
style="padding:8px 12px;flex-shrink:0;font-size:var(--text-sm)">
|
||||
<svg class="ph-icon" style="width:1rem;height:1rem"><use href="/icons/phosphor.svg#plus"></use></svg>
|
||||
</button>
|
||||
|
|
@ -261,9 +253,9 @@ window.Page_reise = (() => {
|
|||
<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--c-border);
|
||||
display:flex;align-items:center;gap:var(--space-2)">
|
||||
<svg class="ph-icon text-primary" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${_esc(cat.icon)}"></use>
|
||||
<use href="/icons/phosphor.svg#${UI.escape(cat.icon)}"></use>
|
||||
</svg>
|
||||
<span style="font-weight:var(--weight-semibold)">${_esc(cat.label)}</span>
|
||||
<span style="font-weight:var(--weight-semibold)">${UI.escape(cat.label)}</span>
|
||||
</div>
|
||||
<div style="padding:var(--space-2) var(--space-4)">
|
||||
${stdRows}${customRows}${addRow}
|
||||
|
|
@ -389,10 +381,10 @@ window.Page_reise = (() => {
|
|||
<span style="font-size:2rem;line-height:1">${l.flag}</span>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-1)">
|
||||
${_esc(l.name)}
|
||||
${UI.escape(l.name)}
|
||||
</div>
|
||||
<div class="text-sm-secondary">
|
||||
${_esc(l.regel)}
|
||||
${UI.escape(l.regel)}
|
||||
</div>
|
||||
</div>
|
||||
${l.warn ? `<svg class="ph-icon" style="color:var(--c-warning,#f59e0b);flex-shrink:0;width:20px;height:20px" aria-hidden="true">
|
||||
|
|
@ -425,9 +417,9 @@ window.Page_reise = (() => {
|
|||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);
|
||||
padding:var(--space-3) 0;border-bottom:1px solid var(--c-surface-2)">
|
||||
<svg class="ph-icon" style="color:var(--c-danger,#ef4444);flex-shrink:0;margin-top:1px" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${_esc(m.icon)}"></use>
|
||||
<use href="/icons/phosphor.svg#${UI.escape(m.icon)}"></use>
|
||||
</svg>
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text)">${_esc(m.text)}</span>
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text)">${UI.escape(m.text)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
el.innerHTML = `
|
||||
|
|
|
|||
|
|
@ -509,9 +509,9 @@ window.Page_settings = (() => {
|
|||
|
||||
// Avatar: Bild oder Buchstabe
|
||||
const avatarInner = u.avatar_url
|
||||
? `<img src="${_esc(u.avatar_url)}" alt="Avatar"
|
||||
? `<img src="${UI.escape(u.avatar_url)}" alt="Avatar"
|
||||
style="width:56px;height:56px;border-radius:50%;object-fit:cover;display:block">`
|
||||
: _esc(u.name.charAt(0).toUpperCase());
|
||||
: UI.escape(u.name.charAt(0).toUpperCase());
|
||||
|
||||
// Mitglied seit
|
||||
const memberSince = (() => {
|
||||
|
|
@ -547,9 +547,9 @@ window.Page_settings = (() => {
|
|||
<input type="file" id="settings-avatar-input" accept="image/*"
|
||||
class="hidden">
|
||||
<div>
|
||||
<div style="font-weight:700;font-size:var(--text-lg)">${_esc(u.name)}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-lg)">${UI.escape(u.name)}</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);color:var(--c-text-secondary);font-size:var(--text-sm)">
|
||||
${_esc(u.email)}
|
||||
${UI.escape(u.email)}
|
||||
${u.email_verified
|
||||
? `<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px;color:#22c55e" title="Bestätigt"><use href="/icons/phosphor.svg#check-circle"></use></svg>`
|
||||
: `<span id="settings-verify-chip"
|
||||
|
|
@ -600,26 +600,26 @@ window.Page_settings = (() => {
|
|||
<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)">
|
||||
${memberSince
|
||||
? `<div class="text-sm-secondary">
|
||||
Mitglied seit ${_esc(memberSince)}
|
||||
Mitglied seit ${UI.escape(memberSince)}
|
||||
</div>`
|
||||
: ''}
|
||||
${u.bio
|
||||
? `<div class="text-sm">${_esc(u.bio)}</div>`
|
||||
? `<div class="text-sm">${UI.escape(u.bio)}</div>`
|
||||
: ''}
|
||||
${u.wohnort
|
||||
? `<div class="text-sm-secondary">
|
||||
📍 ${_esc(u.wohnort)}
|
||||
📍 ${UI.escape(u.wohnort)}
|
||||
</div>`
|
||||
: ''}
|
||||
${u.erfahrung && erfahrungLabel[u.erfahrung]
|
||||
? `<div class="text-sm-secondary">
|
||||
${_esc(erfahrungLabel[u.erfahrung])}
|
||||
${UI.escape(erfahrungLabel[u.erfahrung])}
|
||||
</div>`
|
||||
: ''}
|
||||
${u.social_link
|
||||
? `<div class="text-sm">
|
||||
<a href="${_esc(u.social_link)}" target="_blank" rel="noopener"
|
||||
class="text-primary">${_esc(u.social_link)}</a>
|
||||
<a href="${UI.escape(u.social_link)}" target="_blank" rel="noopener"
|
||||
class="text-primary">${UI.escape(u.social_link)}</a>
|
||||
</div>`
|
||||
: ''}
|
||||
${!u.bio && !u.wohnort && !u.erfahrung && !u.social_link
|
||||
|
|
@ -877,17 +877,17 @@ window.Page_settings = (() => {
|
|||
if (!el || !dogs.length) return;
|
||||
el.innerHTML = dogs.map(d => {
|
||||
const av = d.foto_url
|
||||
? `<img src="${_esc(d.foto_url)}" style="width:36px;height:36px;border-radius:50%;object-fit:cover;flex-shrink:0">`
|
||||
? `<img src="${UI.escape(d.foto_url)}" style="width:36px;height:36px;border-radius:50%;object-fit:cover;flex-shrink:0">`
|
||||
: `<div style="width:36px;height:36px;border-radius:50%;background:var(--c-surface-2);display:flex;align-items:center;justify-content:center;flex-shrink:0">
|
||||
<svg class="ph-icon" style="width:18px;height:18px;color:var(--c-text-muted)" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
|
||||
</div>`;
|
||||
const jahr = d.verstorben_am ? d.verstorben_am.slice(0, 4) : '';
|
||||
return `
|
||||
<div class="sidebar-item settings-erinnerung-btn" data-dog-id="${d.id}" data-dog-name="${_esc(d.name)}"
|
||||
<div class="sidebar-item settings-erinnerung-btn" data-dog-id="${d.id}" data-dog-name="${UI.escape(d.name)}"
|
||||
style="padding:var(--space-3) var(--space-4);border-radius:0;border-bottom:1px solid var(--c-border);cursor:pointer">
|
||||
${av}
|
||||
<div style="display:flex;flex-direction:column;gap:1px;flex:1;min-width:0">
|
||||
<span style="font-weight:600;font-size:var(--text-sm)">${_esc(d.name)}</span>
|
||||
<span style="font-weight:600;font-size:var(--text-sm)">${UI.escape(d.name)}</span>
|
||||
<span class="text-xs-muted">
|
||||
<svg class="ph-icon" style="width:11px;height:11px" aria-hidden="true"><use href="/icons/phosphor.svg#heart-break"></use></svg>
|
||||
Erinnerungen${jahr ? ' · ' + jahr : ''}
|
||||
|
|
@ -1034,7 +1034,7 @@ window.Page_settings = (() => {
|
|||
|
||||
// Alle Stufen als kleine Punkte
|
||||
const dots = (cat.alle_stufen || []).map(s =>
|
||||
`<div title="${_esc(s.name)}" style="width:8px;height:8px;border-radius:50%;
|
||||
`<div title="${UI.escape(s.name)}" style="width:8px;height:8px;border-radius:50%;
|
||||
background:${s.earned ? s.color : 'var(--c-border)'}"></div>`
|
||||
).join('');
|
||||
|
||||
|
|
@ -1046,7 +1046,7 @@ window.Page_settings = (() => {
|
|||
// Fortschrittsbalken
|
||||
const progressBar = nxt ? `
|
||||
<div style="font-size:10px;color:var(--c-text-muted);margin-top:4px">
|
||||
${val}${cat.einheit} / ${nxt.schwelle}${cat.einheit} → ${_esc(nxt.name)}
|
||||
${val}${cat.einheit} / ${nxt.schwelle}${cat.einheit} → ${UI.escape(nxt.name)}
|
||||
</div>
|
||||
<div style="height:4px;background:var(--c-border);border-radius:2px;margin-top:4px;overflow:hidden">
|
||||
<div style="height:100%;width:${cat.progress}%;background:${nxt.color};border-radius:2px;transition:width .4s"></div>
|
||||
|
|
@ -1061,9 +1061,9 @@ window.Page_settings = (() => {
|
|||
${shieldSvg}
|
||||
<div class="flex-1-min">
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px">
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${_esc(cat.name)}</span>
|
||||
<span style="font-weight:700;font-size:var(--text-sm)">${UI.escape(cat.name)}</span>
|
||||
${cur ? `<span style="font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;
|
||||
background:${cur.color};color:${cur.text}">${_esc(cur.name)}</span>` : ''}
|
||||
background:${cur.color};color:${cur.text}">${UI.escape(cur.name)}</span>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:6px">${dots}</div>
|
||||
${progressBar}
|
||||
|
|
@ -1131,7 +1131,7 @@ window.Page_settings = (() => {
|
|||
['trainer', 'Trainer / Ausbilder'],
|
||||
['zuechter', 'Züchter'],
|
||||
].map(([val, label]) =>
|
||||
`<option value="${_esc(val)}" ${u.erfahrung === val ? 'selected' : ''}>${_esc(label)}</option>`
|
||||
`<option value="${UI.escape(val)}" ${u.erfahrung === val ? 'selected' : ''}>${UI.escape(label)}</option>`
|
||||
).join('');
|
||||
|
||||
const sichtbarkeitOpts = [
|
||||
|
|
@ -1139,7 +1139,7 @@ window.Page_settings = (() => {
|
|||
['friends', 'Nur Freunde'],
|
||||
['private', 'Privat'],
|
||||
].map(([val, label]) =>
|
||||
`<option value="${_esc(val)}" ${(u.profil_sichtbarkeit || 'public') === val ? 'selected' : ''}>${_esc(label)}</option>`
|
||||
`<option value="${UI.escape(val)}" ${(u.profil_sichtbarkeit || 'public') === val ? 'selected' : ''}>${UI.escape(label)}</option>`
|
||||
).join('');
|
||||
|
||||
UI.modal.open({
|
||||
|
|
@ -1150,20 +1150,20 @@ window.Page_settings = (() => {
|
|||
<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 || '')}"
|
||||
value="${UI.escape(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"
|
||||
placeholder="Kurze Vorstellung (max. 300 Zeichen)"
|
||||
style="${inputStyle};resize:vertical">${_esc(u.bio || '')}</textarea>
|
||||
style="${inputStyle};resize:vertical">${UI.escape(u.bio || '')}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Wohnort</label>
|
||||
<input name="wohnort" type="text" maxlength="60"
|
||||
placeholder="z.B. München"
|
||||
value="${_esc(u.wohnort || '')}"
|
||||
value="${UI.escape(u.wohnort || '')}"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -1174,7 +1174,7 @@ window.Page_settings = (() => {
|
|||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Social-Link</label>
|
||||
<input name="social_link" type="url" maxlength="120"
|
||||
placeholder="https://instagram.com/dein-hundeaccount"
|
||||
value="${_esc(u.social_link || '')}"
|
||||
value="${UI.escape(u.social_link || '')}"
|
||||
style="${inputStyle}">
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--c-border);padding-top:var(--space-3);margin-top:var(--space-1)">
|
||||
|
|
@ -1182,12 +1182,12 @@ window.Page_settings = (() => {
|
|||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">Wird auf Rechnungen gedruckt. Straße in Zeile 1, PLZ + Ort in Zeile 2.</div>
|
||||
<textarea name="billing_address" rows="2" maxlength="200"
|
||||
placeholder="Musterstraße 1 12345 Berlin"
|
||||
style="${inputStyle};resize:vertical;font-family:inherit">${_esc(u.billing_address || '')}</textarea>
|
||||
style="${inputStyle};resize:vertical;font-family:inherit">${UI.escape(u.billing_address || '')}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Dein Geburtstag <span style="font-weight:400;color:var(--c-text-secondary)">(optional)</span></label>
|
||||
<input name="geburtstag" type="text" maxlength="5" placeholder="TT.MM"
|
||||
value="${_esc(u.geburtstag || '')}"
|
||||
value="${UI.escape(u.geburtstag || '')}"
|
||||
pattern="\\d{2}\\.\\d{2}"
|
||||
title="Format: TT.MM, z.B. 16.05"
|
||||
style="${inputStyle}">
|
||||
|
|
@ -1525,8 +1525,8 @@ window.Page_settings = (() => {
|
|||
return `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;
|
||||
padding:var(--space-2) 0;font-size:var(--text-sm)">
|
||||
<span>${_esc(label)}</span>
|
||||
<button class="by-toggle ki-toggle-btn" data-key="${_esc(key)}"
|
||||
<span>${UI.escape(label)}</span>
|
||||
<button class="by-toggle ki-toggle-btn" data-key="${UI.escape(key)}"
|
||||
data-active="${active ? '1' : '0'}"
|
||||
style="position:relative;display:inline-block;width:44px;height:24px;
|
||||
border:none;border-radius:12px;cursor:pointer;flex-shrink:0;
|
||||
|
|
@ -1572,8 +1572,8 @@ window.Page_settings = (() => {
|
|||
</span>`;
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3);font-size:var(--text-sm);display:flex;flex-direction:column;gap:var(--space-1)">
|
||||
${profile?.zwingername ? `<div class="text-secondary">Zwinger: <strong>${_esc(profile.zwingername)}</strong></div>` : ''}
|
||||
${profile?.rasse_text ? `<div class="text-secondary">Rasse: <strong>${_esc(profile.rasse_text)}</strong></div>` : ''}
|
||||
${profile?.zwingername ? `<div class="text-secondary">Zwinger: <strong>${UI.escape(profile.zwingername)}</strong></div>` : ''}
|
||||
${profile?.rasse_text ? `<div class="text-secondary">Rasse: <strong>${UI.escape(profile.rasse_text)}</strong></div>` : ''}
|
||||
</div>
|
||||
${rolle === 'breeder' && profile ? `
|
||||
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3">
|
||||
|
|
@ -1700,22 +1700,22 @@ window.Page_settings = (() => {
|
|||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Zwingername</label>
|
||||
<input name="zwingername" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.zwingername || '')}">
|
||||
value="${UI.escape(profile?.zwingername || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Rasse</label>
|
||||
<input name="rasse_text" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.rasse_text || '')}">
|
||||
value="${UI.escape(profile?.rasse_text || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Zuchtverein</label>
|
||||
<input name="verein" type="text" maxlength="100" style="${inputStyle}"
|
||||
value="${_esc(profile?.verein || '')}">
|
||||
value="${UI.escape(profile?.verein || '')}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Stadt</label>
|
||||
<input name="stadt" type="text" maxlength="80" style="${inputStyle}"
|
||||
value="${_esc(profile?.stadt || '')}">
|
||||
value="${UI.escape(profile?.stadt || '')}">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3)">
|
||||
<input name="vdh_mitglied" type="checkbox" id="edit-breeder-vdh"
|
||||
|
|
@ -1726,12 +1726,12 @@ window.Page_settings = (() => {
|
|||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Website (optional)</label>
|
||||
<input name="website" type="url" maxlength="200" style="${inputStyle}"
|
||||
value="${_esc(profile?.website || '')}" placeholder="https://mein-zwinger.de">
|
||||
value="${UI.escape(profile?.website || '')}" placeholder="https://mein-zwinger.de">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">Beschreibung (optional)</label>
|
||||
<textarea name="beschreibung" maxlength="500" rows="3"
|
||||
style="${inputStyle};resize:vertical">${_esc(profile?.beschreibung || '')}</textarea>
|
||||
style="${inputStyle};resize:vertical">${UI.escape(profile?.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
|
|
@ -1969,7 +1969,7 @@ window.Page_settings = (() => {
|
|||
// GEDENKSEITE — für verstorbene Hunde
|
||||
// ----------------------------------------------------------
|
||||
async function _openGedenkseite(dogId, dogName) {
|
||||
UI.modal.open({ title: `Erinnerungen an ${_esc(dogName)}`, body: `
|
||||
UI.modal.open({ title: `Erinnerungen an ${UI.escape(dogName)}`, body: `
|
||||
<div style="text-align:center;padding:var(--space-4)">
|
||||
<svg class="ph-icon" style="width:32px;height:32px;color:var(--c-primary);animation:spin 1s linear infinite" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#spinner"></use>
|
||||
|
|
@ -1982,12 +1982,12 @@ window.Page_settings = (() => {
|
|||
|
||||
const d = data;
|
||||
const av = d.dog.foto_url
|
||||
? `<img src="${_esc(d.dog.foto_url)}" style="width:100px;height:100px;border-radius:50%;object-fit:cover;border:3px solid var(--c-primary)">`
|
||||
? `<img src="${UI.escape(d.dog.foto_url)}" style="width:100px;height:100px;border-radius:50%;object-fit:cover;border:3px solid var(--c-primary)">`
|
||||
: `<div style="width:100px;height:100px;border-radius:50%;background:var(--c-primary-subtle);display:flex;align-items:center;justify-content:center;border:3px solid var(--c-primary)"><svg class="ph-icon" style="width:48px;height:48px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg></div>`;
|
||||
|
||||
const photoGrid = d.photos?.length ? `
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin:var(--space-4) 0">
|
||||
${d.photos.map(url => `<img src="${_esc(url)}" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:6px">`).join('')}
|
||||
${d.photos.map(url => `<img src="${UI.escape(url)}" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:6px">`).join('')}
|
||||
</div>` : '';
|
||||
|
||||
const statsHtml = `
|
||||
|
|
@ -2012,11 +2012,11 @@ window.Page_settings = (() => {
|
|||
: '';
|
||||
|
||||
UI.modal.open({
|
||||
title: `Erinnerungen an ${_esc(d.dog.name)}`,
|
||||
title: `Erinnerungen an ${UI.escape(d.dog.name)}`,
|
||||
body: `
|
||||
<div style="text-align:center;margin-bottom:var(--space-4)">
|
||||
${av}
|
||||
<div style="margin-top:var(--space-3);font-size:var(--text-lg);font-weight:700">${_esc(d.dog.name)}</div>
|
||||
<div style="margin-top:var(--space-3);font-size:var(--text-lg);font-weight:700">${UI.escape(d.dog.name)}</div>
|
||||
${passedStr ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:4px">
|
||||
<svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#heart-break"></use></svg>
|
||||
${passedStr}
|
||||
|
|
@ -2033,7 +2033,7 @@ window.Page_settings = (() => {
|
|||
${d.ki_abschied ? `<div style="font-style:italic;font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
line-height:1.7;padding:var(--space-3);background:var(--c-surface);
|
||||
border-radius:var(--radius-md);border:1px solid var(--c-border)">
|
||||
"${_esc(d.ki_abschied)}"
|
||||
"${UI.escape(d.ki_abschied)}"
|
||||
</div>` : ''}
|
||||
`,
|
||||
});
|
||||
|
|
@ -2591,11 +2591,6 @@ window.Page_settings = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
|
|
|
|||
|
|
@ -242,13 +242,13 @@ window.Page_social = (() => {
|
|||
🌙 Noch nicht gezeigt:
|
||||
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:6px">
|
||||
${_unusedBreeds.map(b =>
|
||||
`<button class="sm-breed-chip" data-id="${b.id}" data-name="${_esc(b.name)}"
|
||||
`<button class="sm-breed-chip" data-id="${b.id}" data-name="${UI.escape(b.name)}"
|
||||
style="padding:5px 12px;border-radius:var(--radius-full);
|
||||
border:1.5px solid var(--c-border);
|
||||
background:var(--c-surface-2);color:var(--c-text);
|
||||
font-size:12px;cursor:pointer;font-family:inherit;
|
||||
transition:all var(--transition-fast)">
|
||||
${_esc(b.name)}</button>`).join('')}
|
||||
${UI.escape(b.name)}</button>`).join('')}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
<input id="sm-breed-search" list="sm-breed-list"
|
||||
|
|
@ -259,7 +259,7 @@ window.Page_social = (() => {
|
|||
padding:9px 12px;font-size:var(--text-sm);
|
||||
font-family:inherit;box-sizing:border-box">
|
||||
<datalist id="sm-breed-list">
|
||||
${_breeds.map(b => `<option value="${_esc(b.name)}" data-id="${b.id}">`).join('')}
|
||||
${_breeds.map(b => `<option value="${UI.escape(b.name)}" data-id="${b.id}">`).join('')}
|
||||
</datalist>
|
||||
<input type="hidden" id="sm-breed-id">
|
||||
</div>
|
||||
|
|
@ -414,9 +414,9 @@ window.Page_social = (() => {
|
|||
<span style="font-size:1.4em;flex-shrink:0;line-height:1.2">${idea.emoji||'💡'}</span>
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:4px;
|
||||
line-height:1.3">${_esc(idea.thema)}</div>
|
||||
line-height:1.3">${UI.escape(idea.thema)}</div>
|
||||
<div style="font-size:11px;color:var(--c-text-secondary);margin-bottom:6px;
|
||||
line-height:1.4">🎓 ${_esc(idea.warum)}</div>
|
||||
line-height:1.4">🎓 ${UI.escape(idea.warum)}</div>
|
||||
<div style="display:flex;gap:6px">
|
||||
<span style="font-size:10px;background:var(--c-surface-2);
|
||||
padding:2px 6px;border-radius:4px">${_FL[idea.format]||idea.format}</span>
|
||||
|
|
@ -427,16 +427,16 @@ window.Page_social = (() => {
|
|||
<div style="display:flex;flex-direction:column;gap:5px;flex-shrink:0">
|
||||
<button class="btn btn-primary btn-sm sm-use"
|
||||
style="font-size:11px;padding:6px 10px;min-height:34px;white-space:nowrap"
|
||||
data-thema="${_esc(idea.thema)}"
|
||||
data-thema="${UI.escape(idea.thema)}"
|
||||
data-format="${idea.format||'post'}"
|
||||
data-platform="${idea.platform||'both'}">
|
||||
Nutzen →</button>
|
||||
<button class="btn btn-secondary btn-sm sm-save-idea"
|
||||
style="font-size:10px;padding:4px 8px;min-height:26px;white-space:nowrap"
|
||||
data-thema="${_esc(idea.thema)}"
|
||||
data-thema="${UI.escape(idea.thema)}"
|
||||
data-format="${idea.format||'post'}"
|
||||
data-platform="${idea.platform||'both'}"
|
||||
data-category="${_esc(idea.category||'')}">
|
||||
data-category="${UI.escape(idea.category||'')}">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#push-pin"></use></svg> Merken</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -522,15 +522,15 @@ window.Page_social = (() => {
|
|||
margin-bottom:6px;display:flex;align-items:center;gap:10px">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-size:var(--text-sm);font-weight:600;color:var(--c-text)">
|
||||
${_esc(e.name)}</div>
|
||||
${UI.escape(e.name)}</div>
|
||||
<div style="font-size:10px;color:var(--c-text-muted)">
|
||||
${_esc(e.schwierigkeit||'')} · ${_esc(e.alter_ab||'')} · ${_esc(e.dauer||'')}</div>
|
||||
${UI.escape(e.schwierigkeit||'')} · ${UI.escape(e.alter_ab||'')} · ${UI.escape(e.dauer||'')}</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;flex-shrink:0">
|
||||
${e.posts_count > 0 ? `<span style="font-size:10px;background:#10b981;
|
||||
color:#fff;padding:1px 6px;border-radius:4px"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#check"></use></svg> ${e.posts_count}x</span>` : ''}
|
||||
<button class="btn btn-sm btn-primary sm-ex-use"
|
||||
data-id="${e.exercise_id}" data-name="${_esc(e.name)}"
|
||||
data-id="${e.exercise_id}" data-name="${UI.escape(e.name)}"
|
||||
style="font-size:11px;padding:4px 10px;min-height:28px">
|
||||
Nutzen</button>
|
||||
</div>
|
||||
|
|
@ -580,9 +580,9 @@ window.Page_social = (() => {
|
|||
<span style="font-size:2.2em;flex-shrink:0">🎾</span>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#4ade80;font-weight:600;margin-bottom:2px">
|
||||
Trainingstipp · ${_esc(data.exercise_kat||'')} · ${stilLabel}</div>
|
||||
Trainingstipp · ${UI.escape(data.exercise_kat||'')} · ${stilLabel}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-base);color:#15803d">
|
||||
${_esc(data.exercise_name||'')}</div>
|
||||
${UI.escape(data.exercise_name||'')}</div>
|
||||
</div>
|
||||
</div>
|
||||
${_renderResult(data, null)}`;
|
||||
|
|
@ -592,7 +592,7 @@ window.Page_social = (() => {
|
|||
} catch(e) {
|
||||
clearInterval(interval.bar); clearInterval(interval.msg);
|
||||
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
||||
😬 ${_esc(e.message||String(e))}</div>`;
|
||||
😬 ${UI.escape(e.message||String(e))}</div>`;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
|
@ -620,10 +620,10 @@ window.Page_social = (() => {
|
|||
<span style="font-size:2.2em;flex-shrink:0">🛁</span>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#c084fc;font-weight:600;margin-bottom:2px">
|
||||
Pflegetipp · ${_esc(data.pflege_kat||'')}
|
||||
${data.rasse_name ? ` · speziell für ${_esc(data.rasse_name)}` : ''}</div>
|
||||
Pflegetipp · ${UI.escape(data.pflege_kat||'')}
|
||||
${data.rasse_name ? ` · speziell für ${UI.escape(data.rasse_name)}` : ''}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-base);color:#7c3aed">
|
||||
${_esc(data.pflege_titel||'')}</div>
|
||||
${UI.escape(data.pflege_titel||'')}</div>
|
||||
</div>
|
||||
</div>
|
||||
${_renderResult(data, null)}`;
|
||||
|
|
@ -633,7 +633,7 @@ window.Page_social = (() => {
|
|||
} catch(e) {
|
||||
clearInterval(interval.bar); clearInterval(interval.msg);
|
||||
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
||||
😬 ${_esc(e.message||String(e))}</div>`;
|
||||
😬 ${UI.escape(e.message||String(e))}</div>`;
|
||||
} finally { btn.disabled = false; }
|
||||
});
|
||||
|
||||
|
|
@ -663,7 +663,7 @@ window.Page_social = (() => {
|
|||
<div style="font-size:11px;color:var(--c-primary);font-weight:600;margin-bottom:2px">
|
||||
Rasse des Tages</div>
|
||||
<div style="font-weight:700;font-size:var(--text-base);color:var(--c-primary-dark)">
|
||||
${_esc(data.topic?.replace('Rasse des Tages: ',''))}</div>
|
||||
${UI.escape(data.topic?.replace('Rasse des Tages: ',''))}</div>
|
||||
</div>
|
||||
</div>
|
||||
${_renderResult(data, mediaUrl)}`;
|
||||
|
|
@ -675,7 +675,7 @@ window.Page_social = (() => {
|
|||
} catch(e) {
|
||||
clearInterval(interval.bar); clearInterval(interval.msg);
|
||||
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
||||
😬 ${_esc(e.message||String(e))}</div>`;
|
||||
😬 ${UI.escape(e.message||String(e))}</div>`;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
|
@ -719,7 +719,7 @@ window.Page_social = (() => {
|
|||
clearInterval(interval);
|
||||
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3);
|
||||
border-radius:8px;background:var(--c-surface-2)">
|
||||
😬 Ups: ${_esc(e.message||String(e))}</div>`;
|
||||
😬 Ups: ${UI.escape(e.message||String(e))}</div>`;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Los geht\'s!';
|
||||
|
|
@ -816,7 +816,7 @@ window.Page_social = (() => {
|
|||
<div>
|
||||
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
|
||||
text-transform:uppercase;letter-spacing:.5px">Luna sagt:</div>
|
||||
<div style="font-size:var(--text-sm);line-height:1.6;color:var(--c-text)">${_esc(data.coaching)}</div>
|
||||
<div style="font-size:var(--text-sm);line-height:1.6;color:var(--c-text)">${UI.escape(data.coaching)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
|
@ -903,9 +903,9 @@ window.Page_social = (() => {
|
|||
${data.hook ? `<div class="sm-label">🎣 Hook</div>
|
||||
<div style="font-size:var(--text-sm);font-style:italic;margin-bottom:var(--space-3);
|
||||
line-height:1.6">
|
||||
"${_esc(data.hook)}"</div>` : ''}
|
||||
"${UI.escape(data.hook)}"</div>` : ''}
|
||||
${data.cta ? `<div class="sm-label">📣 Call-to-Action</div>
|
||||
<div style="font-size:var(--text-sm);line-height:1.6">${_esc(data.cta)}</div>` : ''}
|
||||
<div style="font-size:var(--text-sm);line-height:1.6">${UI.escape(data.cta)}</div>` : ''}
|
||||
</div>` : ''}
|
||||
${_resultBlock('📸 Was du filmen/fotografieren solltest', data.visual_brief, false)}
|
||||
${data.script ? `
|
||||
|
|
@ -914,7 +914,7 @@ window.Page_social = (() => {
|
|||
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
||||
<div class="sm-label">🎬 Video-Aufbau</div>
|
||||
<div style="font-size:var(--text-sm);white-space:pre-wrap;
|
||||
line-height:1.7">${_esc(data.script)}</div>
|
||||
line-height:1.7">${UI.escape(data.script)}</div>
|
||||
</div>` : ''}
|
||||
${(data.image_prompt||data.canva_notes||unsplash) ? `
|
||||
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
||||
|
|
@ -926,12 +926,12 @@ window.Page_social = (() => {
|
|||
DALL-E / Midjourney:</div>
|
||||
<div style="font-size:11px;background:var(--c-surface-2);padding:10px;
|
||||
border-radius:var(--radius-md);font-family:monospace;word-break:break-word;
|
||||
margin-bottom:var(--space-3);line-height:1.5">${_esc(data.image_prompt)}</div>
|
||||
margin-bottom:var(--space-3);line-height:1.5">${UI.escape(data.image_prompt)}</div>
|
||||
${_copyBtn(data.image_prompt)}` : ''}
|
||||
${data.canva_notes ? `
|
||||
<div style="font-size:11px;color:var(--c-text-muted);margin:var(--space-3) 0 6px">Canva:</div>
|
||||
<div style="font-size:var(--text-sm);margin-bottom:var(--space-3);
|
||||
line-height:1.6">${_esc(data.canva_notes)}</div>` : ''}
|
||||
line-height:1.6">${UI.escape(data.canva_notes)}</div>` : ''}
|
||||
${unsplash ? `<a href="${unsplash}" target="_blank" rel="noopener"
|
||||
style="font-size:var(--text-sm);color:var(--c-primary);display:inline-block">
|
||||
🔍 Kostenlose Fotos auf Unsplash →</a>` : ''}
|
||||
|
|
@ -945,14 +945,14 @@ window.Page_social = (() => {
|
|||
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
||||
<div class="sm-label">${label}</div>
|
||||
<div style="font-size:var(--text-sm);white-space:pre-wrap;line-height:1.7;
|
||||
margin-bottom:${copyable?'var(--space-3)':'0'}">${_esc(text)}</div>
|
||||
margin-bottom:${copyable?'var(--space-3)':'0'}">${UI.escape(text)}</div>
|
||||
${copyable ? _copyBtn(text) : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function _copyBtn(text) {
|
||||
return `<button class="btn btn-sm btn-secondary sm-copy"
|
||||
data-copy="${_esc(text)}"
|
||||
data-copy="${UI.escape(text)}"
|
||||
style="font-size:11px;padding:5px 14px;min-height:32px;
|
||||
border-radius:var(--radius-full)">
|
||||
📋 Kopieren</button>`;
|
||||
|
|
@ -1012,7 +1012,7 @@ window.Page_social = (() => {
|
|||
<div style="text-align:center;padding:8px;color:var(--c-success);
|
||||
font-weight:600;font-size:var(--text-sm)">
|
||||
🎉 Super! Post als veröffentlicht markiert.
|
||||
${url ? `<br><a href="${_esc(url)}" target="_blank" rel="noopener"
|
||||
${url ? `<br><a href="${UI.escape(url)}" target="_blank" rel="noopener"
|
||||
style="font-size:11px;color:var(--c-primary)">Post ansehen →</a>` : ''}
|
||||
</div>`;
|
||||
// Stats aktualisieren
|
||||
|
|
@ -1073,7 +1073,7 @@ window.Page_social = (() => {
|
|||
<div style="font-size:11px;color:#000;line-height:1.4;
|
||||
display:-webkit-box;-webkit-line-clamp:3;
|
||||
-webkit-box-orient:vertical;overflow:hidden">
|
||||
${_esc((item.caption||'').substring(0,150))}${(item.caption||'').length>150?'…':''}</div>
|
||||
${UI.escape((item.caption||'').substring(0,150))}${(item.caption||'').length>150?'…':''}</div>
|
||||
${item.hashtags ? `<div style="font-size:10px;color:#00376b;margin-top:4px;
|
||||
word-break:break-word;display:-webkit-box;-webkit-line-clamp:2;
|
||||
-webkit-box-orient:vertical;overflow:hidden">
|
||||
|
|
@ -1156,11 +1156,11 @@ window.Page_social = (() => {
|
|||
${c.ai_score ? `<span style="font-size:10px">${'⭐'.repeat(c.ai_score)}</span>` : ''}
|
||||
</div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:3px;
|
||||
line-height:1.3">${_esc(c.topic)}</div>
|
||||
line-height:1.3">${UI.escape(c.topic)}</div>
|
||||
${c.hook ? `<div style="font-size:11px;color:var(--c-text-secondary);
|
||||
font-style:italic;margin-bottom:2px">🎣 ${_esc(c.hook)}</div>` : ''}
|
||||
font-style:italic;margin-bottom:2px">🎣 ${UI.escape(c.hook)}</div>` : ''}
|
||||
${c.post_url
|
||||
? `<a href="${_esc(c.post_url)}" target="_blank" rel="noopener"
|
||||
? `<a href="${UI.escape(c.post_url)}" target="_blank" rel="noopener"
|
||||
style="font-size:10px;color:var(--c-primary);display:inline-flex;
|
||||
align-items:center;gap:3px;margin-top:2px">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#link-simple"></use></svg> Post ansehen</a>`
|
||||
|
|
@ -1195,10 +1195,10 @@ window.Page_social = (() => {
|
|||
border-top:1px solid var(--c-border);padding-top:10px">
|
||||
${c.coaching ? `<div style="background:var(--c-surface-2);border-radius:8px;
|
||||
padding:10px;margin-bottom:10px;font-size:11px;line-height:1.5">
|
||||
🌙 ${_esc(c.coaching)}</div>` : ''}
|
||||
🌙 ${UI.escape(c.coaching)}</div>` : ''}
|
||||
${c.caption ? `<div style="font-size:11px;color:var(--c-text-muted);margin-bottom:2px">Caption:</div>
|
||||
<div style="font-size:var(--text-sm);white-space:pre-wrap;line-height:1.5;
|
||||
margin-bottom:8px">${_esc(c.caption)}</div>
|
||||
margin-bottom:8px">${UI.escape(c.caption)}</div>
|
||||
${_copyBtn(c.caption)}` : ''}
|
||||
${c.hashtags ? `<div style="font-size:11px;color:var(--c-primary);margin-top:8px;
|
||||
word-break:break-word">
|
||||
|
|
@ -1375,7 +1375,7 @@ window.Page_social = (() => {
|
|||
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
|
||||
text-transform:uppercase;letter-spacing:.5px">
|
||||
Lunas Feedback:</div>
|
||||
<div style="font-size:var(--text-sm);line-height:1.6">${_esc(data.notes)}</div>
|
||||
<div style="font-size:var(--text-sm);line-height:1.6">${UI.escape(data.notes)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
|
@ -1384,7 +1384,7 @@ window.Page_social = (() => {
|
|||
API.get('/social/stats').then(s => { _stats = s; });
|
||||
} catch(e) {
|
||||
clearInterval(interval);
|
||||
res.innerHTML = `<div class="text-danger">😬 Fehler: ${_esc(e.message||String(e))}</div>`;
|
||||
res.innerHTML = `<div class="text-danger">😬 Fehler: ${UI.escape(e.message||String(e))}</div>`;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '🔍 Luna, schau mal drüber!';
|
||||
|
|
@ -1404,12 +1404,6 @@ window.Page_social = (() => {
|
|||
return picked;
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<')
|
||||
.replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// CSS
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
|
|
|
|||
|
|
@ -26,16 +26,7 @@ window.Page_trainingsplaene = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function _icon(name) {
|
||||
function _icon(name) {
|
||||
return `<svg class="ph-icon" aria-hidden="true" style="width:1em;height:1em;vertical-align:-0.15em"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
|
||||
}
|
||||
|
||||
|
|
@ -57,9 +48,9 @@ window.Page_trainingsplaene = (() => {
|
|||
// ----------------------------------------------------------
|
||||
|
||||
function _renderTable(headers, rows) {
|
||||
const ths = headers.map(h => `<th style="padding:6px 10px;background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);white-space:nowrap">${_esc(h)}</th>`).join('');
|
||||
const ths = headers.map(h => `<th style="padding:6px 10px;background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);white-space:nowrap">${UI.escape(h)}</th>`).join('');
|
||||
const trs = rows.map(row => {
|
||||
const tds = row.map((cell, i) => `<td style="padding:6px 10px;font-size:var(--text-sm);color:var(--c-text);border-top:1px solid var(--c-border);${i === 0 ? 'white-space:nowrap;font-weight:var(--weight-semibold)' : ''}">${_esc(cell)}</td>`).join('');
|
||||
const tds = row.map((cell, i) => `<td style="padding:6px 10px;font-size:var(--text-sm);color:var(--c-text);border-top:1px solid var(--c-border);${i === 0 ? 'white-space:nowrap;font-weight:var(--weight-semibold)' : ''}">${UI.escape(cell)}</td>`).join('');
|
||||
return `<tr>${tds}</tr>`;
|
||||
}).join('');
|
||||
return `
|
||||
|
|
@ -80,15 +71,15 @@ window.Page_trainingsplaene = (() => {
|
|||
if (checked) doneCount++;
|
||||
return `
|
||||
<label style="display:flex;align-items:flex-start;gap:var(--space-2);cursor:pointer;padding:var(--space-1) 0" class="tp-goal-label">
|
||||
<input type="checkbox" data-lskey="${_esc(key)}" ${checked ? 'checked' : ''}
|
||||
<input type="checkbox" data-lskey="${UI.escape(key)}" ${checked ? 'checked' : ''}
|
||||
style="margin-top:2px;flex-shrink:0;accent-color:var(--c-primary);width:16px;height:16px">
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${_esc(goal)}</span>
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${UI.escape(goal)}</span>
|
||||
</label>`;
|
||||
}).join('');
|
||||
|
||||
const progress = total > 0 ? Math.round((doneCount / total) * 100) : 0;
|
||||
return `
|
||||
<div class="tp-goals" data-plan="${_esc(planId)}" data-total="${total}">
|
||||
<div class="tp-goals" data-plan="${UI.escape(planId)}" data-total="${total}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-2)">
|
||||
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">
|
||||
${_icon('check-circle')} Lernziele
|
||||
|
|
@ -106,13 +97,13 @@ window.Page_trainingsplaene = (() => {
|
|||
|
||||
function _renderAccordionPhase(id, title, content) {
|
||||
return `
|
||||
<div class="tp-acc" id="tp-acc-${_esc(id)}">
|
||||
<button class="tp-acc-head" data-acc="${_esc(id)}"
|
||||
<div class="tp-acc" id="tp-acc-${UI.escape(id)}">
|
||||
<button class="tp-acc-head" data-acc="${UI.escape(id)}"
|
||||
style="width:100%;display:flex;justify-content:space-between;align-items:center;padding:var(--space-3) var(--space-4);background:none;border:none;border-top:1px solid var(--c-border);cursor:pointer;text-align:left">
|
||||
<span style="font-weight:var(--weight-semibold);color:var(--c-text)">${title}</span>
|
||||
<span class="tp-acc-arrow">${_icon('caret-down')}</span>
|
||||
</button>
|
||||
<div class="tp-acc-body" id="tp-acc-body-${_esc(id)}" hidden
|
||||
<div class="tp-acc-body" id="tp-acc-body-${UI.escape(id)}" hidden
|
||||
style="padding:var(--space-3) var(--space-4) var(--space-4)">
|
||||
${content}
|
||||
</div>
|
||||
|
|
@ -122,7 +113,7 @@ window.Page_trainingsplaene = (() => {
|
|||
function _renderHintBox(text) {
|
||||
return `
|
||||
<div style="background:var(--c-surface-2);border-left:3px solid var(--c-primary);border-radius:var(--radius-sm);padding:var(--space-3) var(--space-4);margin:var(--space-3) 0;font-size:var(--text-sm);color:var(--c-text);line-height:1.6">
|
||||
${_icon('info')} ${_esc(text)}
|
||||
${_icon('info')} ${UI.escape(text)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +132,7 @@ window.Page_trainingsplaene = (() => {
|
|||
justify-content:center;gap:2px;white-space:normal;text-align:center;line-height:1.2">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:22px;height:22px"><use href="/icons/phosphor.svg#${p.icon}"></use></svg>
|
||||
<span style="font-size:var(--text-sm);font-weight:600">${p.label}</span>
|
||||
<span style="font-size:var(--text-xs);opacity:0.8">${_esc(p.sub)}</span>
|
||||
<span style="font-size:var(--text-xs);opacity:0.8">${UI.escape(p.sub)}</span>
|
||||
</button>`).join('');
|
||||
return `<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-4);flex-wrap:wrap">${btns}</div>`;
|
||||
}
|
||||
|
|
@ -764,7 +755,7 @@ window.Page_trainingsplaene = (() => {
|
|||
<div style="flex:1;min-width:220px">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);margin-bottom:var(--space-2)">
|
||||
${_esc(MONTHS[month - 1])} ${year}
|
||||
${UI.escape(MONTHS[month - 1])} ${year}
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:3px">
|
||||
${wdayHeader}
|
||||
|
|
@ -793,7 +784,7 @@ window.Page_trainingsplaene = (() => {
|
|||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@ window.Page_uebungen = (() => {
|
|||
border-radius:var(--radius-full,999px);
|
||||
background:var(--c-primary-subtle);color:var(--c-primary);
|
||||
font-size:var(--text-xs);font-weight:var(--weight-semibold)">
|
||||
${b.icon || '🏅'} ${_esc(b.name || b.badge_id)}
|
||||
${b.icon || '🏅'} ${UI.escape(b.name || b.badge_id)}
|
||||
</span>
|
||||
`).join('');
|
||||
const more = rest > 0
|
||||
|
|
@ -715,8 +715,8 @@ window.Page_uebungen = (() => {
|
|||
text-transform:uppercase;letter-spacing:.04em">${cfg.label}</span>
|
||||
</div>
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);line-height:1.3">${_esc(r.exercise_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.4">${_esc(r.reason)}</div>
|
||||
color:var(--c-text);line-height:1.3">${UI.escape(r.exercise_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.4">${UI.escape(r.reason)}</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);margin-top:auto;padding-top:var(--space-1)">
|
||||
<div>
|
||||
<span class="text-xs-secondary">${r.suggested_reps}× empfohlen</span>
|
||||
|
|
@ -724,7 +724,7 @@ window.Page_uebungen = (() => {
|
|||
${prognose}
|
||||
</div>
|
||||
<button class="ueb-trainer-btn btn btn-primary"
|
||||
data-tab="${_esc(r.tab)}" data-name="${_esc(r.exercise_name)}" data-reps="${r.suggested_reps}"
|
||||
data-tab="${UI.escape(r.tab)}" data-name="${UI.escape(r.exercise_name)}" data-reps="${r.suggested_reps}"
|
||||
style="font-size:var(--text-xs);padding:4px 10px;width:100%">
|
||||
Üben
|
||||
</button>
|
||||
|
|
@ -817,25 +817,25 @@ window.Page_uebungen = (() => {
|
|||
${ALL.map(g => `
|
||||
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--c-text-secondary);
|
||||
text-transform:uppercase;letter-spacing:.05em;padding:var(--space-3) 0 var(--space-1)">
|
||||
${_esc(g.group)}
|
||||
${UI.escape(g.group)}
|
||||
</div>
|
||||
${g.items.map(u => {
|
||||
const key = _progressKey(g.tab, u.name);
|
||||
const cur = _getStatus(g.tab, u.name);
|
||||
return `
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);
|
||||
padding:var(--space-2) 0;border-bottom:1px solid var(--c-border)" data-key="${_esc(key)}">
|
||||
padding:var(--space-2) 0;border-bottom:1px solid var(--c-border)" data-key="${UI.escape(key)}">
|
||||
<span style="flex:1;font-size:var(--text-sm);color:var(--c-text);min-width:0;
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${_esc(u.name)}</span>
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${UI.escape(u.name)}</span>
|
||||
<div style="display:flex;gap:3px;flex-shrink:0">
|
||||
${STATUS_QUICK.map(s => `
|
||||
<button class="qs-btn" data-key="${_esc(key)}" data-val="${s.id === null ? '' : _esc(s.id)}"
|
||||
<button class="qs-btn" data-key="${UI.escape(key)}" data-val="${s.id === null ? '' : UI.escape(s.id)}"
|
||||
style="font-size:9px;font-weight:700;padding:3px 6px;border-radius:999px;cursor:pointer;
|
||||
white-space:nowrap;transition:all .15s;
|
||||
background:${cur === s.id ? s.bg : 'var(--c-surface-2)'};
|
||||
color:${cur === s.id ? s.color : 'var(--c-text-secondary)'};
|
||||
border:1px solid ${cur === s.id ? s.color + '66' : 'var(--c-border)'}">
|
||||
${_esc(s.label)}
|
||||
${UI.escape(s.label)}
|
||||
</button>`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -899,7 +899,7 @@ window.Page_uebungen = (() => {
|
|||
${TABS.map(t => `
|
||||
<button class="by-tab${t.id === _activeTab ? ' active' : ''}"
|
||||
data-tab="${t.id}">
|
||||
${_esc(t.label)}
|
||||
${UI.escape(t.label)}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
|
@ -922,18 +922,18 @@ window.Page_uebungen = (() => {
|
|||
return `
|
||||
<div style="background:${c.bg};border:1px solid ${c.border};border-radius:var(--radius-md);
|
||||
padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-3);align-items:flex-start;cursor:pointer"
|
||||
data-action-tab="${_esc(s.action_tab || '')}"
|
||||
data-action-name="${_esc(s.action_name || '')}"
|
||||
data-action-tab="${UI.escape(s.action_tab || '')}"
|
||||
data-action-name="${UI.escape(s.action_name || '')}"
|
||||
class="ueb-suggestion-card">
|
||||
<svg class="ph-icon" style="width:20px;height:20px;flex-shrink:0;color:${c.text};margin-top:2px" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${_esc(s.icon)}"></use>
|
||||
<use href="/icons/phosphor.svg#${UI.escape(s.icon)}"></use>
|
||||
</svg>
|
||||
<div style="min-width:0">
|
||||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:${c.text};margin-bottom:2px">
|
||||
${_esc(s.title)}
|
||||
${UI.escape(s.title)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">
|
||||
${_esc(s.text)}
|
||||
${UI.escape(s.text)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1074,7 +1074,7 @@ window.Page_uebungen = (() => {
|
|||
);
|
||||
if (!allExercises.length) {
|
||||
return `<div style="padding:var(--space-8);text-align:center;color:var(--c-text-muted)">
|
||||
${UI.icon('magnifying-glass')} Keine Übungen gefunden für „${_esc(q)}"
|
||||
${UI.icon('magnifying-glass')} Keine Übungen gefunden für „${UI.escape(q)}"
|
||||
</div>`;
|
||||
}
|
||||
return `<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
|
|
@ -1082,7 +1082,7 @@ window.Page_uebungen = (() => {
|
|||
<div>
|
||||
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.05em;
|
||||
margin-bottom:var(--space-1);padding:0 var(--space-2)">${_esc(u.kategorie)}</div>
|
||||
margin-bottom:var(--space-1);padding:0 var(--space-2)">${UI.escape(u.kategorie)}</div>
|
||||
${_renderCard(u, i)}
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
|
|
@ -1114,25 +1114,25 @@ window.Page_uebungen = (() => {
|
|||
const hasBody = (u.schritte?.length > 0) || (u.fehler?.length > 0) || !!u.steigerung || !!u.tipp;
|
||||
|
||||
return `
|
||||
<div class="card" style="padding:0;overflow:hidden" data-exercise-name="${_esc(u.name)}" data-exercise-id="${_esc(_progressKey(_activeTab, u.name))}">
|
||||
<div class="card" style="padding:0;overflow:hidden" data-exercise-name="${UI.escape(u.name)}" data-exercise-id="${UI.escape(_progressKey(_activeTab, u.name))}">
|
||||
<!-- Header -->
|
||||
<div style="padding:var(--space-4) var(--space-4) var(--space-3)">
|
||||
<!-- Zeile 1: Name + Schwierigkeits-Badge -->
|
||||
<div style="display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-2);margin-bottom:var(--space-2)">
|
||||
<span style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text);line-height:1.3">
|
||||
${_esc(u.name)}
|
||||
${UI.escape(u.name)}
|
||||
</span>
|
||||
<span style="font-size:var(--text-xs);font-weight:var(--weight-semibold);white-space:nowrap;
|
||||
padding:2px var(--space-2);border-radius:var(--radius-sm);flex-shrink:0;
|
||||
background:${diff.color}22;color:${diff.color}">
|
||||
${_esc(diff.label)}
|
||||
${UI.escape(diff.label)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Zeile 2: Aktions-Buttons -->
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2);flex-wrap:wrap">
|
||||
<button class="ueb-log-btn"
|
||||
data-tab="${_esc(_activeTab)}"
|
||||
data-name="${_esc(u.name)}"
|
||||
data-tab="${UI.escape(_activeTab)}"
|
||||
data-name="${UI.escape(u.name)}"
|
||||
title="Trainingseinheit loggen"
|
||||
style="background:var(--c-primary-subtle);border:1px solid var(--c-primary-light);
|
||||
color:var(--c-primary);cursor:pointer;padding:3px 9px;
|
||||
|
|
@ -1146,8 +1146,8 @@ window.Page_uebungen = (() => {
|
|||
</button>
|
||||
${_sessionStatsChip(_activeTab, u.name)}
|
||||
<button class="ueb-notiz-btn"
|
||||
data-tab="${_esc(_activeTab)}"
|
||||
data-name="${_esc(u.name)}"
|
||||
data-tab="${UI.escape(_activeTab)}"
|
||||
data-name="${UI.escape(u.name)}"
|
||||
title="Notiz hinzufügen"
|
||||
style="background:none;border:1px solid var(--c-border);cursor:pointer;
|
||||
padding:3px 7px;border-radius:var(--radius-sm);
|
||||
|
|
@ -1159,9 +1159,9 @@ window.Page_uebungen = (() => {
|
|||
Notiz
|
||||
</button>
|
||||
<button class="ueb-status-btn"
|
||||
data-tab="${_esc(_activeTab)}"
|
||||
data-name="${_esc(u.name)}"
|
||||
title="${_esc(sm.label)}"
|
||||
data-tab="${UI.escape(_activeTab)}"
|
||||
data-name="${UI.escape(u.name)}"
|
||||
title="${UI.escape(sm.label)}"
|
||||
style="background:none;border:none;cursor:pointer;padding:2px;
|
||||
display:flex;align-items:center;gap:4px;
|
||||
font-size:var(--text-xs);color:${sm.color};
|
||||
|
|
@ -1170,7 +1170,7 @@ window.Page_uebungen = (() => {
|
|||
<use href="/icons/phosphor.svg#${sm.icon}"></use>
|
||||
</svg>
|
||||
<span style="font-size:10px;font-weight:var(--weight-semibold);white-space:nowrap">
|
||||
${_esc(sm.label)}
|
||||
${UI.escape(sm.label)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -1178,20 +1178,20 @@ window.Page_uebungen = (() => {
|
|||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:${u.beschreibung ? 'var(--space-3)' : '0'}">
|
||||
<span class="text-xs-secondary">
|
||||
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#clock"></use></svg>
|
||||
${_esc(u.dauer)}
|
||||
${UI.escape(u.dauer)}
|
||||
</span>
|
||||
<span class="text-xs-secondary">
|
||||
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
|
||||
${_esc(u.alter)}
|
||||
${UI.escape(u.alter)}
|
||||
</span>
|
||||
<span class="text-xs-secondary">
|
||||
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#package"></use></svg>
|
||||
${_esc(u.material)}
|
||||
${UI.escape(u.material)}
|
||||
</span>
|
||||
</div>
|
||||
${u.beschreibung ? `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.5;margin:0">
|
||||
${_esc(u.beschreibung)}
|
||||
${UI.escape(u.beschreibung)}
|
||||
</p>
|
||||
` : ''}
|
||||
${u.hinweis ? `
|
||||
|
|
@ -1200,7 +1200,7 @@ window.Page_uebungen = (() => {
|
|||
font-size:var(--text-xs);color:var(--c-text);line-height:1.4;
|
||||
display:flex;align-items:flex-start;gap:var(--space-2)">
|
||||
<svg class="ph-icon" style="width:13px;height:13px;flex-shrink:0;margin-top:1px;color:var(--c-warning)" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>
|
||||
<span>${_esc(u.hinweis)}</span>
|
||||
<span>${UI.escape(u.hinweis)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
|
@ -1225,7 +1225,7 @@ window.Page_uebungen = (() => {
|
|||
</p>
|
||||
<ol style="margin:0 0 var(--space-4);padding-left:var(--space-5);display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${u.schritte.map(s => `
|
||||
<li style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${_esc(s)}</li>
|
||||
<li style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${UI.escape(s)}</li>
|
||||
`).join('')}
|
||||
</ol>
|
||||
` : ''}
|
||||
|
|
@ -1237,7 +1237,7 @@ window.Page_uebungen = (() => {
|
|||
</p>
|
||||
<ul style="margin:0 0 var(--space-4);padding-left:var(--space-5);display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${u.fehler.map(f => `
|
||||
<li style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.5">${_esc(f)}</li>
|
||||
<li style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.5">${UI.escape(f)}</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
` : ''}
|
||||
|
|
@ -1247,14 +1247,14 @@ window.Page_uebungen = (() => {
|
|||
<svg class="ph-icon" style="width:14px;height:14px;color:var(--c-primary)" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#arrow-right"></use>
|
||||
</svg>
|
||||
<strong>Steigerung:</strong> ${_esc(u.steigerung)}
|
||||
<strong>Steigerung:</strong> ${UI.escape(u.steigerung)}
|
||||
</div>
|
||||
` : ''}
|
||||
${u.tipp ? `
|
||||
<div style="margin-top:var(--space-3);padding:var(--space-2) var(--space-3);
|
||||
background:var(--c-primary-subtle);border-radius:var(--radius-sm);
|
||||
font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
💡 ${_esc(u.tipp)}
|
||||
💡 ${UI.escape(u.tipp)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
|
|
@ -1281,7 +1281,7 @@ window.Page_uebungen = (() => {
|
|||
<svg class="ph-icon" style="width:18px;height:18px;flex-shrink:0" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${sm.icon}"></use>
|
||||
</svg>
|
||||
${next ? `<span style="font-size:10px;font-weight:var(--weight-semibold);white-space:nowrap">${_esc(sm.label)}</span>` : ''}
|
||||
${next ? `<span style="font-size:10px;font-weight:var(--weight-semibold);white-space:nowrap">${UI.escape(sm.label)}</span>` : ''}
|
||||
`;
|
||||
});
|
||||
});
|
||||
|
|
@ -1344,7 +1344,7 @@ window.Page_uebungen = (() => {
|
|||
|
||||
<h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text);
|
||||
margin:0 0 var(--space-4);text-align:center">
|
||||
Notiz: ${_esc(exerciseName)}
|
||||
Notiz: ${UI.escape(exerciseName)}
|
||||
</h3>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
|
|
@ -1359,7 +1359,7 @@ window.Page_uebungen = (() => {
|
|||
border-radius:var(--radius-md);font-size:var(--text-sm);
|
||||
font-family:var(--font-sans);background:var(--c-surface);
|
||||
color:var(--c-text);resize:vertical;outline:none;
|
||||
line-height:1.5">${_esc(noteText)}</textarea>
|
||||
line-height:1.5">${UI.escape(noteText)}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Erfolgsquote -->
|
||||
|
|
@ -1569,7 +1569,7 @@ window.Page_uebungen = (() => {
|
|||
|
||||
<h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text);
|
||||
margin:0 0 var(--space-4);text-align:center">
|
||||
Einheit loggen: ${_esc(exerciseName)}
|
||||
Einheit loggen: ${UI.escape(exerciseName)}
|
||||
</h3>
|
||||
|
||||
<form id="${formId}" style="display:flex;flex-direction:column;gap:var(--space-4)">
|
||||
|
|
@ -1620,7 +1620,7 @@ window.Page_uebungen = (() => {
|
|||
border-radius:var(--radius-md);border:1.5px solid var(--c-border);
|
||||
background:var(--c-surface-2);cursor:pointer;transition:all 0.15s">
|
||||
${emoji}
|
||||
<span style="font-size:9px;color:var(--c-text-secondary)">${_esc(val)}</span>
|
||||
<span style="font-size:9px;color:var(--c-text-secondary)">${UI.escape(val)}</span>
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
|
@ -2103,7 +2103,7 @@ window.Page_uebungen = (() => {
|
|||
: '';
|
||||
const noteHtml = s.notiz
|
||||
? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:3px;
|
||||
line-height:1.4;font-style:italic">${_esc(s.notiz)}</div>`
|
||||
line-height:1.4;font-style:italic">${UI.escape(s.notiz)}</div>`
|
||||
: '';
|
||||
return `
|
||||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);
|
||||
|
|
@ -2113,7 +2113,7 @@ window.Page_uebungen = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
|
||||
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text)">${_esc(s.exercise_name)}</span>
|
||||
color:var(--c-text)">${UI.escape(s.exercise_name)}</span>
|
||||
${topBadge}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:1px">
|
||||
|
|
@ -2146,7 +2146,7 @@ window.Page_uebungen = (() => {
|
|||
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text-secondary);text-transform:uppercase;
|
||||
letter-spacing:.05em;padding:var(--space-2) 0 var(--space-1)">
|
||||
${_esc(label)}
|
||||
${UI.escape(label)}
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-1)">
|
||||
${sessions.map(_sessionRow).join('')}
|
||||
|
|
@ -2216,7 +2216,7 @@ window.Page_uebungen = (() => {
|
|||
<div style="display:flex;align-items:center;gap:var(--space-2);
|
||||
padding:4px var(--space-2);border-radius:var(--radius-sm);
|
||||
font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
<span style="flex-shrink:0;min-width:52px">${_esc(dateLabel)}</span>
|
||||
<span style="flex-shrink:0;min-width:52px">${UI.escape(dateLabel)}</span>
|
||||
<span style="flex-shrink:0">${erfolg}</span>
|
||||
<span style="flex-shrink:0">${s.erfolgsquote}%${top}</span>
|
||||
<span class="flex-1-min">${s.wiederholungen}× Wdh.${stimmung ? ' ' + stimmung : ''}</span>
|
||||
|
|
@ -2244,12 +2244,12 @@ window.Page_uebungen = (() => {
|
|||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);line-height:1.3;
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
${_esc(ex.name)}
|
||||
${UI.escape(ex.name)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
|
||||
${ex.sessions.length} Einheit${ex.sessions.length !== 1 ? 'en' : ''}
|
||||
${ex.topCount ? ' · ' + ex.topCount + '× TOP' : ''}
|
||||
· ${_esc(lastLabel)}
|
||||
· ${UI.escape(lastLabel)}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Chevron -->
|
||||
|
|
@ -2350,8 +2350,8 @@ window.Page_uebungen = (() => {
|
|||
['Ablenkungstraining', 'Wieder häufiger (höhere Anforderung)'],
|
||||
].map(([p, b], i) => `
|
||||
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text);border-bottom:1px solid var(--c-border)">${_esc(p)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text-secondary);border-bottom:1px solid var(--c-border)">${_esc(b)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text);border-bottom:1px solid var(--c-border)">${UI.escape(p)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text-secondary);border-bottom:1px solid var(--c-border)">${UI.escape(b)}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
|
|
@ -2391,9 +2391,9 @@ window.Page_uebungen = (() => {
|
|||
].map(([s, b, c], i) => `
|
||||
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
|
||||
<td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">
|
||||
<span style="font-weight:var(--weight-semibold);color:${c}">${_esc(s)}</span>
|
||||
<span style="font-weight:var(--weight-semibold);color:${c}">${UI.escape(s)}</span>
|
||||
</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text-secondary);border-bottom:1px solid var(--c-border)">${_esc(b)}</td>
|
||||
<td style="padding:var(--space-2) var(--space-3);color:var(--c-text-secondary);border-bottom:1px solid var(--c-border)">${UI.escape(b)}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
|
|
@ -2425,7 +2425,7 @@ window.Page_uebungen = (() => {
|
|||
color:${ok ? 'var(--c-success)' : 'var(--c-danger)'}" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${ok ? 'check-circle' : 'x-circle'}"></use>
|
||||
</svg>
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${_esc(text)}</span>
|
||||
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">${UI.escape(text)}</span>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
|
|
@ -2438,16 +2438,7 @@ window.Page_uebungen = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// HELPER
|
||||
// ----------------------------------------------------------
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
return { init, refresh, onDogChange };
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ window.Page_wetter = (() => {
|
|||
box-shadow:${shadow};transform:${transform};
|
||||
transition:all .15s;user-select:none">
|
||||
<span style="font-size:var(--text-xs);font-weight:600;
|
||||
margin-bottom:var(--space-1)">${_esc(dayName)}</span>
|
||||
margin-bottom:var(--space-1)">${UI.escape(dayName)}</span>
|
||||
<div style="margin-bottom:var(--space-1)">${_wmoIcon(d.weathercode, '1.5rem', active ? 'filter:brightness(0) invert(1)' : '')}</div>
|
||||
<span class="wttr-temp"
|
||||
style="font-size:var(--text-xs);color:${textSec};white-space:nowrap">
|
||||
|
|
@ -414,13 +414,13 @@ window.Page_wetter = (() => {
|
|||
: 0;
|
||||
}
|
||||
|
||||
const locName = _data.location_name ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">${_esc(_data.location_name)}</div>` : '';
|
||||
const locName = _data.location_name ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">${UI.escape(_data.location_name)}</div>` : '';
|
||||
el.innerHTML = `
|
||||
${locName}
|
||||
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4)">
|
||||
${_wmoIcon(d.weathercode, '3.5rem')}
|
||||
<div>
|
||||
<div style="font-weight:700;font-size:var(--text-lg)">${_esc(desc)}</div>
|
||||
<div style="font-weight:700;font-size:var(--text-lg)">${UI.escape(desc)}</div>
|
||||
<div style="font-size:var(--text-2xl);font-weight:800;color:var(--c-primary);line-height:1.1">
|
||||
${Math.round(d.temp_max)}°
|
||||
<span style="font-size:var(--text-base);font-weight:400;color:var(--c-text-secondary)">
|
||||
|
|
@ -445,10 +445,10 @@ window.Page_wetter = (() => {
|
|||
margin-bottom:var(--space-1)">
|
||||
<span style="display:flex;align-items:center;gap:4px">
|
||||
<svg class="ph-icon" style="width:14px;height:14px;color:#F97316"><use href="/icons/phosphor.svg#sun-horizon"></use></svg>
|
||||
${_esc(sunriseStr)}
|
||||
${UI.escape(sunriseStr)}
|
||||
</span>
|
||||
<span style="display:flex;align-items:center;gap:4px">
|
||||
${_esc(sunsetStr)}
|
||||
${UI.escape(sunsetStr)}
|
||||
<svg class="ph-icon" style="width:14px;height:14px;color:#7C3AED"><use href="/icons/phosphor.svg#moon-stars"></use></svg>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -469,9 +469,9 @@ window.Page_wetter = (() => {
|
|||
</span>
|
||||
<div class="flex-1">
|
||||
<div style="font-size:var(--text-sm);font-weight:600">
|
||||
${_esc(compass)} · ${Math.round(d.wind_kmh ?? 0)} km/h
|
||||
${UI.escape(compass)} · ${Math.round(d.wind_kmh ?? 0)} km/h
|
||||
</div>
|
||||
<div class="text-xs-secondary">${_esc(bft)}</div>
|
||||
<div class="text-xs-secondary">${UI.escape(bft)}</div>
|
||||
</div>
|
||||
${d.precip_sum != null ? `
|
||||
<div class="text-right">
|
||||
|
|
@ -488,7 +488,7 @@ window.Page_wetter = (() => {
|
|||
font-size:var(--text-xs);margin-bottom:4px">
|
||||
<span class="text-secondary">UV-Index</span>
|
||||
<span style="font-weight:600;color:${uvColor}">
|
||||
${d.uv_index ?? 0} — ${_esc(uvLabel)}
|
||||
${d.uv_index ?? 0} — ${UI.escape(uvLabel)}
|
||||
</span>
|
||||
</div>
|
||||
<div style="height:6px;border-radius:999px;background:var(--c-border);overflow:hidden">
|
||||
|
|
@ -596,7 +596,7 @@ window.Page_wetter = (() => {
|
|||
Niederschlagswahrscheinlichkeit
|
||||
</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-left:auto">
|
||||
${_selDay === 0 ? 'heute' : _esc(d.date ? new Date(d.date + 'T12:00').toLocaleDateString('de', {weekday:'short', day:'numeric', month:'short'}) : '')}
|
||||
${_selDay === 0 ? 'heute' : UI.escape(d.date ? new Date(d.date + 'T12:00').toLocaleDateString('de', {weekday:'short', day:'numeric', month:'short'}) : '')}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Baseline -->
|
||||
|
|
@ -648,10 +648,10 @@ window.Page_wetter = (() => {
|
|||
margin-bottom:var(--space-4);text-align:center">
|
||||
<div style="font-size:2rem;line-height:1;margin-bottom:4px">${_wl.emoji}</div>
|
||||
<div style="font-weight:800;font-size:var(--text-lg);color:${_wl.color};line-height:1.2">
|
||||
${_esc(_wl.label)}
|
||||
${UI.escape(_wl.label)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">
|
||||
${_esc(_wl.sub)}
|
||||
${UI.escape(_wl.sub)}
|
||||
</div>
|
||||
</div>
|
||||
<h3 style="font-size:var(--text-base);font-weight:700;margin-bottom:var(--space-4)">
|
||||
|
|
@ -670,10 +670,10 @@ window.Page_wetter = (() => {
|
|||
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
||||
<div class="flex-1">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);color:${aspColor}">
|
||||
Asphalt ~${Math.round(d.asphalt_temp)}°C — ${_esc(aspText)}
|
||||
Asphalt ~${Math.round(d.asphalt_temp)}°C — ${UI.escape(aspText)}
|
||||
</div>
|
||||
${aspAdvice ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
|
||||
${_esc(aspAdvice)}
|
||||
${UI.escape(aspAdvice)}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -735,7 +735,7 @@ window.Page_wetter = (() => {
|
|||
padding:3px 10px;background:${col}22;
|
||||
border:1px solid ${col}55;color:${col};font-weight:600">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:${col};display:inline-block"></span>
|
||||
${_esc(name)}: ${_esc(lbl)}
|
||||
${UI.escape(name)}: ${UI.escape(lbl)}
|
||||
</span>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
|
|
@ -756,7 +756,7 @@ window.Page_wetter = (() => {
|
|||
<div class="flex-1">
|
||||
<span style="font-size:var(--text-sm);font-weight:600">Zecken-Risiko: </span>
|
||||
<span style="font-size:var(--text-sm);color:${tickColor};font-weight:700">
|
||||
${_esc(tickLabel)}
|
||||
${UI.escape(tickLabel)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -786,7 +786,7 @@ window.Page_wetter = (() => {
|
|||
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:${fellHint.color}">
|
||||
<use href="/icons/phosphor.svg#${fellHint.icon}"></use>
|
||||
</svg>
|
||||
<div class="text-sm">${_esc(fellHint.text)}</div>
|
||||
<div class="text-sm">${UI.escape(fellHint.text)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -876,7 +876,7 @@ window.Page_wetter = (() => {
|
|||
</span>
|
||||
<span class="text-xs-secondary">/ 10</span>
|
||||
<span style="font-size:var(--text-xs);font-weight:600;color:${color};
|
||||
white-space:nowrap">— ${_esc(text)}</span>
|
||||
white-space:nowrap">— ${UI.escape(text)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -912,7 +912,7 @@ window.Page_wetter = (() => {
|
|||
padding:3px 10px;background:${s.color}22;
|
||||
border:1px solid ${s.color}55;color:${s.color};font-weight:600">
|
||||
<svg class="ph-icon" style="width:12px;height:12px"><use href="/icons/phosphor.svg#nose"></use></svg>
|
||||
Schnüffel: ${_esc(s.label)}
|
||||
Schnüffel: ${UI.escape(s.label)}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
|
@ -1072,15 +1072,6 @@ window.Page_wetter = (() => {
|
|||
return ['hoch', '#F44336'];
|
||||
}
|
||||
|
||||
function _esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// MEINE WETTERREKORDE
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -1116,15 +1107,15 @@ window.Page_wetter = (() => {
|
|||
display:flex;align-items:center;gap:3px;font-weight:700;
|
||||
text-transform:uppercase;letter-spacing:.04em">
|
||||
<span>${emoji}</span>
|
||||
<span>${_esc(title)}</span>
|
||||
<span>${UI.escape(title)}</span>
|
||||
</div>
|
||||
<div style="font-size:var(--text-lg);font-weight:800;color:${color};line-height:1.1">
|
||||
${_esc(value)}
|
||||
${UI.escape(value)}
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--c-text-secondary);
|
||||
overflow:hidden;display:-webkit-box;
|
||||
-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.3">
|
||||
${_esc(subtitle)}
|
||||
${UI.escape(subtitle)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ window.Page_widget = (() => {
|
|||
|
||||
const photoHtml = photo
|
||||
? `<div class="widget-photo-wrap">
|
||||
<img src="${_esc(photo.media_url)}" alt="${_esc(photo.titel || '')}" class="widget-photo">
|
||||
<img src="${UI.escape(photo.media_url)}" alt="${UI.escape(photo.titel || '')}" class="widget-photo">
|
||||
<div class="widget-photo-caption">
|
||||
${_esc(photo.titel || '')}
|
||||
${UI.escape(photo.titel || '')}
|
||||
<span class="widget-photo-date">${_fmtDate(photo.datum)}</span>
|
||||
</div>
|
||||
</div>`
|
||||
|
|
@ -64,7 +64,7 @@ window.Page_widget = (() => {
|
|||
? `<div class="widget-reminder">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-check"></use></svg>
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${_esc(rem.bezeichnung)}</div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${UI.escape(rem.bezeichnung)}</div>
|
||||
<div class="text-xs-muted">${_fmtDate(rem.naechstes)}</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
|
@ -79,7 +79,7 @@ window.Page_widget = (() => {
|
|||
</div>`;
|
||||
|
||||
const dogAvatar = dog.foto_url
|
||||
? `<img src="${_esc(dog.foto_url)}" class="widget-dog-av" alt="${_esc(dog.name)}">`
|
||||
? `<img src="${UI.escape(dog.foto_url)}" class="widget-dog-av" alt="${UI.escape(dog.name)}">`
|
||||
: `<div class="widget-dog-av widget-dog-av--placeholder">🐕</div>`;
|
||||
|
||||
_container.innerHTML = `
|
||||
|
|
@ -89,8 +89,8 @@ window.Page_widget = (() => {
|
|||
<div class="widget-dog-row">
|
||||
${dogAvatar}
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-bold);font-size:var(--text-lg)">${_esc(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="text-sm-muted">${_esc(dog.rasse)}</div>` : ''}
|
||||
<div style="font-weight:var(--weight-bold);font-size:var(--text-lg)">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="text-sm-muted">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm" id="widget-refresh-btn" style="margin-left:auto"
|
||||
title="Neues Zufallsbild">
|
||||
|
|
@ -126,13 +126,7 @@ window.Page_widget = (() => {
|
|||
|
||||
_container.querySelector('#widget-refresh-btn')?.addEventListener('click', () => _render());
|
||||
}
|
||||
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function _fmtDate(d) {
|
||||
function _fmtDate(d) {
|
||||
if (!d) return '';
|
||||
try {
|
||||
const [y, m, day] = d.split('-');
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ window.Page_wiki = (() => {
|
|||
try {
|
||||
subs = await _apiFetch('/api/wiki/foto-submissions');
|
||||
} catch (e) {
|
||||
el.innerHTML = `<div class="empty-state"><p>${_esc(e.message)}</p></div>`;
|
||||
el.innerHTML = `<div class="empty-state"><p>${UI.escape(e.message)}</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -163,16 +163,16 @@ window.Page_wiki = (() => {
|
|||
${subs.map(s => `
|
||||
<div class="card" style="margin-bottom:var(--space-3);padding:var(--space-3)" id="wiki-sub-${s.id}">
|
||||
<div style="display:flex;gap:var(--space-3);align-items:flex-start">
|
||||
<img src="${_esc(s.foto_url)}" alt=""
|
||||
<img src="${UI.escape(s.foto_url)}" alt=""
|
||||
style="width:100px;height:80px;object-fit:cover;border-radius:var(--radius-md);flex-shrink:0;background:var(--c-surface-2)">
|
||||
<div class="flex-1-min">
|
||||
<div style="font-weight:var(--weight-semibold)">${_esc(s.rasse_name)}</div>
|
||||
<div style="font-weight:var(--weight-semibold)">${UI.escape(s.rasse_name)}</div>
|
||||
<div class="text-xs-muted">
|
||||
von ${_esc(s.user_name)} · ${_formatDate(s.created_at)}
|
||||
von ${UI.escape(s.user_name)} · ${_formatDate(s.created_at)}
|
||||
</div>
|
||||
${s.aktuell_foto
|
||||
? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">
|
||||
Aktuelles Foto: <img src="${_esc(s.aktuell_foto)}" style="height:20px;vertical-align:middle;border-radius:2px">
|
||||
Aktuelles Foto: <img src="${UI.escape(s.aktuell_foto)}" style="height:20px;vertical-align:middle;border-radius:2px">
|
||||
</div>`
|
||||
: `<div style="font-size:var(--text-xs);color:var(--c-warning);margin-top:4px">Kein Foto vorhanden</div>`
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ window.Page_wiki = (() => {
|
|||
// Preserve current selection
|
||||
const cur = _currentGruppe;
|
||||
sel.innerHTML = `<option value="">Alle Gruppen</option>` +
|
||||
_gruppen.map(g => `<option value="${_esc(g)}"${g === cur ? ' selected' : ''}>${_esc(g)}</option>`).join('');
|
||||
_gruppen.map(g => `<option value="${UI.escape(g)}"${g === cur ? ' selected' : ''}>${UI.escape(g)}</option>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,24 +389,24 @@ window.Page_wiki = (() => {
|
|||
? fotoUrl.replace(/\.(jpe?g|png|gif|webp)$/i, '_preview.webp')
|
||||
: fotoUrl;
|
||||
const photoHtml = fotoUrl
|
||||
? `<img class="wiki-breed-photo" src="${_esc(srcUrl)}" loading="lazy" alt="${_esc(r.name)}"
|
||||
onerror="if(this.src.includes('_preview')){this.src='${_esc(fotoUrl)}'}else{this.style.display='none';this.nextElementSibling.style.display='flex'}">`
|
||||
? `<img class="wiki-breed-photo" src="${UI.escape(srcUrl)}" loading="lazy" alt="${UI.escape(r.name)}"
|
||||
onerror="if(this.src.includes('_preview')){this.src='${UI.escape(fotoUrl)}'}else{this.style.display='none';this.nextElementSibling.style.display='flex'}">`
|
||||
: '';
|
||||
const fallbackHtml = `<div class="wiki-breed-photo-fallback" style="${fotoUrl ? 'display:none' : ''}">${_DOG_SILHOUETTE}</div>`;
|
||||
|
||||
return `
|
||||
<div class="wiki-breed-card" data-slug="${_esc(r.slug)}">
|
||||
<div class="wiki-breed-card" data-slug="${UI.escape(r.slug)}">
|
||||
<div class="wiki-breed-photo-wrap">
|
||||
${photoHtml}
|
||||
${fallbackHtml}
|
||||
</div>
|
||||
<div class="wiki-breed-card-body">
|
||||
<div class="wiki-breed-card-name">${_esc(r.name)}</div>
|
||||
<div class="wiki-breed-card-gruppe">${_esc(r.gruppe || '—')}</div>
|
||||
<div class="wiki-breed-card-name">${UI.escape(r.name)}</div>
|
||||
<div class="wiki-breed-card-gruppe">${UI.escape(r.gruppe || '—')}</div>
|
||||
<div class="wiki-breed-badges">
|
||||
<span class="wiki-badge-groesse wiki-badge-groesse--${_esc(r.groesse)}">${_groesseLabel(r.groesse)}</span>
|
||||
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${_esc(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
|
||||
<span class="wiki-badge-erfahrung wiki-badge-erfahrung--${_esc(r.erfahrung)}">${_erfahrungLabel(r.erfahrung)}</span>
|
||||
<span class="wiki-badge-groesse wiki-badge-groesse--${UI.escape(r.groesse)}">${_groesseLabel(r.groesse)}</span>
|
||||
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${UI.escape(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
|
||||
<span class="wiki-badge-erfahrung wiki-badge-erfahrung--${UI.escape(r.erfahrung)}">${_erfahrungLabel(r.erfahrung)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -472,12 +472,12 @@ window.Page_wiki = (() => {
|
|||
const rows = [
|
||||
['Größe', _groesseLabel(rasse.groesse) || '—'],
|
||||
['Gewicht', gewicht],
|
||||
['Lebensdauer', _esc(rasse.lebensdauer) || '—'],
|
||||
['Lebensdauer', UI.escape(rasse.lebensdauer) || '—'],
|
||||
['Aktivität', _aktivLabel(rasse.aktivitaet) || '—'],
|
||||
['Eignung', _erfahrungLabel(rasse.erfahrung) || '—'],
|
||||
['Kinder', kinderLabel],
|
||||
['Wohnung', wohnungLabel],
|
||||
['FCI-Gruppe', _esc(rasse.gruppe) || '—'],
|
||||
['FCI-Gruppe', UI.escape(rasse.gruppe) || '—'],
|
||||
];
|
||||
|
||||
return `
|
||||
|
|
@ -517,12 +517,12 @@ window.Page_wiki = (() => {
|
|||
<div class="flex-gap-2">
|
||||
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-hat"
|
||||
style="flex:1;${hatStyle}"
|
||||
data-slug="${_esc(slug)}" data-typ="hat">
|
||||
data-slug="${UI.escape(slug)}" data-typ="hat">
|
||||
${isLoggedIn ? '' : '🔒 '}Ich hab einen
|
||||
</button>
|
||||
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-will"
|
||||
style="flex:1;${willStyle}"
|
||||
data-slug="${_esc(slug)}" data-typ="will">
|
||||
data-slug="${UI.escape(slug)}" data-typ="will">
|
||||
${isLoggedIn ? '' : '🔒 '}Ich will einen
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -593,13 +593,13 @@ window.Page_wiki = (() => {
|
|||
? `<p style="color:var(--c-text-secondary);font-size:var(--text-sm)">Noch keine Züchter eingetragen.</p>`
|
||||
: zuchter.map(z => `
|
||||
<div class="wiki-zuchter-card" style="padding:var(--space-3);border-radius:var(--radius-md);background:var(--c-surface-2);margin-bottom:var(--space-2)">
|
||||
<div style="font-weight:var(--weight-semibold)">${_esc(z.name)}
|
||||
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> „${_esc(z.zwingername)}“</em>` : ''}
|
||||
<div style="font-weight:var(--weight-semibold)">${UI.escape(z.name)}
|
||||
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> „${UI.escape(z.zwingername)}“</em>` : ''}
|
||||
${z.vdh_mitglied ? `<span class="badge badge-sm" style="margin-left:var(--space-1);background:var(--c-primary);color:#fff">VDH</span>` : ''}
|
||||
</div>
|
||||
${(z.ort || z.bundesland) ? `<div class="text-sm-secondary">${[z.ort, z.bundesland].filter(Boolean).map(_esc).join(', ')}</div>` : ''}
|
||||
${z.beschreibung ? `<p style="font-size:var(--text-sm);margin-top:var(--space-1)">${_esc(z.beschreibung)}</p>` : ''}
|
||||
${z.website ? `<a href="${_esc(z.website)}" target="_blank" rel="noopener" style="font-size:var(--text-sm);color:var(--c-primary)">${_esc(z.website)}</a>` : ''}
|
||||
${z.beschreibung ? `<p style="font-size:var(--text-sm);margin-top:var(--space-1)">${UI.escape(z.beschreibung)}</p>` : ''}
|
||||
${z.website ? `<a href="${UI.escape(z.website)}" target="_blank" rel="noopener" style="font-size:var(--text-sm);color:var(--c-primary)">${UI.escape(z.website)}</a>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
|
|
@ -627,7 +627,7 @@ window.Page_wiki = (() => {
|
|||
<label class="form-label">Bundesland</label>
|
||||
<select class="form-control" name="bundesland">
|
||||
<option value="">— bitte wählen —</option>
|
||||
${DE_BUNDESLAENDER.map(bl => `<option value="${_esc(bl)}">${_esc(bl)}</option>`).join('')}
|
||||
${DE_BUNDESLAENDER.map(bl => `<option value="${UI.escape(bl)}">${UI.escape(bl)}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:1/-1">
|
||||
|
|
@ -731,7 +731,7 @@ window.Page_wiki = (() => {
|
|||
|
||||
// Temperament chips
|
||||
const chips = rasse.temperament
|
||||
? rasse.temperament.split(',').map(t => `<span class="wiki-trait-chip">${_esc(t.trim())}</span>`).join('')
|
||||
? rasse.temperament.split(',').map(t => `<span class="wiki-trait-chip">${UI.escape(t.trim())}</span>`).join('')
|
||||
: '';
|
||||
|
||||
const _dogSvgLg = _DOG_SILHOUETTE.replace('width="48" height="48"', 'width="56" height="56"');
|
||||
|
|
@ -743,7 +743,7 @@ window.Page_wiki = (() => {
|
|||
const photoHtml = allFotos.length
|
||||
? `<div class="wiki-gallery-wrap">
|
||||
<img class="wiki-detail-photo wiki-gallery-main" id="wiki-main-photo"
|
||||
src="${_esc(allFotos[0].foto_url)}" alt="${_esc(rasse.name)}"
|
||||
src="${UI.escape(allFotos[0].foto_url)}" alt="${UI.escape(rasse.name)}"
|
||||
onerror="this.style.display='none';document.getElementById('wiki-photo-fallback').style.display='flex'">
|
||||
<div id="wiki-photo-fallback" class="wiki-detail-photo-placeholder hidden">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>
|
||||
${allFotos.length > 1 ? `
|
||||
|
|
@ -751,10 +751,10 @@ window.Page_wiki = (() => {
|
|||
${allFotos.map((f, i) => `
|
||||
<button class="wiki-gallery-thumb${i === 0 ? ' active' : ''}" data-idx="${i}"
|
||||
aria-label="Foto ${i + 1}">
|
||||
<img src="${_esc(f.foto_url.startsWith('/media/') ? f.foto_url.replace(/\.(jpe?g|png|gif|webp)$/i,'_preview.webp') : f.foto_url)}"
|
||||
<img src="${UI.escape(f.foto_url.startsWith('/media/') ? f.foto_url.replace(/\.(jpe?g|png|gif|webp)$/i,'_preview.webp') : f.foto_url)}"
|
||||
alt="" loading="lazy"
|
||||
onerror="if(this.src.includes('_preview')){this.src='${_esc(f.foto_url)}'}else{this.style.display='none'}">
|
||||
${f.user_name ? `<span class="wiki-gallery-thumb-label">von ${_esc(f.user_name)}</span>` : ''}
|
||||
onerror="if(this.src.includes('_preview')){this.src='${UI.escape(f.foto_url)}'}else{this.style.display='none'}">
|
||||
${f.user_name ? `<span class="wiki-gallery-thumb-label">von ${UI.escape(f.user_name)}</span>` : ''}
|
||||
</button>`).join('')}
|
||||
</div>` : ''}
|
||||
<button class="wiki-gallery-expand" id="wiki-gallery-expand" aria-label="Vollbild">
|
||||
|
|
@ -771,9 +771,9 @@ window.Page_wiki = (() => {
|
|||
<div class="wiki-detail-hero" style="text-align:center;margin-bottom:var(--space-4)">
|
||||
${photoHtml}
|
||||
${userFotosHtml}
|
||||
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${_esc(rasse.name)}</h1>
|
||||
${rasse.herkunft ? `<div class="text-sm-secondary">${UI.icon('map-pin')} ${_esc(rasse.herkunft)}</div>` : ''}
|
||||
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${_esc(rasse.gruppe)}</div>` : ''}
|
||||
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${UI.escape(rasse.name)}</h1>
|
||||
${rasse.herkunft ? `<div class="text-sm-secondary">${UI.icon('map-pin')} ${UI.escape(rasse.herkunft)}</div>` : ''}
|
||||
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${UI.escape(rasse.gruppe)}</div>` : ''}
|
||||
</div>
|
||||
|
||||
${/* 2. Charakter-Badges */ chips ? `
|
||||
|
|
@ -785,11 +785,11 @@ window.Page_wiki = (() => {
|
|||
${/* 3. Beschreibung */ rasse.beschreibung ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Beschreibung</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.beschreibung)}</p>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(rasse.beschreibung)}</p>
|
||||
</div>` : (rasse.bred_for ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Ursprüngliche Aufgabe</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.bred_for)}</p>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(rasse.bred_for)}</p>
|
||||
</div>` : '')}
|
||||
|
||||
${/* 4. Steckbrief */ _renderSteckbriefGrid(rasse)}
|
||||
|
|
@ -797,7 +797,7 @@ window.Page_wiki = (() => {
|
|||
${/* 5. Vorkommen */ rasse.vorkommen_de ? `
|
||||
<div class="wiki-detail-section">
|
||||
<div class="wiki-detail-label">Vorkommen in Deutschland</div>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${_esc(rasse.vorkommen_de)}</p>
|
||||
<p style="line-height:1.6;color:var(--c-text)">${UI.escape(rasse.vorkommen_de)}</p>
|
||||
</div>` : ''}
|
||||
|
||||
${/* 6. Interesse — wird async befüllt */ `
|
||||
|
|
@ -835,7 +835,7 @@ window.Page_wiki = (() => {
|
|||
</div>` : ''}`}
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: _esc(rasse.name), body });
|
||||
UI.modal.open({ title: UI.escape(rasse.name), body });
|
||||
|
||||
// Async: load stats + züchter in parallel
|
||||
Promise.all([_fetchStats(slug), _fetchZuchter(slug)]).then(([stats, zuchter]) => {
|
||||
|
|
@ -936,7 +936,7 @@ window.Page_wiki = (() => {
|
|||
</p>
|
||||
<form id="wiki-foto-form" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Foto von <strong>${_esc(rasseName)}</strong></label>
|
||||
<label class="form-label">Foto von <strong>${UI.escape(rasseName)}</strong></label>
|
||||
<input class="form-control" type="file" id="wiki-foto-input"
|
||||
accept="image/jpeg,image/png,image/webp" required>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:4px">
|
||||
|
|
@ -1030,14 +1030,14 @@ window.Page_wiki = (() => {
|
|||
return berichte.map(b => `
|
||||
<div class="wiki-bericht-item" data-id="${b.id}">
|
||||
<div class="wiki-bericht-header">
|
||||
<span class="wiki-bericht-autor">${_esc(b.autor)}</span>
|
||||
<span class="wiki-bericht-autor">${UI.escape(b.autor)}</span>
|
||||
<span class="wiki-bericht-date">${_formatDate(b.created_at)}</span>
|
||||
${_appState.user && _appState.user.name === b.autor
|
||||
? `<button class="btn btn-danger btn-xs wiki-bericht-del" data-id="${b.id}" data-slug="${_esc(slug)}" style="margin-left:auto;padding:2px 8px;font-size:0.7rem">Löschen</button>`
|
||||
? `<button class="btn btn-danger btn-xs wiki-bericht-del" data-id="${b.id}" data-slug="${UI.escape(slug)}" style="margin-left:auto;padding:2px 8px;font-size:0.7rem">Löschen</button>`
|
||||
: ''}
|
||||
</div>
|
||||
<div class="wiki-bericht-titel">${_esc(b.titel)}</div>
|
||||
<p class="wiki-bericht-text">${_esc(b.text)}</p>
|
||||
<div class="wiki-bericht-titel">${UI.escape(b.titel)}</div>
|
||||
<p class="wiki-bericht-text">${UI.escape(b.text)}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
|
@ -1047,7 +1047,7 @@ window.Page_wiki = (() => {
|
|||
<form id="wiki-bericht-form" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rasse</label>
|
||||
<input class="form-control" type="text" value="${_esc(rasseName)}" disabled>
|
||||
<input class="form-control" type="text" value="${UI.escape(rasseName)}" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titel</label>
|
||||
|
|
@ -1095,11 +1095,11 @@ window.Page_wiki = (() => {
|
|||
<div class="wiki-section" data-idx="${i}">
|
||||
<div class="wiki-section-header">
|
||||
<span class="wiki-section-icon">${UI.icon(s.icon)}</span>
|
||||
<span class="wiki-section-titel">${_esc(s.titel)}</span>
|
||||
<span class="wiki-section-titel">${UI.escape(s.titel)}</span>
|
||||
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
|
||||
</div>
|
||||
<div class="wiki-section-body hidden">
|
||||
<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_esc(s.text)}</p>
|
||||
<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${UI.escape(s.text)}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
|
@ -1126,13 +1126,13 @@ window.Page_wiki = (() => {
|
|||
<div class="wiki-section" data-idx="${i}">
|
||||
<div class="wiki-section-header">
|
||||
<span class="wiki-section-icon">${UI.icon('map-pin')}</span>
|
||||
<span class="wiki-section-titel">${_esc(r.land)}</span>
|
||||
<span class="wiki-section-titel">${UI.escape(r.land)}</span>
|
||||
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
|
||||
</div>
|
||||
<div class="wiki-section-body hidden">
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Leinenpflicht</span><span>${_esc(r.leine)}</span></div>
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Rasseliste</span><span>${_esc(r.rasse)}</span></div>
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Hundesteuer</span><span>${_esc(r.steuer)}</span></div>
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Leinenpflicht</span><span>${UI.escape(r.leine)}</span></div>
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Rasseliste</span><span>${UI.escape(r.rasse)}</span></div>
|
||||
<div class="wiki-recht-row"><span class="wiki-recht-label">Hundesteuer</span><span>${UI.escape(r.steuer)}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
|
@ -1174,8 +1174,8 @@ window.Page_wiki = (() => {
|
|||
|
||||
const optionsHtml = frage.optionen.map(o => `
|
||||
<button class="wiki-quiz-option${_quizAnswers[frage.key] === o.val ? ' selected' : ''}"
|
||||
data-key="${_esc(frage.key)}" data-val="${_esc(o.val)}">
|
||||
${_esc(o.label)}
|
||||
data-key="${UI.escape(frage.key)}" data-val="${UI.escape(o.val)}">
|
||||
${UI.escape(o.label)}
|
||||
</button>
|
||||
`).join('');
|
||||
|
||||
|
|
@ -1185,7 +1185,7 @@ window.Page_wiki = (() => {
|
|||
<div class="wiki-quiz-progress" style="width:${progress}%"></div>
|
||||
</div>
|
||||
<p class="wiki-quiz-step-info">Frage ${_quizStep + 1} von ${QUIZ_FRAGEN.length}</p>
|
||||
<p class="wiki-quiz-frage">${_esc(frage.frage)}</p>
|
||||
<p class="wiki-quiz-frage">${UI.escape(frage.frage)}</p>
|
||||
<div class="wiki-quiz-options">${optionsHtml}</div>
|
||||
<div class="wiki-quiz-nav">
|
||||
${_quizStep > 0
|
||||
|
|
@ -1225,24 +1225,24 @@ window.Page_wiki = (() => {
|
|||
|
||||
const cardsHtml = data.results.map(r => {
|
||||
const photoHtml = r.foto_url
|
||||
? `<img class="wiki-quiz-result-photo" src="${_esc(r.foto_url)}" loading="lazy" alt="${_esc(r.name)}" onerror="this.style.display='none'">`
|
||||
? `<img class="wiki-quiz-result-photo" src="${UI.escape(r.foto_url)}" loading="lazy" alt="${UI.escape(r.name)}" onerror="this.style.display='none'">`
|
||||
: `<div class="wiki-quiz-result-photo-fallback">${UI.icon('dog')}</div>`;
|
||||
return `
|
||||
<div class="wiki-quiz-result-card">
|
||||
<div class="wiki-quiz-result-photo-wrap">${photoHtml}</div>
|
||||
<div class="wiki-quiz-result-card-body">
|
||||
<div class="wiki-quiz-result-name">${_esc(r.name)}</div>
|
||||
<div class="wiki-quiz-result-gruppe">${_esc(r.gruppe || '')}</div>
|
||||
<div class="wiki-quiz-result-name">${UI.escape(r.name)}</div>
|
||||
<div class="wiki-quiz-result-gruppe">${UI.escape(r.gruppe || '')}</div>
|
||||
<div class="wiki-breed-badges" style="margin:var(--space-2) 0">
|
||||
<span class="wiki-badge-groesse wiki-badge-groesse--${_esc(r.groesse)}">${_groesseLabel(r.groesse)}</span>
|
||||
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${_esc(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
|
||||
<span class="wiki-badge-groesse wiki-badge-groesse--${UI.escape(r.groesse)}">${_groesseLabel(r.groesse)}</span>
|
||||
<span class="wiki-badge-aktivitaet wiki-badge-aktivitaet--${UI.escape(r.aktivitaet)}">${_aktivLabel(r.aktivitaet)}</span>
|
||||
</div>
|
||||
${r.temperament ? `<p class="wiki-quiz-result-char">${_esc(r.temperament.split(',').slice(0,4).join(', '))}</p>` : ''}
|
||||
${r.temperament ? `<p class="wiki-quiz-result-char">${UI.escape(r.temperament.split(',').slice(0,4).join(', '))}</p>` : ''}
|
||||
<div class="wiki-fit-row" style="font-size:var(--text-xs);margin-top:var(--space-1)">
|
||||
<span>${UI.icon('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span>
|
||||
<span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${_esc(r.slug)}" class="mt-2">Mehr erfahren</button>
|
||||
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${UI.escape(r.slug)}" class="mt-2">Mehr erfahren</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -1334,14 +1334,7 @@ window.Page_wiki = (() => {
|
|||
return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
} catch { return iso; }
|
||||
}
|
||||
|
||||
function _esc(str) {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// ----------------------------------------------------------
|
||||
// RASSEN-ERKENNUNG PER KI (Wiki-Tab)
|
||||
// ----------------------------------------------------------
|
||||
function _bindWikiRasseErkennung(el) {
|
||||
|
|
@ -1405,7 +1398,7 @@ window.Page_wiki = (() => {
|
|||
Auf diesem Foto konnte kein Hund erkannt werden.<br>
|
||||
Bitte lade ein deutlicheres Foto hoch.
|
||||
</p>
|
||||
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${_esc(data.hinweis)}</p>` : ''}
|
||||
${data.hinweis ? `<p style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:var(--space-3)">${UI.escape(data.hinweis)}</p>` : ''}
|
||||
</div>`,
|
||||
footer: `<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>`,
|
||||
});
|
||||
|
|
@ -1418,18 +1411,18 @@ window.Page_wiki = (() => {
|
|||
return `
|
||||
<div class="rasse-result-card${isTop ? ' rasse-result-card--top' : ''}">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||||
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${_esc(r.name)}</div>
|
||||
<div class="rasse-result-name">${isTop ? '🐕 ' : ''}${UI.escape(r.name)}</div>
|
||||
<span class="rasse-result-pct${isTop ? '' : ' rasse-result-pct--dim'}">${r.sicherheit}%</span>
|
||||
</div>
|
||||
<div class="rasse-result-bar-wrap">
|
||||
<div class="rasse-result-bar${isTop ? '' : ' rasse-result-bar--dim'}"
|
||||
style="width:${r.sicherheit}%"></div>
|
||||
</div>
|
||||
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''}
|
||||
${r.beschreibung ? `<div class="rasse-result-desc">${UI.escape(r.beschreibung)}</div>` : ''}
|
||||
${r.wiki_slug ? `
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-${isTop ? 'primary' : 'secondary'} btn-sm w-full"
|
||||
data-action="wiki" data-slug="${_esc(r.wiki_slug)}">
|
||||
data-action="wiki" data-slug="${UI.escape(r.wiki_slug)}">
|
||||
Im Wiki nachschlagen
|
||||
</button>
|
||||
</div>` : ''}
|
||||
|
|
@ -1443,7 +1436,7 @@ window.Page_wiki = (() => {
|
|||
<div style="padding-bottom:var(--space-2)">
|
||||
${data.hinweis ? `<div style="background:var(--c-surface-2);border-radius:var(--radius-md);
|
||||
padding:var(--space-3);margin-bottom:var(--space-3);font-size:var(--text-sm);
|
||||
color:var(--c-text-secondary)">ℹ️ ${_esc(data.hinweis)}</div>` : ''}
|
||||
color:var(--c-text-secondary)">ℹ️ ${UI.escape(data.hinweis)}</div>` : ''}
|
||||
${cardsHtml}
|
||||
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2);
|
||||
text-align:center">
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ window.Page_wurfboerse = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
return UI.escape ? UI.escape(s || '') : (s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '—';
|
||||
|
|
@ -157,8 +153,8 @@ window.Page_wurfboerse = (() => {
|
|||
el.innerHTML = `
|
||||
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
|
||||
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
|
||||
<h3 style="margin:0 0 var(--space-2)">${_esc(title)}</h3>
|
||||
<p style="color:var(--c-text-secondary);margin:0">${_esc(text)}</p>
|
||||
<h3 style="margin:0 0 var(--space-2)">${UI.escape(title)}</h3>
|
||||
<p style="color:var(--c-text-secondary);margin:0">${UI.escape(text)}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -168,13 +164,13 @@ window.Page_wurfboerse = (() => {
|
|||
function _cardHTML(b) {
|
||||
// Züchter-Kopfzeile
|
||||
const zuechterName = b.zuechter_name || b.zwingername || '—';
|
||||
const zwingername = b.zwingername ? ` (${_esc(b.zwingername)})` : '';
|
||||
const stadtLine = b.stadt ? ` · ${_esc(b.stadt)}` : '';
|
||||
const zwingername = b.zwingername ? ` (${UI.escape(b.zwingername)})` : '';
|
||||
const stadtLine = b.stadt ? ` · ${UI.escape(b.stadt)}` : '';
|
||||
|
||||
// Elterntiere
|
||||
const elternParts = [];
|
||||
if (b.vater_name) elternParts.push(_esc(b.vater_name));
|
||||
if (b.mutter_name) elternParts.push(_esc(b.mutter_name));
|
||||
if (b.vater_name) elternParts.push(UI.escape(b.vater_name));
|
||||
if (b.mutter_name) elternParts.push(UI.escape(b.mutter_name));
|
||||
const elternLine = elternParts.length === 2
|
||||
? `<div class="wb-card-eltern">${UI.icon('gender-male')} ${elternParts[0]} × ${UI.icon('gender-female')} ${elternParts[1]}</div>`
|
||||
: elternParts.length === 1
|
||||
|
|
@ -194,34 +190,34 @@ window.Page_wurfboerse = (() => {
|
|||
if (b.welpen_gesamt != null || b.welpen_verfuegbar != null) {
|
||||
const gesamt = b.welpen_gesamt != null ? b.welpen_gesamt : '?';
|
||||
const verfuegb = b.welpen_verfuegbar != null ? b.welpen_verfuegbar : '?';
|
||||
welpenLine = `<div class="wb-card-welpen">${UI.icon('paw-print')} Welpen verfügbar: ${_esc(String(verfuegb))} von ${_esc(String(gesamt))}</div>`;
|
||||
welpenLine = `<div class="wb-card-welpen">${UI.icon('paw-print')} Welpen verfügbar: ${UI.escape(String(verfuegb))} von ${UI.escape(String(gesamt))}</div>`;
|
||||
}
|
||||
|
||||
// Preis
|
||||
const preisLine = b.preis_spanne
|
||||
? `<div class="wb-card-preis">${UI.icon('currency-eur')} Preis: ${_esc(b.preis_spanne)} €</div>`
|
||||
? `<div class="wb-card-preis">${UI.icon('currency-eur')} Preis: ${UI.escape(b.preis_spanne)} €</div>`
|
||||
: '';
|
||||
|
||||
// Gesundheitstests
|
||||
const gesundheitLine = b.gesundheitstests
|
||||
? `<div class="wb-card-gesundheit">${UI.icon('heart')} ${_esc(b.gesundheitstests)}</div>`
|
||||
? `<div class="wb-card-gesundheit">${UI.icon('heart')} ${UI.escape(b.gesundheitstests)}</div>`
|
||||
: '';
|
||||
|
||||
// Beschreibung (max. 150 Zeichen)
|
||||
const beschreibungLine = b.beschreibung
|
||||
? `<div class="wb-card-beschreibung">${_esc(_truncate(b.beschreibung, 150))}</div>`
|
||||
? `<div class="wb-card-beschreibung">${UI.escape(_truncate(b.beschreibung, 150))}</div>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="wb-card">
|
||||
<div class="wb-card-header">
|
||||
<div class="wb-card-zuechter">
|
||||
${_esc(zuechterName)}${zwingername}${stadtLine}
|
||||
${UI.escape(zuechterName)}${zwingername}${stadtLine}
|
||||
</div>
|
||||
${_statusBadge(b.status)}
|
||||
</div>
|
||||
|
||||
${b.rasse_text ? `<div class="wb-card-rasse">${UI.icon('dog')} ${_esc(b.rasse_text)}</div>` : ''}
|
||||
${b.rasse_text ? `<div class="wb-card-rasse">${UI.icon('dog')} ${UI.escape(b.rasse_text)}</div>` : ''}
|
||||
|
||||
<div class="wb-card-details">
|
||||
${elternLine}
|
||||
|
|
@ -235,7 +231,7 @@ window.Page_wurfboerse = (() => {
|
|||
<div class="wb-card-footer">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm wb-profile-btn"
|
||||
data-zwingername="${_esc(b.zwingername || '')}"
|
||||
data-zwingername="${UI.escape(b.zwingername || '')}"
|
||||
>
|
||||
${UI.icon('user')} Profil ansehen
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,6 @@ window.Page_zucht_profil = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '—';
|
||||
|
|
@ -56,7 +47,7 @@ window.Page_zucht_profil = (() => {
|
|||
if (el === 'affected') color = '#EF4444';
|
||||
}
|
||||
|
||||
return `<span class="zp-badge" style="background:${color}">${_esc(ergebnis || '—')}</span>`;
|
||||
return `<span class="zp-badge" style="background:${color}">${UI.escape(ergebnis || '—')}</span>`;
|
||||
}
|
||||
|
||||
function _geneticBadge(ergebnis) {
|
||||
|
|
@ -65,7 +56,7 @@ window.Page_zucht_profil = (() => {
|
|||
if (e === 'clear') color = '#22C55E';
|
||||
if (e === 'carrier') color = '#F59E0B';
|
||||
if (e === 'affected') color = '#EF4444';
|
||||
return `<span class="zp-badge" style="background:${color}">${_esc(ergebnis || '—')}</span>`;
|
||||
return `<span class="zp-badge" style="background:${color}">${UI.escape(ergebnis || '—')}</span>`;
|
||||
}
|
||||
|
||||
function _titleTypBadge(typ) {
|
||||
|
|
@ -78,7 +69,7 @@ window.Page_zucht_profil = (() => {
|
|||
zucht: '#10B981',
|
||||
};
|
||||
const color = colors[t] || '#6B7280';
|
||||
return `<span class="zp-badge" style="background:${color}">${_esc(typ || '—')}</span>`;
|
||||
return `<span class="zp-badge" style="background:${color}">${UI.escape(typ || '—')}</span>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -126,7 +117,7 @@ window.Page_zucht_profil = (() => {
|
|||
_container.innerHTML = `
|
||||
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
|
||||
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('warning')}</div>
|
||||
<p class="text-danger">${_esc(err.message || 'Fehler beim Laden.')}</p>
|
||||
<p class="text-danger">${UI.escape(err.message || 'Fehler beim Laden.')}</p>
|
||||
<button class="btn btn-secondary" onclick="history.back()">Zurück</button>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -240,21 +231,21 @@ window.Page_zucht_profil = (() => {
|
|||
h.geschlecht === 'weiblich' ? 'Hündin' : null;
|
||||
|
||||
const metaItems = [
|
||||
h.rasse ? `${UI.icon('paw-print')} ${_esc(h.rasse)}` : null,
|
||||
h.rasse ? `${UI.icon('paw-print')} ${UI.escape(h.rasse)}` : null,
|
||||
geschlechtLabel ? `${gIcon} ${geschlechtLabel}` : null,
|
||||
geburtsjahrLabel ? `${UI.icon('calendar-dots')} ${geburtsjahrLabel}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
const identItems = [
|
||||
h.chip_nr ? `${UI.icon('barcode')} Chip: ${_esc(h.chip_nr)}` : null,
|
||||
h.zuchtbuchnummer ? `${UI.icon('book-open')} ZB-Nr.: ${_esc(h.zuchtbuchnummer)}` : null,
|
||||
h.taetowier_nr ? `${UI.icon('pencil-simple')} Tätowierung: ${_esc(h.taetowier_nr)}` : null,
|
||||
h.farbe ? `${UI.icon('palette')} ${_esc(h.farbe)}` : null,
|
||||
h.chip_nr ? `${UI.icon('barcode')} Chip: ${UI.escape(h.chip_nr)}` : null,
|
||||
h.zuchtbuchnummer ? `${UI.icon('book-open')} ZB-Nr.: ${UI.escape(h.zuchtbuchnummer)}` : null,
|
||||
h.taetowier_nr ? `${UI.icon('pencil-simple')} Tätowierung: ${UI.escape(h.taetowier_nr)}` : null,
|
||||
h.farbe ? `${UI.icon('palette')} ${UI.escape(h.farbe)}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
const elternItems = [
|
||||
h.vater_name ? `Vater: ${_esc(h.vater_name)}` : null,
|
||||
h.mutter_name ? `Mutter: ${_esc(h.mutter_name)}` : null,
|
||||
h.vater_name ? `Vater: ${UI.escape(h.vater_name)}` : null,
|
||||
h.mutter_name ? `Mutter: ${UI.escape(h.mutter_name)}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
return `
|
||||
|
|
@ -262,8 +253,8 @@ window.Page_zucht_profil = (() => {
|
|||
<div class="zp-header-icon">${gIcon}</div>
|
||||
<div class="zp-header-body">
|
||||
<h2 class="zp-header-name">
|
||||
${_esc(h.name)}
|
||||
${h.rufname ? `<span class="zp-header-rufname">(${_esc(h.rufname)})</span>` : ''}
|
||||
${UI.escape(h.name)}
|
||||
${h.rufname ? `<span class="zp-header-rufname">(${UI.escape(h.rufname)})</span>` : ''}
|
||||
</h2>
|
||||
${metaItems.length ? `
|
||||
<div class="zp-header-meta">
|
||||
|
|
@ -277,7 +268,7 @@ window.Page_zucht_profil = (() => {
|
|||
<div class="zp-header-meta text-xs-secondary">
|
||||
${elternItems.join(' · ')}
|
||||
</div>` : ''}
|
||||
${h.notiz ? `<div class="zp-header-notiz">${_esc(h.notiz)}</div>` : ''}
|
||||
${h.notiz ? `<div class="zp-header-notiz">${UI.escape(h.notiz)}</div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -350,18 +341,18 @@ window.Page_zucht_profil = (() => {
|
|||
box-sizing:border-box;">
|
||||
<div style="font-weight:600;font-size:var(--text-sm);
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
|
||||
${gIcon} ${_esc(node.name)}
|
||||
${gIcon} ${UI.escape(node.name)}
|
||||
</div>
|
||||
${node.rufname
|
||||
? `<div style="font-size:var(--text-xs);opacity:.75;white-space:nowrap;
|
||||
overflow:hidden;text-overflow:ellipsis;">${_esc(node.rufname)}</div>`
|
||||
overflow:hidden;text-overflow:ellipsis;">${UI.escape(node.rufname)}</div>`
|
||||
: ''}
|
||||
${dob
|
||||
? `<div style="font-size:var(--text-xs);opacity:.65;">${dob}</div>`
|
||||
: ''}
|
||||
${node.zuchtbuchnummer
|
||||
? `<div style="font-size:var(--text-xs);opacity:.55;white-space:nowrap;
|
||||
overflow:hidden;text-overflow:ellipsis;">${_esc(node.zuchtbuchnummer)}</div>`
|
||||
overflow:hidden;text-overflow:ellipsis;">${UI.escape(node.zuchtbuchnummer)}</div>`
|
||||
: ''}
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -377,12 +368,12 @@ window.Page_zucht_profil = (() => {
|
|||
const rows = tests.map(t => `
|
||||
<tr>
|
||||
<td class="zp-td">
|
||||
<span style="font-weight:var(--weight-medium)">${_esc(t.test_typ || 'Sonstiges')}</span>
|
||||
${t.test_name ? `<br><span class="text-xs-secondary">${_esc(t.test_name)}</span>` : ''}
|
||||
<span style="font-weight:var(--weight-medium)">${UI.escape(t.test_typ || 'Sonstiges')}</span>
|
||||
${t.test_name ? `<br><span class="text-xs-secondary">${UI.escape(t.test_name)}</span>` : ''}
|
||||
</td>
|
||||
<td class="zp-td">${_healthBadge(t.test_typ || '', t.ergebnis)}</td>
|
||||
<td class="zp-td zp-td-muted">${t.untersuch_am ? _fmtDate(t.untersuch_am) : '—'}</td>
|
||||
<td class="zp-td zp-td-muted">${t.labor ? _esc(t.labor) : '—'}</td>
|
||||
<td class="zp-td zp-td-muted">${t.labor ? UI.escape(t.labor) : '—'}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
return `
|
||||
|
|
@ -412,11 +403,11 @@ window.Page_zucht_profil = (() => {
|
|||
const rows = tests.map(t => `
|
||||
<tr>
|
||||
<td class="zp-td">
|
||||
<span style="font-weight:var(--weight-medium)">${_esc(t.marker_name || '—')}</span>
|
||||
<span style="font-weight:var(--weight-medium)">${UI.escape(t.marker_name || '—')}</span>
|
||||
</td>
|
||||
<td class="zp-td">${_geneticBadge(t.ergebnis_klasse)}</td>
|
||||
<td class="zp-td zp-td-muted">${t.getestet_am ? _fmtDate(t.getestet_am) : '—'}</td>
|
||||
<td class="zp-td zp-td-muted">${t.labor ? _esc(t.labor) : '—'}</td>
|
||||
<td class="zp-td zp-td-muted">${t.labor ? UI.escape(t.labor) : '—'}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
return `
|
||||
|
|
@ -455,15 +446,15 @@ window.Page_zucht_profil = (() => {
|
|||
<div class="zp-title-badges">
|
||||
${_titleTypBadge(t.titel_typ)}
|
||||
${t.formwert
|
||||
? `<span class="zp-badge" style="background:#3B82F6">${_esc(t.formwert)}</span>`
|
||||
? `<span class="zp-badge" style="background:#3B82F6">${UI.escape(t.formwert)}</span>`
|
||||
: ''}
|
||||
</div>
|
||||
<div class="zp-title-name">${_esc(t.titel_name || '—')}</div>
|
||||
<div class="zp-title-name">${UI.escape(t.titel_name || '—')}</div>
|
||||
<div class="zp-title-meta">
|
||||
${t.verliehen_am ? `${UI.icon('calendar-dots')} ${_fmtDate(t.verliehen_am)}` : ''}
|
||||
${t.ort ? ` · ${UI.icon('map-pin')} ${_esc(t.ort)}` : ''}
|
||||
${t.richter ? ` · ${UI.icon('user')} ${_esc(t.richter)}` : ''}
|
||||
${t.ausstellung ? `<br><span class="text-xs">${UI.icon('ticket')} ${_esc(t.ausstellung)}</span>` : ''}
|
||||
${t.ort ? ` · ${UI.icon('map-pin')} ${UI.escape(t.ort)}` : ''}
|
||||
${t.richter ? ` · ${UI.icon('user')} ${UI.escape(t.richter)}` : ''}
|
||||
${t.ausstellung ? `<br><span class="text-xs">${UI.icon('ticket')} ${UI.escape(t.ausstellung)}</span>` : ''}
|
||||
</div>
|
||||
</div>`).join('');
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ window.Page_zuchthunde = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
// ----------------------------------------------------------
|
||||
function _esc(s) {
|
||||
return UI.escape ? UI.escape(s || '') : (s || '').replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '—';
|
||||
|
|
@ -54,7 +50,7 @@ window.Page_zuchthunde = (() => {
|
|||
else if (e === '3' || e === 'ED 3') color = '#EF4444';
|
||||
}
|
||||
|
||||
return `<span class="zh-badge" style="background:${color}">${_esc(ergebnis || '—')}</span>`;
|
||||
return `<span class="zh-badge" style="background:${color}">${UI.escape(ergebnis || '—')}</span>`;
|
||||
}
|
||||
|
||||
function _geneticBadge(ergebnis) {
|
||||
|
|
@ -63,7 +59,7 @@ window.Page_zuchthunde = (() => {
|
|||
if (e === 'clear') color = '#22C55E';
|
||||
if (e === 'carrier') color = '#EAB308';
|
||||
if (e === 'affected') color = '#EF4444';
|
||||
return `<span class="zh-badge" style="background:${color}">${_esc(ergebnis || '—')}</span>`;
|
||||
return `<span class="zh-badge" style="background:${color}">${UI.escape(ergebnis || '—')}</span>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -103,7 +99,7 @@ window.Page_zuchthunde = (() => {
|
|||
const zwinger = _breederInfo?.zwingername || 'Mein Zwinger';
|
||||
const logoUrl = _breederInfo?.logo_url || null;
|
||||
const logoHtml = logoUrl
|
||||
? `<img src="${_esc(logoUrl)}" alt="Logo"
|
||||
? `<img src="${UI.escape(logoUrl)}" alt="Logo"
|
||||
style="width:48px;height:48px;border-radius:50%;object-fit:cover;
|
||||
border:2px solid rgba(196,132,58,.5);flex-shrink:0"
|
||||
onerror="this.style.display='none'">`
|
||||
|
|
@ -123,7 +119,7 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="flex-1-min">
|
||||
<h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
|
||||
color:var(--c-text);white-space:nowrap;overflow:hidden;
|
||||
text-overflow:ellipsis;line-height:1.2">${_esc(zwinger)}</h2>
|
||||
text-overflow:ellipsis;line-height:1.2">${UI.escape(zwinger)}</h2>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<svg style="width:11px;height:11px;color:var(--c-primary);flex-shrink:0" viewBox="0 0 256 256">
|
||||
<use href="/icons/phosphor.svg#lock-key"></use>
|
||||
|
|
@ -208,7 +204,7 @@ window.Page_zuchthunde = (() => {
|
|||
const el = document.getElementById('zh-list');
|
||||
if (el) el.innerHTML = `
|
||||
<p style="color:var(--c-danger);text-align:center;padding:var(--space-8)">
|
||||
${_esc(err.message || 'Fehler beim Laden.')}
|
||||
${UI.escape(err.message || 'Fehler beim Laden.')}
|
||||
</p>`;
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +224,7 @@ window.Page_zuchthunde = (() => {
|
|||
|
||||
if (!filtered.length) {
|
||||
el.innerHTML = _query
|
||||
? `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">Keine Treffer für „${_esc(_query)}".</p>`
|
||||
? `<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-6)">Keine Treffer für „${UI.escape(_query)}".</p>`
|
||||
: `
|
||||
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
|
||||
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
|
||||
|
|
@ -299,12 +295,12 @@ window.Page_zuchthunde = (() => {
|
|||
// Hund-Card HTML
|
||||
// ----------------------------------------------------------
|
||||
function _hundCardHTML(h) {
|
||||
const nameLabel = h.name ? _esc(h.name) : '<em class="text-muted">Unbenannt</em>';
|
||||
const rufname = h.rufname ? ` (${_esc(h.rufname)})` : '';
|
||||
const nameLabel = h.name ? UI.escape(h.name) : '<em class="text-muted">Unbenannt</em>';
|
||||
const rufname = h.rufname ? ` (${UI.escape(h.rufname)})` : '';
|
||||
const geburtstag = h.geburtsdatum ? _fmtDate(h.geburtsdatum) : null;
|
||||
|
||||
const vaterLabel = h.vater_name ? `Vater: ${_esc(h.vater_name)}` : null;
|
||||
const mutterLabel = h.mutter_name ? `Mutter: ${_esc(h.mutter_name)}` : null;
|
||||
const vaterLabel = h.vater_name ? `Vater: ${UI.escape(h.vater_name)}` : null;
|
||||
const mutterLabel = h.mutter_name ? `Mutter: ${UI.escape(h.mutter_name)}` : null;
|
||||
const eltern = [vaterLabel, mutterLabel].filter(Boolean).join(' · ');
|
||||
|
||||
const pubLabel = h.is_public
|
||||
|
|
@ -317,14 +313,14 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="flex-1-min">
|
||||
<div class="zh-card-title">
|
||||
${_genderIcon(h.geschlecht)}
|
||||
${nameLabel}${_esc(rufname)}
|
||||
${nameLabel}${UI.escape(rufname)}
|
||||
${pubLabel}
|
||||
</div>
|
||||
<div class="zh-card-meta">
|
||||
${h.rasse ? `${UI.icon('paw-print')} ${_esc(h.rasse)} ` : ''}
|
||||
${h.rasse ? `${UI.icon('paw-print')} ${UI.escape(h.rasse)} ` : ''}
|
||||
${geburtstag ? `${UI.icon('calendar-dots')} ${geburtstag} ` : ''}
|
||||
${h.chip_nr ? `${UI.icon('barcode')} ${_esc(h.chip_nr)} ` : ''}
|
||||
${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${_esc(h.zuchtbuchnummer)} ` : ''}
|
||||
${h.chip_nr ? `${UI.icon('barcode')} ${UI.escape(h.chip_nr)} ` : ''}
|
||||
${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${UI.escape(h.zuchtbuchnummer)} ` : ''}
|
||||
</div>
|
||||
${eltern ? `<div class="zh-card-meta text-xs-secondary">${eltern}</div>` : ''}
|
||||
</div>
|
||||
|
|
@ -416,7 +412,7 @@ window.Page_zuchthunde = (() => {
|
|||
const tests = await API.zuchthunde.healthTests(hundId);
|
||||
_renderHealthSection(hundId, wrap, tests);
|
||||
} catch (err) {
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${_esc(err.message || 'Fehler.')}</p>`;
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${UI.escape(err.message || 'Fehler.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -425,11 +421,11 @@ window.Page_zuchthunde = (() => {
|
|||
? tests.map(t => `
|
||||
<div class="zh-detail-row">
|
||||
<div class="zh-detail-info">
|
||||
<span class="zh-detail-label">${_esc(t.test_typ || 'Sonstiges')}</span>
|
||||
${t.test_name ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(t.test_name)}</span>` : ''}
|
||||
<span class="zh-detail-label">${UI.escape(t.test_typ || 'Sonstiges')}</span>
|
||||
${t.test_name ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(t.test_name)}</span>` : ''}
|
||||
${_healthBadge(t.test_typ || '', t.ergebnis)}
|
||||
${t.untersuch_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.untersuch_am)}</span>` : ''}
|
||||
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
|
||||
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen"
|
||||
class="text-danger">${UI.icon('trash')}</button>
|
||||
|
|
@ -480,7 +476,7 @@ window.Page_zuchthunde = (() => {
|
|||
const tests = await API.zuchthunde.geneticTests(hundId);
|
||||
_renderGeneticSection(hundId, wrap, tests);
|
||||
} catch (err) {
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${_esc(err.message || 'Fehler.')}</p>`;
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${UI.escape(err.message || 'Fehler.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -489,10 +485,10 @@ window.Page_zuchthunde = (() => {
|
|||
? tests.map(t => `
|
||||
<div class="zh-detail-row">
|
||||
<div class="zh-detail-info">
|
||||
<span class="zh-detail-label">${_esc(t.marker_name || '—')}</span>
|
||||
<span class="zh-detail-label">${UI.escape(t.marker_name || '—')}</span>
|
||||
${_geneticBadge(t.ergebnis_klasse)}
|
||||
${t.getestet_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.getestet_am)}</span>` : ''}
|
||||
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
|
||||
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen"
|
||||
class="text-danger">${UI.icon('trash')}</button>
|
||||
|
|
@ -543,7 +539,7 @@ window.Page_zuchthunde = (() => {
|
|||
const titles = await API.zuchthunde.titles(hundId);
|
||||
_renderTitlesSection(hundId, wrap, titles);
|
||||
} catch (err) {
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${_esc(err.message || 'Fehler.')}</p>`;
|
||||
wrap.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm);padding:var(--space-2)">${UI.escape(err.message || 'Fehler.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,12 +548,12 @@ window.Page_zuchthunde = (() => {
|
|||
? titles.map(t => `
|
||||
<div class="zh-detail-row">
|
||||
<div class="zh-detail-info">
|
||||
<span class="zh-detail-label">${_esc(t.titel_name || '—')}</span>
|
||||
${t.titel_typ ? `<span class="zh-badge" style="background:#6B7280">${_esc(t.titel_typ)}</span>` : ''}
|
||||
<span class="zh-detail-label">${UI.escape(t.titel_name || '—')}</span>
|
||||
${t.titel_typ ? `<span class="zh-badge" style="background:#6B7280">${UI.escape(t.titel_typ)}</span>` : ''}
|
||||
${t.verliehen_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.verliehen_am)}</span>` : ''}
|
||||
${t.ort ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.ort)}</span>` : ''}
|
||||
${t.richter ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.richter)}</span>` : ''}
|
||||
${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${_esc(t.formwert)}</span>` : ''}
|
||||
${t.ort ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.ort)}</span>` : ''}
|
||||
${t.richter ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.richter)}</span>` : ''}
|
||||
${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${UI.escape(t.formwert)}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen"
|
||||
class="text-danger">${UI.icon('trash')}</button>
|
||||
|
|
@ -614,13 +610,13 @@ window.Page_zuchthunde = (() => {
|
|||
const vaterOptions = [
|
||||
`<option value="">— kein Vater —</option>`,
|
||||
...maennliche.map(h =>
|
||||
`<option value="${h.id}" ${v.vater_id === h.id ? 'selected' : ''}>${_esc(h.name)}${h.rufname ? ` (${_esc(h.rufname)})` : ''}</option>`),
|
||||
`<option value="${h.id}" ${v.vater_id === h.id ? 'selected' : ''}>${UI.escape(h.name)}${h.rufname ? ` (${UI.escape(h.rufname)})` : ''}</option>`),
|
||||
].join('');
|
||||
|
||||
const mutterOptions = [
|
||||
`<option value="">— keine Mutter —</option>`,
|
||||
...weibliche.map(h =>
|
||||
`<option value="${h.id}" ${v.mutter_id === h.id ? 'selected' : ''}>${_esc(h.name)}${h.rufname ? ` (${_esc(h.rufname)})` : ''}</option>`),
|
||||
`<option value="${h.id}" ${v.mutter_id === h.id ? 'selected' : ''}>${UI.escape(h.name)}${h.rufname ? ` (${UI.escape(h.rufname)})` : ''}</option>`),
|
||||
].join('');
|
||||
|
||||
const body = `
|
||||
|
|
@ -630,12 +626,12 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Vollständiger Name <span class="text-danger">*</span></label>
|
||||
<input class="form-control" type="text" name="name" required
|
||||
value="${_esc(v.name || '')}" placeholder="z. B. Banyaro's Black Diamond">
|
||||
value="${UI.escape(v.name || '')}" placeholder="z. B. Banyaro's Black Diamond">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rufname</label>
|
||||
<input class="form-control" type="text" name="rufname"
|
||||
value="${_esc(v.rufname || '')}" placeholder="z. B. Diamond">
|
||||
value="${UI.escape(v.rufname || '')}" placeholder="z. B. Diamond">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -651,7 +647,7 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Geburtsdatum</label>
|
||||
<input class="form-control" type="date" name="geburtsdatum"
|
||||
value="${_esc(v.geburtsdatum || '')}">
|
||||
value="${UI.escape(v.geburtsdatum || '')}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -659,12 +655,12 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Sterbedatum</label>
|
||||
<input class="form-control" type="date" name="sterbedatum"
|
||||
value="${_esc(v.sterbedatum || '')}">
|
||||
value="${UI.escape(v.sterbedatum || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Farbe / Fell</label>
|
||||
<input class="form-control" type="text" name="farbe"
|
||||
value="${_esc(v.farbe || '')}" placeholder="z. B. schwarz-braun">
|
||||
value="${UI.escape(v.farbe || '')}" placeholder="z. B. schwarz-braun">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -672,19 +668,19 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Chip-Nr.</label>
|
||||
<input class="form-control" type="text" name="chip_nr"
|
||||
value="${_esc(v.chip_nr || '')}" placeholder="15-stellig">
|
||||
value="${UI.escape(v.chip_nr || '')}" placeholder="15-stellig">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Tätowiernummer</label>
|
||||
<input class="form-control" type="text" name="taetowier_nr"
|
||||
value="${_esc(v.taetowier_nr || '')}">
|
||||
value="${UI.escape(v.taetowier_nr || '')}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Zuchtbuchnummer</label>
|
||||
<input class="form-control" type="text" name="zuchtbuchnummer"
|
||||
value="${_esc(v.zuchtbuchnummer || '')}" placeholder="z. B. SZ 123456">
|
||||
value="${UI.escape(v.zuchtbuchnummer || '')}" placeholder="z. B. SZ 123456">
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
|
|
@ -702,19 +698,19 @@ window.Page_zuchthunde = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Züchter-Name</label>
|
||||
<input class="form-control" type="text" name="zuechter_name"
|
||||
value="${_esc(v.zuechter_name || '')}" placeholder="Bei Fremdzüchter">
|
||||
value="${UI.escape(v.zuechter_name || '')}" placeholder="Bei Fremdzüchter">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Eigentümer-Name</label>
|
||||
<input class="form-control" type="text" name="eigentuemer_name"
|
||||
value="${_esc(v.eigentuemer_name || '')}">
|
||||
value="${UI.escape(v.eigentuemer_name || '')}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notiz <span class="text-secondary">(intern)</span></label>
|
||||
<textarea class="form-control" name="notiz" rows="2"
|
||||
placeholder="Interne Anmerkungen…">${_esc(v.notiz || '')}</textarea>
|
||||
placeholder="Interne Anmerkungen…">${UI.escape(v.notiz || '')}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -1121,14 +1117,14 @@ window.Page_zuchthunde = (() => {
|
|||
`<option value="">-- Aus eigenen Hunden --</option>`,
|
||||
..._hunde
|
||||
.filter(h => h.geschlecht !== 'weiblich')
|
||||
.map(h => `<option value="${h.id}">${_esc(h.name)}${h.rufname ? ` (${_esc(h.rufname)})` : ''}</option>`),
|
||||
.map(h => `<option value="${h.id}">${UI.escape(h.name)}${h.rufname ? ` (${UI.escape(h.rufname)})` : ''}</option>`),
|
||||
].join('');
|
||||
|
||||
const muetterOptions = [
|
||||
`<option value="">-- Aus eigenen Hunden --</option>`,
|
||||
..._hunde
|
||||
.filter(h => h.geschlecht !== 'maennlich')
|
||||
.map(h => `<option value="${h.id}">${_esc(h.name)}${h.rufname ? ` (${_esc(h.rufname)})` : ''}</option>`),
|
||||
.map(h => `<option value="${h.id}">${UI.escape(h.name)}${h.rufname ? ` (${UI.escape(h.rufname)})` : ''}</option>`),
|
||||
].join('');
|
||||
|
||||
const body = `
|
||||
|
|
@ -1206,7 +1202,7 @@ window.Page_zuchthunde = (() => {
|
|||
if (v.gen_vater != null) genInfo.push(`Gen. ${v.gen_vater} Vater`);
|
||||
if (v.gen_mutter != null) genInfo.push(`Gen. ${v.gen_mutter} Mutter`);
|
||||
const genStr = genInfo.length ? ` <span style="color:var(--c-text-secondary);font-size:var(--text-xs)">(${genInfo.join(' / ')})</span>` : '';
|
||||
return `<li style="padding:var(--space-1) 0">${_esc(v.name || '—')}${genStr}</li>`;
|
||||
return `<li style="padding:var(--space-1) 0">${UI.escape(v.name || '—')}${genStr}</li>`;
|
||||
}).join('')
|
||||
: `<li class="text-muted">Keine gemeinsamen Vorfahren gefunden.</li>`;
|
||||
|
||||
|
|
@ -1220,13 +1216,13 @@ window.Page_zuchthunde = (() => {
|
|||
const wIssueHTML = (welfare.issues || []).map(i => `
|
||||
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(0,0,0,.06)">
|
||||
<span style="color:${wColor};flex-shrink:0">${UI.icon('warning')}</span>
|
||||
<span class="text-sm">${_esc(i.text)}</span>
|
||||
<span class="text-sm">${UI.escape(i.text)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
const wOkHTML = (welfare.ok_points || []).map(p => `
|
||||
<div style="display:flex;gap:8px;padding:4px 0">
|
||||
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
|
||||
<span class="text-sm-secondary">${_esc(p)}</span>
|
||||
<span class="text-sm-secondary">${UI.escape(p)}</span>
|
||||
</div>`).join('');
|
||||
|
||||
welfareHTML = `
|
||||
|
|
@ -1254,7 +1250,7 @@ window.Page_zuchthunde = (() => {
|
|||
${ik.toFixed(2)} %
|
||||
</div>
|
||||
<div style="font-size:var(--text-sm);color:${ampelColor};font-weight:var(--weight-semibold)">
|
||||
${_esc(result.ik_rating || ampelLabel)}
|
||||
${UI.escape(result.ik_rating || ampelLabel)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1314,7 +1310,7 @@ window.Page_zuchthunde = (() => {
|
|||
} catch (err) {
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
|
||||
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
body: `<p class="text-danger">${UI.escape(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
|
||||
});
|
||||
return;
|
||||
|
|
@ -1322,7 +1318,7 @@ window.Page_zuchthunde = (() => {
|
|||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
|
||||
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
|
||||
body: `<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${UI.escape(text)}</div>`,
|
||||
footer: `
|
||||
<button class="btn btn-secondary flex-1" id="ki-desc-copy">
|
||||
${UI.icon('clipboard-text')} Kopieren
|
||||
|
|
@ -1414,7 +1410,7 @@ window.Page_zuchthunde = (() => {
|
|||
} catch (err) {
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
|
||||
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
body: `<p class="text-danger">${UI.escape(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
|
||||
});
|
||||
return;
|
||||
|
|
@ -1429,7 +1425,7 @@ window.Page_zuchthunde = (() => {
|
|||
body: `
|
||||
${savedId ? `<p style="font-size:var(--text-xs);color:var(--c-success);margin:0 0 var(--space-3);display:flex;align-items:center;gap:4px">
|
||||
${UI.icon('check-circle')} Automatisch gespeichert</p>` : ''}
|
||||
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>`,
|
||||
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${UI.escape(text)}</div>`,
|
||||
footer: `
|
||||
<button class="btn btn-ghost btn-sm" id="ki-bericht-copy">
|
||||
${UI.icon('clipboard-text')} Kopieren
|
||||
|
|
@ -1532,7 +1528,7 @@ window.Page_zuchthunde = (() => {
|
|||
} catch (err) {
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`,
|
||||
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
body: `<p class="text-danger">${UI.escape(err.message || 'Fehler beim Generieren.')}</p>`,
|
||||
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
|
||||
});
|
||||
return;
|
||||
|
|
@ -1561,9 +1557,9 @@ window.Page_zuchthunde = (() => {
|
|||
<div style="padding:var(--space-3);border-radius:var(--radius-md);
|
||||
background:${empfehlungColor}18;border:1.5px solid ${empfehlungColor}40;
|
||||
font-weight:var(--weight-semibold);color:${empfehlungColor}">
|
||||
${UI.icon('check-circle')} ${_esc(empfehlungLabel)}
|
||||
${UI.icon('check-circle')} ${UI.escape(empfehlungLabel)}
|
||||
</div>` : ''}
|
||||
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${_esc(text)}</div>
|
||||
<div style="white-space:pre-wrap;font-size:var(--text-sm);line-height:1.6">${UI.escape(text)}</div>
|
||||
</div>`,
|
||||
footer: `
|
||||
<button class="btn btn-secondary flex-1" id="ki-paarung-copy">
|
||||
|
|
@ -1752,15 +1748,15 @@ window.Page_zuchthunde = (() => {
|
|||
<div style="position:relative;border-radius:var(--radius-md);overflow:hidden;
|
||||
border:${isPrimary ? '2px solid var(--c-primary)' : '1px solid var(--c-border)'};aspect-ratio:1"
|
||||
data-photo-id="${ph.id}">
|
||||
<a href="${_esc(ph.url || '')}" target="_blank" rel="noopener noreferrer">
|
||||
<img src="${_esc(thumb)}" alt="${_esc(ph.caption || '')}"
|
||||
<a href="${UI.escape(ph.url || '')}" target="_blank" rel="noopener noreferrer">
|
||||
<img src="${UI.escape(thumb)}" alt="${UI.escape(ph.caption || '')}"
|
||||
loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block"
|
||||
onerror="this.parentElement.parentElement.style.opacity='.4'">
|
||||
</a>
|
||||
${isPrimary ? `<span style="position:absolute;top:3px;left:3px;background:var(--c-primary);color:white;
|
||||
font-size:9px;font-weight:700;border-radius:999px;padding:1px 5px">Logo</span>` : ''}
|
||||
<!-- Sichtbarkeit -->
|
||||
<button class="bp-vis-btn" data-photo-id="${ph.id}" data-vis="${_esc(ph.visibility)}"
|
||||
<button class="bp-vis-btn" data-photo-id="${ph.id}" data-vis="${UI.escape(ph.visibility)}"
|
||||
style="position:absolute;bottom:0;left:0;right:0;background:${vis.color};color:#fff;
|
||||
border:none;cursor:pointer;font-size:9px;padding:2px 4px;font-weight:700">
|
||||
${vis.text}
|
||||
|
|
@ -1805,7 +1801,7 @@ window.Page_zuchthunde = (() => {
|
|||
});
|
||||
} catch (err) {
|
||||
const el = document.getElementById(galleryId);
|
||||
if (el) el.innerHTML = `<p class="text-danger">${_esc(err.message || 'Fehler')}</p>`;
|
||||
if (el) el.innerHTML = `<p class="text-danger">${UI.escape(err.message || 'Fehler')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<script src="/js/landing-init.js?v=1112"></script>
|
||||
<script src="/js/landing-init.js?v=1113"></script>
|
||||
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
||||
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
|
||||
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
============================================================ */
|
||||
|
||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||
const VER = '1112';
|
||||
const VER = '1113';
|
||||
const CACHE_VERSION = `by-v${VER}`;
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue