Feature: Ratings, Lightbox, Forum-Standort, Notifications, Routen-Recording, Chat-Picker
- Bewertungssystem (ratings.py): Sterne für Sitter/Walks/Places/Routen - Admin: Server-Log-Viewer + OSM-Cache-Statistiken - Chat: "Neue Nachricht"-Button mit Freundesliste-Picker - Forum: 5 neue Kategorien, Standorteingabe (locationPicker), Absende-Toast, Lightbox - Freunde: Aktivitäts-Filter (Chips), Freundschaftsanfrage → In-App-Notification - Sitter: locationPicker statt manuelle Koordinateneingabe + ratingStars - Tagebuch: Bilder-Lightbox im Detail-View, iOS-Modal-Header-Fix (90svh) - Routen: Start/Stopp-Button wechselt Zustand, nutzt Page_map.isRecording() - Benachrichtigungen: Delete-Button sichtbar, typ-basierte Navigation, Toast-Feedback - OSM: globales Semaphore + 429-Retry-Logic; Scheduler: München-Umland, täglich - SW by-v225, APP_VER 202
This commit is contained in:
parent
aa70a838f2
commit
e56183b642
21 changed files with 648 additions and 175 deletions
|
|
@ -53,6 +53,7 @@ window.Page_diary = (() => {
|
|||
meilenstein:{ label: 'Meilenstein',icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trophy"></use></svg>' },
|
||||
training: { label: 'Training', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#target"></use></svg>' },
|
||||
gesundheit: { label: 'Gesundheit', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#syringe"></use></svg>' },
|
||||
spaziergang:{ label: 'Spaziergang', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>' },
|
||||
ausflug: { label: 'Ausflug', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#car"></use></svg>' },
|
||||
};
|
||||
|
||||
|
|
@ -302,7 +303,7 @@ window.Page_diary = (() => {
|
|||
const typ = TYPEN[e.typ] || TYPEN.eintrag;
|
||||
const isMile = e.is_milestone || e.typ === 'meilenstein';
|
||||
const dateStr = e.datum ? UI.time.format(e.datum + 'T00:00:00') : '';
|
||||
const tags = (e.tags || []).slice(0, 4);
|
||||
const tags = (e.tags || []).filter(t => t && t.trim()).slice(0, 4);
|
||||
|
||||
const allMedia = _allMedia(e);
|
||||
const coverMedia = allMedia.find(m => m.is_cover) || allMedia[0] || null;
|
||||
|
|
@ -367,6 +368,18 @@ window.Page_diary = (() => {
|
|||
return `<div class="diary-dog-row">${avatars}</div>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// LIGHTBOX
|
||||
// ----------------------------------------------------------
|
||||
function _showLightbox(src) {
|
||||
const lb = document.createElement('div');
|
||||
lb.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.92);z-index:9999;display:flex;align-items:center;justify-content:center;cursor:zoom-out';
|
||||
lb.innerHTML = `<img src="${UI.escape(src)}" style="max-width:100%;max-height:100%;object-fit:contain;touch-action:pinch-zoom">
|
||||
<button style="position:absolute;top:16px;right:16px;background:rgba(255,255,255,.2);border:none;border-radius:50%;width:40px;height:40px;color:#fff;font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center">✕</button>`;
|
||||
lb.addEventListener('click', () => lb.remove());
|
||||
document.body.appendChild(lb);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// DETAIL-ANSICHT
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -376,7 +389,7 @@ window.Page_diary = (() => {
|
|||
|
||||
const typ = TYPEN[entry.typ] || TYPEN.eintrag;
|
||||
const isMile = entry.is_milestone || entry.typ === 'meilenstein';
|
||||
const tags = (entry.tags || []);
|
||||
const tags = (entry.tags || []).filter(t => t && t.trim());
|
||||
|
||||
const allMedia = _allMedia(entry);
|
||||
const photo = allMedia.length > 0
|
||||
|
|
@ -394,7 +407,8 @@ window.Page_diary = (() => {
|
|||
class="diary-cover-btn${m.is_cover ? ' diary-cover-btn--active' : ''}"
|
||||
data-media-id="${m.id}"
|
||||
aria-label="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}"
|
||||
title="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}">⭐</button>
|
||||
title="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}"
|
||||
style="background:${m.is_cover ? '#f5c518' : 'rgba(0,0,0,.45)'};color:${m.is_cover ? '#fff' : 'rgba(255,255,255,.7)'}"><svg style="width:16px;height:16px;display:block" aria-hidden="true"><use href="/icons/phosphor.svg#star"></use></svg></button>
|
||||
</div>`).join('')}
|
||||
</div>`)
|
||||
: '';
|
||||
|
|
@ -417,7 +431,6 @@ window.Page_diary = (() => {
|
|||
|
||||
const body = `
|
||||
${isMile ? `<div class="diary-detail-milestone-badge">${UI.icon('trophy')} Meilenstein</div>` : ''}
|
||||
${photo}
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center;margin-bottom:var(--space-3)">
|
||||
<span class="badge badge-primary">${typ.icon} ${typ.label}</span>
|
||||
<span style="color:var(--c-text-secondary);font-size:var(--text-sm)">
|
||||
|
|
@ -425,24 +438,31 @@ window.Page_diary = (() => {
|
|||
</span>
|
||||
</div>
|
||||
${entry.location_name ? `
|
||||
<div class="diary-detail-location">
|
||||
<div class="diary-detail-location" style="margin-bottom:var(--space-3)">
|
||||
<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">${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)">${UI.escape(entry.text)}</p>`
|
||||
? `<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text);margin-bottom:var(--space-4)">${UI.escape(_cleanText(entry.text))}</p>`
|
||||
: ''}
|
||||
${tags.length
|
||||
? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:var(--space-3)">
|
||||
? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);margin-bottom:var(--space-4)">
|
||||
${tags.map(t => `<span class="badge">${t}</span>`).join('')}
|
||||
</div>`
|
||||
: ''}
|
||||
<button class="btn btn-secondary" style="width:100%;margin-top:var(--space-5)" id="detail-edit">Bearbeiten</button>
|
||||
${dogsHtml}
|
||||
${photo}
|
||||
<button class="btn btn-secondary" style="width:100%;margin-top:var(--space-4)" id="detail-edit">Bearbeiten</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: entry.titel || typ.label, body });
|
||||
|
||||
// Bilder anklickbar machen (Lightbox)
|
||||
document.querySelector('#modal-container .modal-body')?.querySelectorAll('img').forEach(img => {
|
||||
img.style.cursor = 'zoom-in';
|
||||
img.addEventListener('click', () => _showLightbox(img.src));
|
||||
});
|
||||
|
||||
// Stern-Buttons: Cover-Bild setzen
|
||||
document.querySelectorAll('.diary-cover-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (ev) => {
|
||||
|
|
@ -460,8 +480,12 @@ window.Page_diary = (() => {
|
|||
document.querySelectorAll('.diary-cover-btn').forEach(b => {
|
||||
const active = parseInt(b.dataset.mediaId) === mediaId;
|
||||
b.classList.toggle('diary-cover-btn--active', active);
|
||||
b.style.background = active ? '#f5c518' : 'rgba(0,0,0,.45)';
|
||||
b.style.color = active ? '#fff' : 'rgba(255,255,255,.7)';
|
||||
b.setAttribute('aria-label', active ? 'Cover-Bild' : 'Als Cover setzen');
|
||||
b.setAttribute('title', active ? 'Cover-Bild' : 'Als Cover setzen');
|
||||
const use = b.querySelector('use');
|
||||
if (use) use.setAttribute('href', `/icons/phosphor.svg#${active ? 'star-fill' : 'star'}`);
|
||||
});
|
||||
UI.toast.success('Cover-Bild gesetzt.');
|
||||
} catch {
|
||||
|
|
@ -593,25 +617,14 @@ window.Page_diary = (() => {
|
|||
<!-- Neue Medien: Vorschau-Grid -->
|
||||
<div id="diary-new-media-grid" class="diary-media-grid" style="display:none"></div>
|
||||
|
||||
<!-- versteckte Inputs -->
|
||||
<input type="file" id="diary-media-input" accept="image/*,video/*" style="display:none">
|
||||
<input type="file" id="diary-camera-input" accept="image/*,video/*" capture="environment" style="display:none">
|
||||
<!-- versteckter Input — multiple für Mehrfachauswahl -->
|
||||
<input type="file" id="diary-media-input" accept="image/*,video/*" multiple style="display:none">
|
||||
|
||||
<!-- Auswahlbuttons — immer sichtbar -->
|
||||
<div class="diary-media-picker" style="margin-top:var(--space-2)">
|
||||
<button type="button" class="diary-media-pick-btn" id="diary-btn-camera">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#camera"></use></svg>
|
||||
Kamera
|
||||
</button>
|
||||
<button type="button" class="diary-media-pick-btn" id="diary-btn-library">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#images"></use></svg>
|
||||
Mediathek
|
||||
</button>
|
||||
<button type="button" class="diary-media-pick-btn" id="diary-btn-file">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#folder-open"></use></svg>
|
||||
Datei
|
||||
</button>
|
||||
</div>
|
||||
<!-- Einzelner Button — iOS zeigt nativen Picker (Mediathek / Kamera / Datei) -->
|
||||
<label for="diary-media-input" class="btn btn-secondary" style="margin-top:var(--space-2);cursor:pointer;display:flex;align-items:center;gap:var(--space-2);justify-content:center">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#images"></use></svg>
|
||||
Fotos / Videos hinzufügen
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
|
@ -637,7 +650,6 @@ window.Page_diary = (() => {
|
|||
|
||||
// ---- Multi-Media-Verwaltung ----
|
||||
const mediaInput = document.getElementById('diary-media-input');
|
||||
const cameraInput = document.getElementById('diary-camera-input');
|
||||
|
||||
// Neue Dateien die noch nicht hochgeladen wurden
|
||||
const _newFiles = [];
|
||||
|
|
@ -646,16 +658,17 @@ window.Page_diary = (() => {
|
|||
const grid = document.getElementById('diary-new-media-grid');
|
||||
if (!grid) return;
|
||||
if (_newFiles.length === 0) { grid.style.display = 'none'; grid.innerHTML = ''; return; }
|
||||
grid.style.display = '';
|
||||
grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:8px;margin-bottom:8px';
|
||||
grid.innerHTML = _newFiles.map((f, i) => {
|
||||
const objUrl = URL.createObjectURL(f);
|
||||
const thumb = f.type.startsWith('video/')
|
||||
? `<video src="${objUrl}" class="diary-media-thumb" muted playsinline></video>`
|
||||
: `<img src="${objUrl}" alt="" class="diary-media-thumb">`;
|
||||
return `<div class="diary-media-thumb-wrap" data-new-idx="${i}">
|
||||
return `<div style="position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--c-surface-2)" data-new-idx="${i}">
|
||||
${thumb}
|
||||
<button type="button" class="diary-media-thumb-del" data-new-idx="${i}"
|
||||
aria-label="Entfernen">${UI.icon('x')}</button>
|
||||
aria-label="Entfernen"
|
||||
style="position:absolute;top:4px;right:4px;width:24px;height:24px;border-radius:50%;border:none;background:rgba(0,0,0,.55);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;font-size:14px">✕</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
grid.querySelectorAll('.diary-media-thumb-del').forEach(btn => {
|
||||
|
|
@ -673,22 +686,24 @@ window.Page_diary = (() => {
|
|||
if (!wrap) return;
|
||||
const items = isEdit ? _allMedia(entry) : [];
|
||||
if (items.length === 0) { wrap.innerHTML = ''; return; }
|
||||
const grid = `<div class="diary-media-grid" style="margin-bottom:var(--space-2)">
|
||||
${items.map(m => `
|
||||
<div class="diary-media-thumb-wrap" data-media-id="${m.id || ''}">
|
||||
const GRID_STYLE = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:8px;margin-bottom:8px';
|
||||
const grid = `<div style="${GRID_STYLE}">
|
||||
${items.map((m, idx) => `
|
||||
<div style="position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--c-surface-2)"
|
||||
data-media-id="${m.id ?? ''}">
|
||||
${m.media_type === 'video'
|
||||
? `<video src="${m.url}" class="diary-media-thumb" muted playsinline></video>`
|
||||
: `<img src="${m.url}" alt="" class="diary-media-thumb">`}
|
||||
${m.id != null
|
||||
? `<button type="button" class="diary-media-thumb-del" data-media-id="${m.id}"
|
||||
aria-label="Entfernen">${UI.icon('x')}</button>
|
||||
<button type="button"
|
||||
class="diary-cover-btn diary-cover-btn--form${m.is_cover ? ' diary-cover-btn--active' : ''}"
|
||||
data-media-id="${m.id}"
|
||||
aria-label="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}"
|
||||
title="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}">⭐</button>`
|
||||
: `<button type="button" class="diary-media-thumb-del" data-legacy="1"
|
||||
aria-label="Entfernen">${UI.icon('x')}</button>`}
|
||||
? `<video src="${m.url}" style="width:100%;height:100%;object-fit:cover;display:block" muted playsinline></video>`
|
||||
: `<img src="${m.url}" alt="" style="width:100%;height:100%;object-fit:cover;display:block">`}
|
||||
<button type="button" class="diary-media-thumb-del"
|
||||
data-media-id="${m.id ?? ''}" data-legacy="${m.id == null ? '1' : ''}"
|
||||
aria-label="Entfernen"
|
||||
style="position:absolute;top:4px;right:4px;width:24px;height:24px;border-radius:50%;border:none;background:rgba(0,0,0,.55);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;font-size:14px">✕</button>
|
||||
${m.id != null ? `
|
||||
<button type="button" class="diary-cover-btn diary-cover-btn--form${m.is_cover ? ' diary-cover-btn--active' : ''}"
|
||||
data-media-id="${m.id}" data-sort="${idx}"
|
||||
aria-label="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}"
|
||||
title="${m.is_cover ? 'Cover-Bild' : 'Als Cover setzen'}"
|
||||
style="position:absolute;bottom:4px;left:4px;width:28px;height:28px;border-radius:50%;border:none;background:${m.is_cover ? '#f5c518' : 'rgba(0,0,0,.45)'};color:${m.is_cover ? '#fff' : 'rgba(255,255,255,.7)'};cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;z-index:2"><svg style="width:16px;height:16px;display:block;flex-shrink:0" aria-hidden="true"><use href="/icons/phosphor.svg#star"></use></svg></button>` : ''}
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
wrap.innerHTML = grid;
|
||||
|
|
@ -730,8 +745,12 @@ window.Page_diary = (() => {
|
|||
wrap.querySelectorAll('.diary-cover-btn--form').forEach(b => {
|
||||
const active = parseInt(b.dataset.mediaId) === mediaId;
|
||||
b.classList.toggle('diary-cover-btn--active', active);
|
||||
b.style.background = active ? '#f5c518' : 'rgba(0,0,0,.45)';
|
||||
b.style.color = active ? '#fff' : 'rgba(255,255,255,.7)';
|
||||
b.setAttribute('aria-label', active ? 'Cover-Bild' : 'Als Cover setzen');
|
||||
b.setAttribute('title', active ? 'Cover-Bild' : 'Als Cover setzen');
|
||||
const use = b.querySelector('use');
|
||||
if (use) use.setAttribute('href', `/icons/phosphor.svg#${active ? 'star-fill' : 'star'}`);
|
||||
});
|
||||
UI.toast.success('Cover-Bild gesetzt.');
|
||||
} catch {
|
||||
|
|
@ -760,12 +779,6 @@ window.Page_diary = (() => {
|
|||
tmp.click();
|
||||
}
|
||||
|
||||
cameraInput?.addEventListener('change', () => {
|
||||
if (cameraInput.files.length) {
|
||||
_addFiles(cameraInput.files);
|
||||
cameraInput.value = '';
|
||||
}
|
||||
});
|
||||
mediaInput?.addEventListener('change', () => {
|
||||
if (mediaInput.files.length) {
|
||||
_addFiles(mediaInput.files);
|
||||
|
|
@ -773,10 +786,6 @@ window.Page_diary = (() => {
|
|||
}
|
||||
});
|
||||
|
||||
document.getElementById('diary-btn-camera') ?.addEventListener('click', () => cameraInput.click());
|
||||
document.getElementById('diary-btn-library')?.addEventListener('click', () => _openPicker({}));
|
||||
document.getElementById('diary-btn-file') ?.addEventListener('click', () => _openPicker({ noAccept: true }));
|
||||
|
||||
document.getElementById('diary-form-cancel')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
// Milestone-Toggle
|
||||
|
|
@ -1168,6 +1177,16 @@ window.Page_diary = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
function _cleanText(text) {
|
||||
if (!text) return text;
|
||||
return text
|
||||
.replace(/!\[([^\]]*)\]\([^\)]*\)/g, '') // Markdown-Bilder ![]()
|
||||
.replace(/\[([^\]]*)\]\([^\)]*\)/g, '$1') // Markdown-Links [text](url) → text
|
||||
.replace(/^\[\]\s*$/gm, '') // leere [] auf eigener Zeile
|
||||
.replace(/\n{3,}/g, '\n\n') // mehrfache Leerzeilen kürzen
|
||||
.trim();
|
||||
}
|
||||
|
||||
return { init, refresh, openNew, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue