Refactor: UI.loadLeaflet, leafletMarker, escape, emptyState, locationPicker zentralisiert
- Task 1: UI.loadLeaflet() in ui.js (mit Cluster-Option), lokale _loadLeaflet() in diary/walks/routes/places/poison/events.js entfernt - Task 2: UI.escape() ersetzt lokale _esc()/_escape() in allen 5 Seiten-Modulen - Task 3: UI.emptyState() ersetzt lokale _emptyState() in diary/routes/events.js - Task 4: _fmtDate/_fmtDateShort in walks/poison bewusst behalten (anderes Format), Kommentare ergänzt - Task 5: UI.locationPicker() eingebaut in places/poison/events (ersetzt manuelle GPS-Input-Blöcke) - Task 6: UI.leafletMarker() factory in ui.js, Kreis-divIcon-Blöcke in walks/places/ poison ersetzt; events.js behält Diamant-Marker (andere Form) - SW by-v207, APP_VER 175
This commit is contained in:
parent
066b722c5e
commit
e98ce0d232
9 changed files with 761 additions and 471 deletions
|
|
@ -17,26 +17,6 @@ window.Page_diary = (() => {
|
|||
let _filterMilestone = false;
|
||||
const LIMIT = 20;
|
||||
|
||||
function _loadLeaflet() {
|
||||
if (window.L) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
const cssLoaded = document.querySelector('link[href*="leaflet"]')
|
||||
? Promise.resolve()
|
||||
: new Promise(res => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet'; link.href = '/css/leaflet.css';
|
||||
link.onload = res; link.onerror = res;
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
cssLoaded.then(() => {
|
||||
if (document.querySelector('script[src*="leaflet.js"]')) { resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.js'; s.onload = resolve; s.onerror = reject;
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _sourceIcon(source) {
|
||||
if (source === 'places') return 'star';
|
||||
if (source === 'osm') return 'map-pin';
|
||||
|
|
@ -70,7 +50,7 @@ window.Page_diary = (() => {
|
|||
async function init(container, appState) {
|
||||
_container = container;
|
||||
_appState = appState;
|
||||
_loadLeaflet(); // Leaflet im Hintergrund vorladen — kein await
|
||||
UI.loadLeaflet(); // Leaflet im Hintergrund vorladen — kein await
|
||||
await _render();
|
||||
}
|
||||
|
||||
|
|
@ -139,14 +119,14 @@ window.Page_diary = (() => {
|
|||
const cards = _appState.dogs.map(dog => {
|
||||
const isActive = dog.id === activeDogId;
|
||||
const av = dog.foto_url
|
||||
? `<img src="${_escape(dog.foto_url)}" alt="${_escape(dog.name)}">`
|
||||
? `<img src="${UI.escape(dog.foto_url)}" alt="${UI.escape(dog.name)}">`
|
||||
: `<span>${UI.icon('dog')}</span>`;
|
||||
return `
|
||||
<div class="diary-picker-card${isActive ? ' diary-picker-card--active' : ''}"
|
||||
data-dog-id="${dog.id}">
|
||||
<div class="diary-picker-av">${av}</div>
|
||||
<div class="diary-picker-name">${_escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="diary-picker-rasse">${_escape(dog.rasse)}</div>` : ''}
|
||||
<div class="diary-picker-name">${UI.escape(dog.name)}</div>
|
||||
${dog.rasse ? `<div class="diary-picker-rasse">${UI.escape(dog.rasse)}</div>` : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
|
|
@ -260,20 +240,6 @@ window.Page_diary = (() => {
|
|||
UI.setLoading(btn, false);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// EMPTY-STATE HELPER
|
||||
// ----------------------------------------------------------
|
||||
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>
|
||||
</svg>
|
||||
<div class="empty-state-title">${title}</div>
|
||||
${text ? `<p class="empty-state-text">${text}</p>` : ''}
|
||||
${cta ? `<div class="empty-state-cta">${cta}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// LISTE RENDERN — Timeline gruppiert nach Monat
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -282,12 +248,12 @@ window.Page_diary = (() => {
|
|||
if (!listEl) return;
|
||||
|
||||
if (_entries.length === 0) {
|
||||
listEl.innerHTML = _emptyState(
|
||||
'book-open',
|
||||
'Noch keine Tagebucheinträge',
|
||||
'Halte besondere Momente mit deinem Hund fest — Spaziergänge, Erlebnisse, Erinnerungen.',
|
||||
`<button class="btn btn-primary" id="diary-first-entry">Ersten Eintrag schreiben</button>`
|
||||
);
|
||||
listEl.innerHTML = UI.emptyState({
|
||||
icon: UI.icon('book-open'),
|
||||
title: 'Noch keine Tagebucheinträge',
|
||||
text: 'Halte besondere Momente mit deinem Hund fest — Spaziergänge, Erlebnisse, Erinnerungen.',
|
||||
action: `<button class="btn btn-primary" id="diary-first-entry">Ersten Eintrag schreiben</button>`,
|
||||
});
|
||||
listEl.querySelector('#diary-first-entry')
|
||||
?.addEventListener('click', () => _showForm(null));
|
||||
return;
|
||||
|
|
@ -339,11 +305,11 @@ window.Page_diary = (() => {
|
|||
: '';
|
||||
|
||||
const locationHtml = e.location_name
|
||||
? `<p class="diary-card-location"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>${_escape(e.location_name)}</p>`
|
||||
? `<p class="diary-card-location"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>${UI.escape(e.location_name)}</p>`
|
||||
: '';
|
||||
|
||||
const textPreview = e.text
|
||||
? `<p class="diary-card-text">${_escape(e.text.slice(0, 140))}${e.text.length > 140 ? '…' : ''}</p>`
|
||||
? `<p class="diary-card-text">${UI.escape(e.text.slice(0, 140))}${e.text.length > 140 ? '…' : ''}</p>`
|
||||
: '';
|
||||
|
||||
// Meilenstein-Badge (nur bei is_milestone=1, nicht bei manuell gewähltem Typ 'meilenstein')
|
||||
|
|
@ -363,7 +329,7 @@ window.Page_diary = (() => {
|
|||
<span class="diary-card-type">${typ.icon} ${typ.label}</span>
|
||||
<span class="diary-card-date">${dateStr}</span>
|
||||
</div>
|
||||
${e.titel ? `<div class="diary-card-title">${_escape(e.titel)}</div>` : ''}
|
||||
${e.titel ? `<div class="diary-card-title">${UI.escape(e.titel)}</div>` : ''}
|
||||
${locationHtml}
|
||||
${textPreview}
|
||||
${tagsHtml}
|
||||
|
|
@ -378,8 +344,8 @@ window.Page_diary = (() => {
|
|||
const avatars = dogIds.map(did => {
|
||||
const dog = _appState.dogs.find(d => d.id === did);
|
||||
if (!dog) return '';
|
||||
return `<div class="diary-dog-av" title="${_escape(dog.name)}">
|
||||
${dog.foto_url ? `<img src="${_escape(dog.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
return `<div class="diary-dog-av" title="${UI.escape(dog.name)}">
|
||||
${dog.foto_url ? `<img src="${UI.escape(dog.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `<div class="diary-dog-row">${avatars}</div>`;
|
||||
|
|
@ -408,9 +374,9 @@ window.Page_diary = (() => {
|
|||
const dog = _appState.dogs.find(d => d.id === did);
|
||||
return dog ? `<div class="diary-dog-chip">
|
||||
<div class="diary-dog-av">
|
||||
${dog.foto_url ? `<img src="${_escape(dog.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
${dog.foto_url ? `<img src="${UI.escape(dog.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
</div>
|
||||
<span>${_escape(dog.name)}</span>
|
||||
<span>${UI.escape(dog.name)}</span>
|
||||
</div>` : '';
|
||||
}).join('')}
|
||||
</div>`
|
||||
|
|
@ -428,11 +394,11 @@ window.Page_diary = (() => {
|
|||
${entry.location_name ? `
|
||||
<div class="diary-detail-location">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
|
||||
${entry.gps_lat ? `<a href="https://maps.apple.com/?q=${encodeURIComponent(entry.location_name)}&ll=${entry.gps_lat},${entry.gps_lon}" target="_blank" rel="noopener" style="color:inherit">${_escape(entry.location_name)}</a>` : _escape(entry.location_name)}
|
||||
${entry.gps_lat ? `<a href="https://maps.apple.com/?q=${encodeURIComponent(entry.location_name)}&ll=${entry.gps_lat},${entry.gps_lon}" target="_blank" rel="noopener" style="color:inherit">${UI.escape(entry.location_name)}</a>` : UI.escape(entry.location_name)}
|
||||
</div>` : ''}
|
||||
${dogsHtml}
|
||||
${entry.text
|
||||
? `<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_escape(entry.text)}</p>`
|
||||
? `<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${UI.escape(entry.text)}</p>`
|
||||
: ''}
|
||||
${tags.length
|
||||
? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:var(--space-3)">
|
||||
|
|
@ -486,9 +452,9 @@ window.Page_diary = (() => {
|
|||
<input type="checkbox" name="extra_dog" value="${d.id}"
|
||||
${entryDogIds.includes(d.id) ? 'checked' : ''}>
|
||||
<div class="diary-dog-av">
|
||||
${d.foto_url ? `<img src="${_escape(d.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
${d.foto_url ? `<img src="${UI.escape(d.foto_url)}" alt="">` : `<span>${UI.icon('dog')}</span>`}
|
||||
</div>
|
||||
<span>${_escape(d.name)}</span>
|
||||
<span>${UI.escape(d.name)}</span>
|
||||
</label>`).join('')}
|
||||
</div>
|
||||
</div>` : '';
|
||||
|
|
@ -507,12 +473,12 @@ window.Page_diary = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Titel <span style="color:var(--c-text-secondary)">(optional)</span></label>
|
||||
<input class="form-control" type="text" name="titel"
|
||||
value="${_escape(entry?.titel || '')}" placeholder="z.B. Erster Schultag">
|
||||
value="${UI.escape(entry?.titel || '')}" placeholder="z.B. Erster Schultag">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Text</label>
|
||||
<textarea class="form-control" name="text" rows="5"
|
||||
placeholder="Was ist passiert? Besonderheiten, Gedanken…">${_escape(entry?.text || '')}</textarea>
|
||||
placeholder="Was ist passiert? Besonderheiten, Gedanken…">${UI.escape(entry?.text || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group" id="diary-location-group">
|
||||
<label class="form-label">Ort <span style="color:var(--c-text-secondary)">(optional)</span></label>
|
||||
|
|
@ -532,7 +498,7 @@ window.Page_diary = (() => {
|
|||
<div id="diary-location-chip-wrap" style="${entry?.location_name ? '' : 'display:none'}">
|
||||
<div class="diary-location-chip">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
|
||||
<span id="diary-location-label">${_escape(entry?.location_name || '')}</span>
|
||||
<span id="diary-location-label">${UI.escape(entry?.location_name || '')}</span>
|
||||
<button type="button" id="diary-location-clear" aria-label="Name entfernen">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>
|
||||
</button>
|
||||
|
|
@ -549,7 +515,7 @@ window.Page_diary = (() => {
|
|||
</div>
|
||||
</div>
|
||||
${dogPickerHtml}
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="margin-top:var(--space-5)">
|
||||
<input type="checkbox" name="is_milestone" id="diary-milestone-cb"
|
||||
${entry?.is_milestone ? 'checked' : ''} style="display:none">
|
||||
<button type="button" id="diary-milestone-btn"
|
||||
|
|
@ -810,7 +776,7 @@ window.Page_diary = (() => {
|
|||
});
|
||||
|
||||
// Karte beim Formular-Open automatisch laden
|
||||
_loadLeaflet().then(() => {
|
||||
UI.loadLeaflet().then(() => {
|
||||
setTimeout(() => {
|
||||
const lat = _locLat || 48.0, lon = _locLon || 11.9, zoom = _locLat ? 15 : 7;
|
||||
_miniMap = L.map('diary-map-wrap', {
|
||||
|
|
@ -854,9 +820,9 @@ window.Page_diary = (() => {
|
|||
} else {
|
||||
sugEl.innerHTML = suggestions.map(s => `
|
||||
<button type="button" class="diary-location-suggestion"
|
||||
data-name="${_escape(s.name)}" data-lat="${s.lat}" data-lon="${s.lon}">
|
||||
data-name="${UI.escape(s.name)}" data-lat="${s.lat}" data-lon="${s.lon}">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${_sourceIcon(s.source)}"></use></svg>
|
||||
<span>${_escape(s.name)}</span>
|
||||
<span>${UI.escape(s.name)}</span>
|
||||
<small>${s.distance_m < 1000 ? s.distance_m + ' m' : (s.distance_m / 1000).toFixed(1) + ' km'}</small>
|
||||
</button>`).join('');
|
||||
sugEl.querySelectorAll('.diary-location-suggestion').forEach(el => {
|
||||
|
|
@ -983,12 +949,6 @@ window.Page_diary = (() => {
|
|||
.format(new Date(+y, +m - 1, 1));
|
||||
}
|
||||
|
||||
function _escape(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// IMPORT
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -998,7 +958,7 @@ window.Page_diary = (() => {
|
|||
body: `
|
||||
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin-bottom:var(--space-4)">
|
||||
Importiere Einträge aus einer anderen App in das Tagebuch von
|
||||
<strong>${_escape(_appState.activeDog?.name || 'deinem Hund')}</strong>.
|
||||
<strong>${UI.escape(_appState.activeDog?.name || 'deinem Hund')}</strong>.
|
||||
</p>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
|
||||
|
|
@ -1076,7 +1036,7 @@ window.Page_diary = (() => {
|
|||
|
||||
const errHtml = res.errors?.length
|
||||
? `<details style="margin-top:var(--space-2)"><summary style="font-size:var(--text-xs);cursor:pointer">${res.errors.length} Fehler anzeigen</summary>
|
||||
<pre style="font-size:var(--text-xs);white-space:pre-wrap;margin-top:var(--space-1)">${_escape(res.errors.join('\n'))}</pre></details>`
|
||||
<pre style="font-size:var(--text-xs);white-space:pre-wrap;margin-top:var(--space-1)">${UI.escape(res.errors.join('\n'))}</pre></details>`
|
||||
: '';
|
||||
|
||||
resultEl.innerHTML = `
|
||||
|
|
@ -1100,7 +1060,7 @@ window.Page_diary = (() => {
|
|||
resultEl.innerHTML = `
|
||||
<div style="background:var(--c-danger-subtle);border-radius:var(--radius-md);
|
||||
padding:var(--space-3) var(--space-4);color:var(--c-danger)">
|
||||
Fehler: ${_escape(e.message || String(e))}
|
||||
Fehler: ${UI.escape(e.message || String(e))}
|
||||
</div>`;
|
||||
resultEl.style.display = 'block';
|
||||
UI.setLoading(btn, false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue