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

@ -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=&quot;display:flex;align-items:center;justify-content:center;height:100%;font-size:2rem&quot;>🐶</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=&quot;display:flex;align-items:center;justify-content:center;height:100%;font-size:2.5rem&quot;>🐾</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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ----------------------------------------------------------
// PUBLIC API
// ----------------------------------------------------------