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:
rene 2026-05-27 10:15:33 +02:00
parent e7939ce98e
commit c517c9281d
42 changed files with 1115 additions and 1341 deletions

View file

@ -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,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// ----------------------------------------------------------
// 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ----------------------------------------------------------
// 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">