Release v1.1.4

This commit is contained in:
rene 2026-04-30 16:58:36 +02:00
commit 89c75bed81
5 changed files with 128 additions and 9 deletions

View file

@ -3,8 +3,8 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '536'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.1.3'; // ← semantische Version, wird bei make release gesetzt
const APP_VER = '539'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.1.4'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
const App = (() => {

View file

@ -136,7 +136,13 @@ window.Page_notes = (() => {
<!-- Header -->
<div class="notes-header">
<h2 class="notes-title">Notizblock</h2>
<span class="notes-count">${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''}</span>
<div style="display:flex;align-items:center;gap:var(--space-2)">
<span class="notes-count">${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''}</span>
<button id="notes-new-btn" class="btn btn-primary btn-sm" style="gap:4px">
<svg class="ph-icon" aria-hidden="true" style="width:15px;height:15px"><use href="/icons/phosphor.svg#plus"></use></svg>
${_filterType && _filterType !== '' ? _rubrik(_filterType).label : 'Neue Notiz'}
</button>
</div>
</div>
<!-- Datenschutz-Hinweis (einmalig, wegklickbar) -->
@ -361,6 +367,11 @@ window.Page_notes = (() => {
_container.querySelector('#notes-privacy-notice')?.remove();
});
// Neue Notiz
_container.querySelector('#notes-new-btn')?.addEventListener('click', () => {
_openCreateModal(_filterType || '');
});
// Filter-Chips
_container.querySelectorAll('.notes-chip').forEach(btn => {
btn.addEventListener('click', () => {
@ -464,6 +475,101 @@ window.Page_notes = (() => {
_render();
}
// ----------------------------------------------------------
// Create-Modal — neue Notiz mit vorausgewählter Kategorie
// ----------------------------------------------------------
function _openCreateModal(preselectedType = '') {
const ERSTELL_RUBRIKEN = RUBRIKEN.filter(r => r.type !== ''); // ohne "Alle"
let _selType = preselectedType || ERSTELL_RUBRIKEN[0].type;
const modalId = 'notes-create-modal';
document.getElementById(modalId)?.remove();
const overlay = document.createElement('div');
overlay.id = modalId;
overlay.style.cssText = `position:fixed;inset:0;z-index:9999;display:flex;align-items:flex-end;justify-content:center;background:rgba(0,0,0,0.45)`;
const _buildContent = () => {
const rb = _rubrik(_selType);
return `
<div style="background:var(--c-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
width:100%;max-width:480px;max-height:90vh;overflow-y:auto;
padding:var(--space-5) var(--space-4) var(--space-6);box-shadow:0 -4px 24px rgba(0,0,0,0.15)">
<div style="width:40px;height:4px;background:var(--c-border);border-radius:2px;margin:0 auto var(--space-4)"></div>
<h3 style="font-size:var(--text-base);font-weight:700;margin:0 0 var(--space-4)">Neue Notiz</h3>
<!-- Kategorie-Auswahl -->
<div style="margin-bottom:var(--space-4)">
<label style="display:block;font-size:var(--text-sm);font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Kategorie</label>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
${ERSTELL_RUBRIKEN.map(r => `
<button class="nc-cat" data-type="${r.type}"
style="font-size:var(--text-xs);font-weight:600;padding:4px var(--space-3);
border-radius:999px;border:1.5px solid ${_selType===r.type ? r.color : 'var(--c-border)'};
background:${_selType===r.type ? r.color+'22' : 'var(--c-surface-2)'};
color:${_selType===r.type ? r.color : 'var(--c-text-secondary)'};cursor:pointer">
${_esc(r.label)}
</button>`).join('')}
</div>
</div>
<!-- Text -->
<div style="margin-bottom:var(--space-4)">
<label style="display:block;font-size:var(--text-sm);font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Notiz</label>
<textarea id="nc-text" rows="5" placeholder="Was möchtest du festhalten…"
style="width:100%;padding:var(--space-3);border:1.5px solid var(--c-border);
border-radius:var(--radius-md);font-size:var(--text-sm);
font-family:var(--font-sans);background:var(--c-surface);
color:var(--c-text);resize:vertical;outline:none;line-height:1.5;
box-sizing:border-box"></textarea>
</div>
<div style="display:flex;gap:var(--space-3)">
<button id="nc-cancel" class="btn btn-ghost" style="flex:1">Abbrechen</button>
<button id="nc-save" class="btn btn-primary" style="flex:1">Speichern</button>
</div>
</div>`;
};
overlay.innerHTML = _buildContent();
document.body.appendChild(overlay);
const _rebind = () => {
overlay.querySelectorAll('.nc-cat').forEach(btn => {
btn.addEventListener('click', () => {
_selType = btn.dataset.type;
overlay.innerHTML = _buildContent();
_rebind();
overlay.querySelector('#nc-text')?.focus();
});
});
overlay.querySelector('#nc-cancel')?.addEventListener('click', () => overlay.remove());
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
overlay.querySelector('#nc-save')?.addEventListener('click', async () => {
const text = overlay.querySelector('#nc-text')?.value?.trim();
if (!text) { UI.toast.warning('Bitte einen Text eingeben.'); return; }
const btn = overlay.querySelector('#nc-save');
await UI.asyncButton(btn, async () => {
const rb = _rubrik(_selType);
await API.notes.create(_selType, 'standalone', {
text,
parent_label: rb.label,
});
overlay.remove();
_filterType = _selType;
await _reload();
UI.toast.success('Notiz gespeichert.');
});
});
setTimeout(() => overlay.querySelector('#nc-text')?.focus(), 100);
};
_rebind();
}
// ----------------------------------------------------------
// Edit-Modal (Bottom-Sheet Stil)
// ----------------------------------------------------------

View file

@ -604,14 +604,27 @@ window.Page_settings = (() => {
}
if (btn) btn.textContent = 'Prüfe…';
try {
// Aktuelle Version vom Server holen (no-cache)
const serverResp = await fetch('/js/app.js', { cache: 'no-store' });
const serverText = await serverResp.text();
const match = serverText.match(/APP_VERSION\s*=\s*'([^']+)'/);
const serverVersion = match?.[1] || null;
const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0';
// SW update anstoßen
const reg = await navigator.serviceWorker.getRegistration();
await reg?.update();
if (reg?.waiting) {
// Neuer SW wartet — sofort aktivieren
if (serverVersion && serverVersion !== localVersion) {
// Neuere Version verfügbar — Seite neu laden
if (reg?.waiting) reg.waiting.postMessage({ type: 'SKIP_WAITING' });
UI.toast.info(`Update auf v${serverVersion} verfügbar — Seite wird neu geladen…`);
setTimeout(() => location.reload(), 1500);
} else if (reg?.waiting) {
reg.waiting.postMessage({ type: 'SKIP_WAITING' });
UI.toast.success('Update wird installiert…');
} else {
UI.toast.success('Ban Yaro ist aktuell — v' + (typeof APP_VERSION !== 'undefined' ? APP_VERSION : '1.0.0') + '.');
UI.toast.success(`Ban Yaro ist aktuell — v${localVersion}`);
}
} catch {
UI.toast.error('Update-Prüfung fehlgeschlagen.');

View file

@ -1,6 +1,6 @@
{
"id": "/",
"version": "1.1.3",
"version": "1.1.4",
"name": "Ban Yaro — Die Hunde-Plattform",
"short_name": "Ban Yaro",
"description": "Alles rund um deinen Hund. Von Welpe bis Opa.",

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v559';
const CACHE_VERSION = 'by-v562';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
@ -134,13 +134,13 @@ function _isCacheableGet(pathname) {
// INSTALL — App Shell cachen
// ----------------------------------------------------------
self.addEventListener('install', event => {
self.skipWaiting(); // Sofort übernehmen — kein Warten auf Cache-Aufbau
event.waitUntil(
caches.open(CACHE_STATIC)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => caches.open(CACHE_API).then(c =>
fetch('/api/training/exercises').then(r => { if (r.ok) c.put('/api/training/exercises', r); }).catch(() => {})
))
.then(() => self.skipWaiting())
);
});