Fix: Notes-Karten — Zeilenumbruch, Clamp + Detail-Modal beim Klick, SW by-v1119

User-Report: Zeilenumbrüche in Notes-Karten gingen nicht, kein Scroll,
keine Detail-Ansicht.

Drei Probleme behoben:

1. _truncate-Limit zu aggressiv (150 Zeichen)
   → erhöht auf 600 Zeichen damit Karten lange Notizen mit Newlines
     sichtbar anzeigen können (CSS-Clamp erledigt visuell den Rest)

2. .list-item-text + .notes-card-text Override-Konflikt
   list-item-text hat fest -webkit-line-clamp:2 mit display:-webkit-box.
   Notes-Override hatte display:block — das deaktiviert clamp komplett,
   aber dann zeigt der Text die ersten 150 Zeichen ohne Newline-Hinweis.
   → Neuer Override: display:-webkit-box + -webkit-line-clamp:5 +
     white-space:pre-wrap → 5 Zeilen mit Newlines sichtbar, Rest '…'

3. Keine Detail-Ansicht beim Klick auf Karte
   → Neue Funktion _openDetailModal(note):
     - Voller Notiz-Text scrollbar (.notes-detail-text mit max-height:60vh)
     - Rubrik-Icon + Label im Titel
     - Parent-Label, Micro-Badges, Meta (Zeit + Ort)
     - Footer: 'Bearbeiten' (öffnet Edit-Modal) + 'Schließen'
   → Card-Click bindet darauf; Klicks auf Action-Buttons werden via
     closest('.list-item-action-btn') ignoriert (kein doppeltes Handling)
This commit is contained in:
rene 2026-05-27 14:42:47 +02:00
parent 1ff66a7083
commit c7a84438d1
6 changed files with 84 additions and 19 deletions

View file

@ -1 +1 @@
1118
1119

View file

@ -86,14 +86,14 @@
<title>Ban Yaro</title>
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
<script src="/js/boot-early.js?v=1118"></script>
<script src="/js/boot-early.js?v=1119"></script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1118">
<link rel="stylesheet" href="/css/layout.css?v=1118">
<link rel="stylesheet" href="/css/components.css?v=1118">
<link rel="stylesheet" href="/css/utilities.css?v=1118">
<link rel="stylesheet" href="/css/lists.css?v=1118">
<link rel="stylesheet" href="/css/design-system.css?v=1119">
<link rel="stylesheet" href="/css/layout.css?v=1119">
<link rel="stylesheet" href="/css/components.css?v=1119">
<link rel="stylesheet" href="/css/utilities.css?v=1119">
<link rel="stylesheet" href="/css/lists.css?v=1119">
</head>
<body>
@ -617,11 +617,11 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1118"></script>
<script src="/js/ui.js?v=1118"></script>
<script src="/js/app.js?v=1118"></script>
<script src="/js/worlds.js?v=1118"></script>
<script src="/js/offline-indicator.js?v=1118"></script>
<script src="/js/api.js?v=1119"></script>
<script src="/js/ui.js?v=1119"></script>
<script src="/js/app.js?v=1119"></script>
<script src="/js/worlds.js?v=1119"></script>
<script src="/js/offline-indicator.js?v=1119"></script>
<!-- Feature-Seiten werden lazy geladen -->
@ -631,7 +631,7 @@
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
<script src="/js/boot.js?v=1118"></script>
<script src="/js/boot.js?v=1119"></script>
</body>

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '1118'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '1119'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;

View file

