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

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