diff --git a/frontend/css/app.css b/frontend/css/app.css
index d3a38f0..74654d8 100644
--- a/frontend/css/app.css
+++ b/frontend/css/app.css
@@ -975,11 +975,15 @@ a { color: var(--primary); text-decoration: none; }
}
.ctx-item:hover { background: var(--bg-hover); }
+<<<<<<< HEAD
/* ── Event Popup ──────────────────────────────────────────
Layout: Color-Dot + Title links, kleine Icon-Toolbar rechts oben.
Icons sind im Ruhezustand transparent (nur das SVG selbst sichtbar),
bekommen erst beim Hover einen runden farbigen Hintergrund. Wirkt
modern und lässt dem Titel die meiste Breite. */
+=======
+/* ── Event Popup ────────────────────────────────────────── */
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
.event-popup {
position: fixed; z-index: 600;
background: var(--bg-surface);
@@ -1697,12 +1701,24 @@ a { color: var(--primary); text-decoration: none; }
.topbar-left { gap: 0; }
.topbar-right { gap: 0; }
+<<<<<<< HEAD
/* Event-Popup auf Mobile: an Viewport-Breite anpassen */
.event-popup { width: min(94vw, 380px); max-width: 94vw; }
.popup-header { padding: 10px 8px 10px 14px; }
.popup-header h4 { font-size: 13.5px; }
.popup-icon-btn { width: 32px; height: 32px; }
.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 ──── */
.month-event-time { display: none; }
diff --git a/frontend/index.html b/frontend/index.html
index 5ee5a49..8526ce5 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,11 @@
+<<<<<<< HEAD
Calendarr v17
+=======
+ Calendarr v11
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -80,7 +84,11 @@
+<<<<<<< HEAD
+=======
+
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -185,7 +193,7 @@
Meine Kalender
@@ -199,7 +207,11 @@
+<<<<<<< HEAD
+=======
+
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -235,7 +247,11 @@
—
+<<<<<<< HEAD
+=======
+
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -253,7 +273,11 @@
—
+<<<<<<< HEAD
+=======
+
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -311,7 +339,11 @@
—
+<<<<<<< HEAD
+=======
+
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
@@ -375,6 +407,7 @@
diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js
index e5fac64..abe53c4 100644
--- a/frontend/js/calendar.js
+++ b/frontend/js/calendar.js
@@ -115,6 +115,7 @@ export async function initCalendar() {
bindProfileModal();
bindSwipeNavigation();
handleHAOAuthReturn();
+<<<<<<< HEAD
// Browser-Back/Forward: URL-Hash → State synchronisieren
window.addEventListener('hashchange', () => {
@@ -131,6 +132,8 @@ export async function initCalendar() {
}
if (changed) fetchAndRender();
});
+=======
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
}
function handleHAOAuthReturn() {
@@ -144,7 +147,11 @@ function handleHAOAuthReturn() {
};
if (params.has('ha_connected')) {
showToast('Home Assistant verbunden');
+<<<<<<< HEAD
window.history.replaceState({}, '', window.location.pathname + window.location.hash);
+=======
+ window.history.replaceState({}, '', window.location.pathname);
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
fetchAndRender(true);
api.get('/homeassistant/accounts').then(accs => {
state.haAccounts = accs || [];
@@ -154,7 +161,11 @@ function handleHAOAuthReturn() {
} else if (params.has('ha_error')) {
const code = params.get('ha_error');
showToast(errMap[code] || `HA-Anmeldung fehlgeschlagen: ${code}`, true);
+<<<<<<< HEAD
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();
updateTitle();
renderMiniCal();
+<<<<<<< HEAD
writeUrlState();
+=======
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
prefetchIfNeeded(start, end); // extend cache in background if approaching an edge
return;
}
diff --git a/frontend/js/i18n.js b/frontend/js/i18n.js
index 957b180..250b214 100644
--- a/frontend/js/i18n.js
+++ b/frontend/js/i18n.js
@@ -154,7 +154,11 @@ const translations = {
rec_every: 'Alle', rec_days: 'Tage', rec_weeks: 'Wochen', rec_months: 'Monate',
rec_ends: 'Endet', rec_never: 'Nie', rec_after_count: 'Nach Anzahl',
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',
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
edit_before_copy: 'Vor dem Kopieren bearbeiten',
event_updated: 'Termin aktualisiert', event_created: 'Termin erstellt',
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_ends: 'Ends', rec_never: 'Never', rec_after_count: 'After count',
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',
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
edit_before_copy: 'Edit before copying',
event_updated: 'Event updated', event_created: 'Event created',
confirm_delete_event: 'Really delete "{title}"?',
diff --git a/frontend/js/version.js b/frontend/js/version.js
index 9b7797e..9d0c41b 100644
--- a/frontend/js/version.js
+++ b/frontend/js/version.js
@@ -1,2 +1,6 @@
// Increment APP_VERSION with every code change
+<<<<<<< HEAD
export const APP_VERSION = 'v17';
+=======
+export const APP_VERSION = 'v11';
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
diff --git a/frontend/js/views/week.js b/frontend/js/views/week.js
index e84c0cb..4aa14d0 100644
--- a/frontend/js/views/week.js
+++ b/frontend/js/views/week.js
@@ -63,6 +63,7 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
const color = ev.color || ev.calendarColor || '#4285f4';
const pastCls = isPast(ev) ? 'past' : '';
const multiCls = isMultiTimed ? 'multiday-timed' : '';
+<<<<<<< HEAD
// continues-left/right: compute on date-only basis for all-day events
let evStart = new Date(ev.start);
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 cL = evStart < firstDay ? 'continues-left' : '';
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])
? `${fmtTime(new Date(ev.start))} ${ev.title}`
: ev.title;
@@ -247,6 +252,7 @@ function renderNowLine(container, days, hourH = 60) {
function layoutWeekAllDay(evs, days) {
const items = [];
evs.forEach(ev => {
+<<<<<<< HEAD
// For all-day events, normalize to date-only with inclusive end-day
// (iCal stores exclusive end → subtract 1). For timed events, keep
// 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;
}
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;
colEnd = i;
}
diff --git a/frontend/sw.js b/frontend/sw.js
index 7d26e5f..1d172c7 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
// Calendarr Service Worker — minimal-cache strategy
//
// Strategy: network-first for everything. The cache is only used as a
@@ -9,13 +10,50 @@
const CACHE_VERSION = 'calendarr-v17';
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 => {
event.waitUntil(
caches.open(CACHE_VERSION).then(cache =>
+<<<<<<< HEAD
Promise.all(OFFLINE_SHELL.map(url =>
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())
);
});
@@ -34,8 +72,12 @@ self.addEventListener('fetch', event => {
const url = new URL(req.url);
+<<<<<<< HEAD
// API routes: always go to the network, no offline fallback (we'd just
// be returning stale account/event data otherwise).
+=======
+ // Network-first for API routes — fail silently if offline
+>>>>>>> e744b1829e99db6b80922f75542ced329138e474
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(req).catch(() =>
@@ -48,6 +90,7 @@ self.addEventListener('fetch', event => {
return;
}
+<<<<<<< HEAD
// Everything else: network-first. The browser's HTTP cache (driven by
// the server's Cache-Control headers) already throttles re-fetches —
// 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 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
})
);
});