@ -68,7 +68,10 @@ window.Page_notes = (() => {
} catch (_) { return 'Älteres'; }
}
function _truncate(str, max = 150) {
function _truncate(str, max = 600) {
// Karten zeigen max 5 Zeilen via CSS-Clamp — Text muss lang genug
// sein dass die Clamp greift. Bei sehr langen Notes: vor Clamp abschneiden
// damit der String nicht riesig in der DOM-Page steht.
if (!str) return '';
return str.length > max ? str.slice(0, max) + '…' : str;
}
@ -248,8 +251,12 @@ window.Page_notes = (() => {
/* .notes-card-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--c-text-muted); } */
/* Notes-Override: Actions in Top-Zeile rechts ausrichten (statt align-self:center bei list-item-actions) */
.notes-card-actions { margin-left: auto; align-self: flex-start; }
/* Notes-Override: Text ohne -webkit-line-clamp (komplett anzeigen) + pre-wrap */
.notes-card-text { line-height: 1.55; white-space: pre-wrap; margin: 0; display: block; -webkit-line-clamp: unset; overflow: visible; color: var(--c-text); }
/* Notes-Override: Newlines (pre-wrap) + max 5 Zeilen mit "…", Rest in Detail-Modal */
.notes-card-text { line-height: 1.55; white-space: pre-wrap; margin: 0; color: var(--c-text);
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5; overflow: hidden; }
/* Detail-Modal: voller Notiz-Text scrollbar */
.notes-detail-text { white-space: pre-wrap; line-height: 1.6; font-size: var(--text-base);
color: var(--c-text); margin: 0; max-height: 60vh; overflow-y: auto; }
/* TODO nach Migration entfernen: ersetzt durch .list-item-micro-badges / .list-item-micro-badge */
/* .notes-micro-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); } */
/* .notes-micro-badge { font-size: var(--text-xs); padding: 2px 6px; border-radius: var(--radius-sm); background: var(--c-surface-2); color: var(--c-text-secondary); } */
@ -458,6 +465,64 @@ window.Page_notes = (() => {
}
});
});
// Karte selbst klickbar → Detail-Modal mit vollem Text
_container.querySelectorAll('.notes-card').forEach(card => {
card.addEventListener('click', e => {
// Klicks auf Action-Buttons nicht doppelt verarbeiten
if (e.target.closest('.list-item-action-btn')) return;
const note = _notes.find(n => n.id === parseInt(card.dataset.id, 10));
if (note) _openDetailModal(note);
});
});
}
// ----------------------------------------------------------
// Detail-Modal: voller Notiz-Text + Meta + Bearbeiten/Löschen
// ----------------------------------------------------------
function _openDetailModal(note) {
const rb = RUBRIKEN.find(r => r.id === note.rubrik) || RUBRIKEN[0];
const meta = (() => { try { return JSON.parse(note.meta || '{}'); } catch { return {}; } })();
const microBadges = [];
if (meta.erfolg) microBadges.push(`🐾 ${meta.erfolg}/5`);
if (meta.umgebung) microBadges.push({ zuhause: '🏠 Zuhause', natur: '🌿 Natur', stadt: '🌆 Stadt' }[meta.umgebung] || meta.umgebung);
if (meta.hund_stimmung) microBadges.push({ super: '😊 Super', ok: '😐 Ok', mude: '😔 Müde' }[meta.hund_stimmung] || meta.hund_stimmung);
UI.modal.open({
title: `${UI.icon(rb.icon)} ${UI.escape(rb.label)}`,
body: `
<div class="flex-col-gap-3">
${note.parent_label
? `<div class="text-sm-secondary"><strong>${UI.escape(note.parent_label)}</strong></div>` : ''}
<p class="notes-detail-text">${UI.escape(note.text || '')}</p>
${microBadges.length ? `
<div class="list-item-micro-badges">
${microBadges.map(b => `<span class="list-item-micro-badge">${UI.escape(b)}</span>`).join('')}
</div>` : ''}
<div class="list-item-meta-row" style="margin-top:var(--space-2)">
<svg class="ph-icon icon-sm" aria-hidden="true"><use href="/icons/phosphor.svg#clock"></use></svg>
${UI.escape(_formatTime(note.updated_at || note.created_at))}
${note.location_name
? `<svg class="ph-icon icon-sm" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> ${UI.escape(note.location_name)}` : ''}
</div>
</div>
`,
footer: `
<div class="flex-gap-2" style="width:100%">
<button class="btn btn-ghost flex-1" id="notes-detail-edit">
${UI.icon('pencil')} Bearbeiten
</button>
<button class="btn btn-secondary" data-modal-close>Schließen</button>
</div>
`,
});
document.getElementById('notes-detail-edit')?.addEventListener('click', () => {
UI.modal.close();
_openEditModal(note);
});
}
// ----------------------------------------------------------

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1118"></script>
<script src="/js/landing-init.js?v=1119"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">

View file

@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
const VER = '1118';
const VER = '1119';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten