merge: beta into master

This commit is contained in:
Scarriffle
2026-05-19 09:35:15 +02:00
7 changed files with 189 additions and 1 deletions

View File

@@ -975,11 +975,15 @@ a { color: var(--primary); text-decoration: none; }
} }
.ctx-item:hover { background: var(--bg-hover); } .ctx-item:hover { background: var(--bg-hover); }
<<<<<<< HEAD
/* ── Event Popup ────────────────────────────────────────── /* ── Event Popup ──────────────────────────────────────────
Layout: Color-Dot + Title links, kleine Icon-Toolbar rechts oben. Layout: Color-Dot + Title links, kleine Icon-Toolbar rechts oben.
Icons sind im Ruhezustand transparent (nur das SVG selbst sichtbar), Icons sind im Ruhezustand transparent (nur das SVG selbst sichtbar),
bekommen erst beim Hover einen runden farbigen Hintergrund. Wirkt bekommen erst beim Hover einen runden farbigen Hintergrund. Wirkt
modern und lässt dem Titel die meiste Breite. */ modern und lässt dem Titel die meiste Breite. */
=======
/* ── Event Popup ────────────────────────────────────────── */
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
.event-popup { .event-popup {
position: fixed; z-index: 600; position: fixed; z-index: 600;
background: var(--bg-surface); background: var(--bg-surface);
@@ -1697,12 +1701,24 @@ a { color: var(--primary); text-decoration: none; }
.topbar-left { gap: 0; } .topbar-left { gap: 0; }
.topbar-right { gap: 0; } .topbar-right { gap: 0; }
<<<<<<< HEAD
/* Event-Popup auf Mobile: an Viewport-Breite anpassen */ /* Event-Popup auf Mobile: an Viewport-Breite anpassen */
.event-popup { width: min(94vw, 380px); max-width: 94vw; } .event-popup { width: min(94vw, 380px); max-width: 94vw; }
.popup-header { padding: 10px 8px 10px 14px; } .popup-header { padding: 10px 8px 10px 14px; }
.popup-header h4 { font-size: 13.5px; } .popup-header h4 { font-size: 13.5px; }
.popup-icon-btn { width: 32px; height: 32px; } .popup-icon-btn { width: 32px; height: 32px; }
.popup-icon-btn svg { width: 16px; height: 16px; } .popup-icon-btn svg { width: 16px; height: 16px; }
=======
/* Event-Popup: Buttons kompakt halten, kein 44px-Override ───── */
.event-popup .icon-btn {
min-width: 32px !important;
min-height: 32px !important;
width: 32px;
height: 32px;
}
.event-popup .popup-header { gap: 2px; padding: 10px 12px; }
.event-popup { width: min(92vw, 340px); max-width: 92vw; }
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
/* Monatsansicht: Startzeit ausblenden — nur Titel anzeigen ──── */ /* Monatsansicht: Startzeit ausblenden — nur Titel anzeigen ──── */
.month-event-time { display: none; } .month-event-time { display: none; }

View File

@@ -4,7 +4,11 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<!-- APP_VERSION: update here + version.js on every release --> <!-- APP_VERSION: update here + version.js on every release -->
<<<<<<< HEAD
<title>Calendarr v17</title> <title>Calendarr v17</title>
=======
<title>Calendarr v11</title>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#4285f4" /> <meta name="theme-color" content="#4285f4" />
@@ -80,7 +84,11 @@
<button type="submit" class="btn btn-primary btn-full">Anmelden</button> <button type="submit" class="btn btn-primary btn-full">Anmelden</button>
</form> </form>
</div> </div>
<<<<<<< HEAD
<button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v17</button> <button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v17</button>
=======
<button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v11</button>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
<!-- ─── MAIN APP ──────────────────────────────────────────── --> <!-- ─── MAIN APP ──────────────────────────────────────────── -->
@@ -185,7 +193,7 @@
<span data-i18n="my_calendars">Meine Kalender</span> <span data-i18n="my_calendars">Meine Kalender</span>
<div class="add-cal-dropdown-wrap"> <div class="add-cal-dropdown-wrap">
<button class="icon-btn mini-btn" id="btn-add-cal" title="Kalender hinzufügen"> <button class="icon-btn mini-btn" id="btn-add-cal" title="Kalender hinzufügen">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13h-6v11h-2v-6H5v-2h6V5h2v11h6v2z"/></svg>
</button> </button>
<div class="add-cal-dropdown hidden" id="add-cal-dropdown"> <div class="add-cal-dropdown hidden" id="add-cal-dropdown">
<button data-action="local">Lokaler Kalender</button> <button data-action="local">Lokaler Kalender</button>
@@ -199,7 +207,11 @@
<div id="cal-list-items"></div> <div id="cal-list-items"></div>
</div> </div>
</div> </div>
<<<<<<< HEAD
<button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v17</button> <button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v17</button>
=======
<button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v11</button>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</aside> </aside>
<div id="sidebar-backdrop" class="sidebar-backdrop"></div> <div id="sidebar-backdrop" class="sidebar-backdrop"></div>
@@ -235,7 +247,11 @@
<input type="hidden" id="ev-start" /> <input type="hidden" id="ev-start" />
<div class="dt-display" id="ev-start-display" tabindex="0" role="button"> <div class="dt-display" id="ev-start-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<<<<<<< HEAD
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg>
=======
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v11H7z"/></svg>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
</div> </div>
<div class="form-group half"> <div class="form-group half">
@@ -243,7 +259,11 @@
<input type="hidden" id="ev-end" /> <input type="hidden" id="ev-end" />
<div class="dt-display" id="ev-end-display" tabindex="0" role="button"> <div class="dt-display" id="ev-end-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<<<<<<< HEAD
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg>
=======
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v11H7z"/></svg>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
</div> </div>
</div> </div>
@@ -253,7 +273,11 @@
<input type="hidden" id="ev-start-date" /> <input type="hidden" id="ev-start-date" />
<div class="dt-display" id="ev-start-date-display" tabindex="0" role="button"> <div class="dt-display" id="ev-start-date-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<<<<<<< HEAD
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg>
=======
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v11H7z"/></svg>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
</div> </div>
<div class="form-group half"> <div class="form-group half">
@@ -261,7 +285,11 @@
<input type="hidden" id="ev-end-date" /> <input type="hidden" id="ev-end-date" />
<div class="dt-display" id="ev-end-date-display" tabindex="0" role="button"> <div class="dt-display" id="ev-end-date-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<<<<<<< HEAD
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg>
=======
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v11H7z"/></svg>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
</div> </div>
</div> </div>
@@ -311,7 +339,11 @@
<input type="hidden" id="ev-rec-until" /> <input type="hidden" id="ev-rec-until" />
<div class="dt-display" id="ev-rec-until-display" tabindex="0" role="button"> <div class="dt-display" id="ev-rec-until-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<<<<<<< HEAD
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v17H7z"/></svg>
=======
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v11H7z"/></svg>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
</div> </div>
</div> </div>
@@ -375,6 +407,7 @@
<div class="popup-header"> <div class="popup-header">
<div class="popup-color-dot" id="popup-color-dot"></div> <div class="popup-color-dot" id="popup-color-dot"></div>
<h4 id="popup-title"></h4> <h4 id="popup-title"></h4>
<<<<<<< HEAD
<div class="popup-toolbar"> <div class="popup-toolbar">
<button class="popup-icon-btn" id="popup-edit" title="Bearbeiten" aria-label="Bearbeiten"> <button class="popup-icon-btn" id="popup-edit" title="Bearbeiten" aria-label="Bearbeiten">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
@@ -389,6 +422,18 @@
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button> </button>
</div> </div>
=======
<button class="icon-btn popup-action" id="popup-edit" title="Bearbeiten">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
</button>
<button class="icon-btn popup-action" id="popup-copy" title="Kopieren nach…">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 1H4c-1.1 0-2 .9-2 2v24h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v24c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v24z"/></svg>
</button>
<button class="icon-btn popup-action" id="popup-delete" title="Löschen">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v22zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
</button>
<button class="icon-btn popup-close" id="popup-close">&times;</button>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
</div> </div>
<div class="popup-body"> <div class="popup-body">
<div class="popup-time" id="popup-time"></div> <div class="popup-time" id="popup-time"></div>
@@ -888,7 +933,11 @@
<a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p> <a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p>
</div> </div>
<div class="modal-footer" style="justify-content:space-between;align-items:center"> <div class="modal-footer" style="justify-content:space-between;align-items:center">
<<<<<<< HEAD
<span style="font-size:12px;color:var(--text-3)">Calendarr v17</span> <span style="font-size:12px;color:var(--text-3)">Calendarr v17</span>
=======
<span style="font-size:12px;color:var(--text-3)">Calendarr v11</span>
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
<button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button> <button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button>
</div> </div>
</div> </div>

View File

@@ -115,6 +115,7 @@ export async function initCalendar() {
bindProfileModal(); bindProfileModal();
bindSwipeNavigation(); bindSwipeNavigation();
handleHAOAuthReturn(); handleHAOAuthReturn();
<<<<<<< HEAD
// Browser-Back/Forward: URL-Hash → State synchronisieren // Browser-Back/Forward: URL-Hash → State synchronisieren
window.addEventListener('hashchange', () => { window.addEventListener('hashchange', () => {
@@ -131,6 +132,8 @@ export async function initCalendar() {
} }
if (changed) fetchAndRender(); if (changed) fetchAndRender();
}); });
=======
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
} }
function handleHAOAuthReturn() { function handleHAOAuthReturn() {
@@ -144,7 +147,11 @@ function handleHAOAuthReturn() {
}; };
if (params.has('ha_connected')) { if (params.has('ha_connected')) {
showToast('Home Assistant verbunden'); showToast('Home Assistant verbunden');
<<<<<<< HEAD
window.history.replaceState({}, '', window.location.pathname + window.location.hash); window.history.replaceState({}, '', window.location.pathname + window.location.hash);
=======
window.history.replaceState({}, '', window.location.pathname);
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
fetchAndRender(true); fetchAndRender(true);
api.get('/homeassistant/accounts').then(accs => { api.get('/homeassistant/accounts').then(accs => {
state.haAccounts = accs || []; state.haAccounts = accs || [];
@@ -154,7 +161,11 @@ function handleHAOAuthReturn() {
} else if (params.has('ha_error')) { } else if (params.has('ha_error')) {
const code = params.get('ha_error'); const code = params.get('ha_error');
showToast(errMap[code] || `HA-Anmeldung fehlgeschlagen: ${code}`, true); showToast(errMap[code] || `HA-Anmeldung fehlgeschlagen: ${code}`, true);
<<<<<<< HEAD
window.history.replaceState({}, '', window.location.pathname + window.location.hash); window.history.replaceState({}, '', window.location.pathname + window.location.hash);
=======
window.history.replaceState({}, '', window.location.pathname);
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
} }
} }
@@ -249,7 +260,10 @@ async function fetchAndRender(force = false, silent = false) {
renderView(); renderView();
updateTitle(); updateTitle();
renderMiniCal(); renderMiniCal();
<<<<<<< HEAD
writeUrlState(); writeUrlState();
=======
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
prefetchIfNeeded(start, end); // extend cache in background if approaching an edge prefetchIfNeeded(start, end); // extend cache in background if approaching an edge
return; return;
} }

View File

@@ -154,7 +154,11 @@ const translations = {
rec_every: 'Alle', rec_days: 'Tage', rec_weeks: 'Wochen', rec_months: 'Monate', rec_every: 'Alle', rec_days: 'Tage', rec_weeks: 'Wochen', rec_months: 'Monate',
rec_ends: 'Endet', rec_never: 'Nie', rec_after_count: 'Nach Anzahl', rec_ends: 'Endet', rec_never: 'Nie', rec_after_count: 'Nach Anzahl',
rec_on_date: 'Am Datum', rec_occurrences: 'Termine', rec_on_date: 'Am Datum', rec_occurrences: 'Termine',
<<<<<<< HEAD
copy_to_calendar: 'Kopieren nach…', event_copied: 'Termin kopiert', copy: 'Kopieren', copy_to_calendar: 'Kopieren nach…', event_copied: 'Termin kopiert', copy: 'Kopieren',
=======
copy_to_calendar: 'Kopieren nach…', event_copied: 'Termin kopiert',
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
edit_before_copy: 'Vor dem Kopieren bearbeiten', edit_before_copy: 'Vor dem Kopieren bearbeiten',
event_updated: 'Termin aktualisiert', event_created: 'Termin erstellt', event_updated: 'Termin aktualisiert', event_created: 'Termin erstellt',
confirm_delete_event: '"{title}" wirklich löschen?', confirm_delete_event: '"{title}" wirklich löschen?',
@@ -365,7 +369,11 @@ const translations = {
rec_every: 'Every', rec_days: 'days', rec_weeks: 'weeks', rec_months: 'months', rec_every: 'Every', rec_days: 'days', rec_weeks: 'weeks', rec_months: 'months',
rec_ends: 'Ends', rec_never: 'Never', rec_after_count: 'After count', rec_ends: 'Ends', rec_never: 'Never', rec_after_count: 'After count',
rec_on_date: 'On date', rec_occurrences: 'occurrences', rec_on_date: 'On date', rec_occurrences: 'occurrences',
<<<<<<< HEAD
copy_to_calendar: 'Copy to…', event_copied: 'Event copied', copy: 'Copy', copy_to_calendar: 'Copy to…', event_copied: 'Event copied', copy: 'Copy',
=======
copy_to_calendar: 'Copy to…', event_copied: 'Event copied',
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
edit_before_copy: 'Edit before copying', edit_before_copy: 'Edit before copying',
event_updated: 'Event updated', event_created: 'Event created', event_updated: 'Event updated', event_created: 'Event created',
confirm_delete_event: 'Really delete "{title}"?', confirm_delete_event: 'Really delete "{title}"?',

View File

@@ -1,2 +1,6 @@
// Increment APP_VERSION with every code change // Increment APP_VERSION with every code change
<<<<<<< HEAD
export const APP_VERSION = 'v17'; export const APP_VERSION = 'v17';
=======
export const APP_VERSION = 'v11';
>>>>>>> e744b1829e99db6b80922f75542ced329138e474

View File

@@ -63,6 +63,7 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
const color = ev.color || ev.calendarColor || '#4285f4'; const color = ev.color || ev.calendarColor || '#4285f4';
const pastCls = isPast(ev) ? 'past' : ''; const pastCls = isPast(ev) ? 'past' : '';
const multiCls = isMultiTimed ? 'multiday-timed' : ''; const multiCls = isMultiTimed ? 'multiday-timed' : '';
<<<<<<< HEAD
// continues-left/right: compute on date-only basis for all-day events // continues-left/right: compute on date-only basis for all-day events
let evStart = new Date(ev.start); let evStart = new Date(ev.start);
let evEnd = new Date(ev.end); let evEnd = new Date(ev.end);
@@ -76,6 +77,10 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
const lastDay = new Date(days[n-1]); lastDay.setHours(0, 0, 0, 0); const lastDay = new Date(days[n-1]); lastDay.setHours(0, 0, 0, 0);
const cL = evStart < firstDay ? 'continues-left' : ''; const cL = evStart < firstDay ? 'continues-left' : '';
const cR = (ev.allDay ? evEnd > lastDay : evEnd > lastDayMidnight) ? 'continues-right' : ''; const cR = (ev.allDay ? evEnd > lastDay : evEnd > lastDayMidnight) ? 'continues-right' : '';
=======
const cL = new Date(ev.start) < new Date(days[0]) ? 'continues-left' : '';
const cR = new Date(ev.end) > (() => { const d = new Date(days[n-1]); d.setHours(24,0,0,0); return d; })() ? 'continues-right' : '';
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
const label = isMultiTimed && isSameDay(new Date(ev.start), days[colStart]) const label = isMultiTimed && isSameDay(new Date(ev.start), days[colStart])
? `${fmtTime(new Date(ev.start))} ${ev.title}` ? `${fmtTime(new Date(ev.start))} ${ev.title}`
: ev.title; : ev.title;
@@ -247,6 +252,7 @@ function renderNowLine(container, days, hourH = 60) {
function layoutWeekAllDay(evs, days) { function layoutWeekAllDay(evs, days) {
const items = []; const items = [];
evs.forEach(ev => { evs.forEach(ev => {
<<<<<<< HEAD
// For all-day events, normalize to date-only with inclusive end-day // For all-day events, normalize to date-only with inclusive end-day
// (iCal stores exclusive end → subtract 1). For timed events, keep // (iCal stores exclusive end → subtract 1). For timed events, keep
// the original strict-overlap logic so events ending exactly at // the original strict-overlap logic so events ending exactly at
@@ -269,6 +275,13 @@ function layoutWeekAllDay(evs, days) {
matches = new Date(ev.start) < de && new Date(ev.end) > ds; matches = new Date(ev.start) < de && new Date(ev.end) > ds;
} }
if (matches) { if (matches) {
=======
let colStart = -1, colEnd = -1;
days.forEach((day, i) => {
const ds = new Date(day); ds.setHours(0, 0, 0, 0);
const de = new Date(day); de.setHours(24, 0, 0, 0);
if (new Date(ev.start) < de && new Date(ev.end) > ds) {
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
if (colStart === -1) colStart = i; if (colStart === -1) colStart = i;
colEnd = i; colEnd = i;
} }

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
// Calendarr Service Worker — minimal-cache strategy // Calendarr Service Worker — minimal-cache strategy
// //
// Strategy: network-first for everything. The cache is only used as a // Strategy: network-first for everything. The cache is only used as a
@@ -9,13 +10,50 @@
const CACHE_VERSION = 'calendarr-v17'; const CACHE_VERSION = 'calendarr-v17';
const OFFLINE_SHELL = ['/', '/index.html']; const OFFLINE_SHELL = ['/', '/index.html'];
=======
// Calendarr Service Worker
// Cache-first for static assets, network-first for /api/* (graceful offline)
const CACHE_VERSION = 'calendarr-v11';
const STATIC_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/static/css/app.css',
'/static/favicon.svg',
'/static/js/app.js',
'/static/js/api.js',
'/static/js/calendar.js',
'/static/js/color-picker.js',
'/static/js/date-picker.js',
'/static/js/i18n.js',
'/static/js/utils.js',
'/static/js/version.js',
'/static/js/views/agenda.js',
'/static/js/views/month.js',
'/static/js/views/quarter.js',
'/static/js/views/week.js',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/icons/icon.svg',
];
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
self.addEventListener('install', event => { self.addEventListener('install', event => {
event.waitUntil( event.waitUntil(
caches.open(CACHE_VERSION).then(cache => caches.open(CACHE_VERSION).then(cache =>
<<<<<<< HEAD
Promise.all(OFFLINE_SHELL.map(url => Promise.all(OFFLINE_SHELL.map(url =>
cache.add(url).catch(err => console.warn('[SW] skip', url, err)) cache.add(url).catch(err => console.warn('[SW] skip', url, err))
)) ))
=======
// Use addAll with a fallback so a single missing file doesn't abort install
Promise.all(
STATIC_ASSETS.map(url =>
cache.add(url).catch(err => console.warn('[SW] skip', url, err))
)
)
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
).then(() => self.skipWaiting()) ).then(() => self.skipWaiting())
); );
}); });
@@ -34,8 +72,12 @@ self.addEventListener('fetch', event => {
const url = new URL(req.url); const url = new URL(req.url);
<<<<<<< HEAD
// API routes: always go to the network, no offline fallback (we'd just // API routes: always go to the network, no offline fallback (we'd just
// be returning stale account/event data otherwise). // be returning stale account/event data otherwise).
=======
// Network-first for API routes — fail silently if offline
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
if (url.pathname.startsWith('/api/')) { if (url.pathname.startsWith('/api/')) {
event.respondWith( event.respondWith(
fetch(req).catch(() => fetch(req).catch(() =>
@@ -48,6 +90,7 @@ self.addEventListener('fetch', event => {
return; return;
} }
<<<<<<< HEAD
// Everything else: network-first. The browser's HTTP cache (driven by // Everything else: network-first. The browser's HTTP cache (driven by
// the server's Cache-Control headers) already throttles re-fetches — // the server's Cache-Control headers) already throttles re-fetches —
// the SW just makes sure offline still works for the entry HTML. // the SW just makes sure offline still works for the entry HTML.
@@ -71,6 +114,47 @@ self.addEventListener('fetch', event => {
return caches.match(req).then(c => c || caches.match('/index.html')); return caches.match(req).then(c => c || caches.match('/index.html'));
} }
return new Response('', { status: 503 }); return new Response('', { status: 503 });
=======
// Network-first for navigation (HTML) and the version-defining files —
// ensures users always get the freshest entry point so new releases
// take effect on the next reload without a manual SW unregister.
const isHtml = req.mode === 'navigate'
|| url.pathname === '/'
|| url.pathname === '/index.html';
const isVersionFile = url.pathname === '/static/js/version.js';
if (isHtml || isVersionFile) {
event.respondWith(
fetch(req).then(resp => {
if (resp && resp.status === 200) {
const clone = resp.clone();
caches.open(CACHE_VERSION).then(c => c.put(req, clone)).catch(() => {});
}
return resp;
}).catch(() =>
caches.match(req).then(c => c || caches.match('/index.html'))
)
);
return;
}
// Cache-first for everything else (static)
event.respondWith(
caches.match(req).then(cached => {
if (cached) return cached;
return fetch(req).then(resp => {
// Only cache successful, basic-origin responses
if (resp && resp.status === 200 && resp.type === 'basic') {
const clone = resp.clone();
caches.open(CACHE_VERSION).then(c => c.put(req, clone)).catch(() => {});
}
return resp;
}).catch(() => {
// Offline fallback for navigation requests
if (req.mode === 'navigate') return caches.match('/index.html');
return new Response('', { status: 503 });
});
>>>>>>> e744b1829e99db6b80922f75542ced329138e474
}) })
); );
}); });