From 8abeefcb5ace9efae958bac388cfe672abe1027d Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 31 May 2026 17:02:03 +0200 Subject: [PATCH] feat: Gruppen-Sichtbarkeit in Einstellungen + "Mit dir geteilt"-Sektion + Import-Fehler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Einstellungen: neuer "Kalender"-Abschnitt – Radio-Auswahl, welcher eigene Kalender fuer Gruppenmitglieder sichtbar ist (group_visible_calendar_id). - Sidebar: geteilte Kalender in eigener Sektion "Mit dir geteilt" (mit Besitzername) statt inline bei "Meine Kalender"; Gruppenkalender dort ausgeblendet (erscheinen unter Gruppen). - Lokale Checkbox tolerant: geteilte/Gruppen-Kalender werden client-seitig ein-/ausgeblendet (kein 404-PUT als Nicht-Besitzer). - Import-Fehler: zeigt HTTP-Status / "Datei zu gross" statt "unbekannter Fehler" (z.B. nginx 413). Version v26. Co-Authored-By: Claude Opus 4.8 --- frontend/css/app.css | 18 ++++++++++ frontend/index.html | 7 ++++ frontend/js/api.js | 9 +++-- frontend/js/calendar.js | 74 ++++++++++++++++++++++++++++++----------- frontend/js/i18n.js | 12 +++++++ frontend/js/version.js | 2 +- 6 files changed, 100 insertions(+), 22 deletions(-) diff --git a/frontend/css/app.css b/frontend/css/app.css index c0cbb0a..b1b3629 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -1841,3 +1841,21 @@ a { color: var(--primary); text-decoration: none; } .group-member-item:hover { background: var(--bg-surface); } .group-member-item input[type="checkbox"] { flex: 0 0 auto; margin: 0; } .group-member-name { flex: 1 1 auto; color: var(--text-1); } + +/* Calendar radio list (group-visible selection in settings). */ +.cal-radio-list { + border: 1px solid var(--border); + border-radius: 10px; + overflow: hidden; +} +.cal-radio-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + border-bottom: 1px solid var(--border); +} +.cal-radio-item:last-child { border-bottom: none; } +.cal-radio-item:hover { background: var(--bg-surface); } +.cal-radio-item .cal-item-dot { border-radius: 50%; flex: 0 0 auto; } diff --git a/frontend/index.html b/frontend/index.html index a1f3bf8..54a1068 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -757,6 +757,13 @@ +

Kalender

+

Wähle, welcher deiner Kalender für deine Gruppenmitglieder sichtbar ist

+
+ +
+
+

Stundenhöhe (Wochen- & Tagesansicht)

Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt

diff --git a/frontend/js/api.js b/frontend/js/api.js index cc12b4a..6c2c1b6 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -50,8 +50,13 @@ async function uploadRequest(path, formData) { return null; } if (!res.ok) { - const err = await res.json().catch(() => ({ detail: t('unknown_error') })); - throw new Error(err.detail || `HTTP ${res.status}`); + // Upload errors may be non-JSON (e.g. an nginx 413/502 HTML page); fall back + // to the HTTP status so the message is diagnostic, not "unknown error". + const err = await res.json().catch(() => null); + const detail = (err && err.detail) + ? err.detail + : (res.status === 413 ? t('upload_too_large') : `HTTP ${res.status} ${res.statusText || ''}`.trim()); + throw new Error(detail); } if (res.status === 204) return null; return res.json(); diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index fe7739a..1a9c608 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -576,28 +576,38 @@ function renderCalendarList() { }).join(''); } - // ── Local calendars (own + shared with me) ───────────── - if (state.localCalendars.length) { + // ── Local calendars: own ones, then a separate "shared with me" group ── + const ownLocal = state.localCalendars.filter(c => c.owned !== false); + const sharedLocal = state.localCalendars.filter(c => c.owned === false && !c.group); + + const renderLocalItem = (cal, withRemove) => { + const removeBtn = withRemove + ? `` + : ''; + return `
+ +
+ ${escHtml(cal.name)} + ${removeBtn} +
`; + }; + + if (ownLocal.length) { html += ``; - html += state.localCalendars.map(cal => { - const owned = cal.owned !== false; - // Shared calendars get an owner badge and no delete button (owner-only). - const sharedBadge = !owned - ? `${escHtml(cal.shared_by || '')}` - : ''; - const removeBtn = owned - ? `` - : ''; - return `
+ html += ownLocal.map(c => renderLocalItem(c, true)).join(''); + } + if (sharedLocal.length) { + html += ``; + html += sharedLocal.map(cal => + `
${escHtml(cal.name)} - ${sharedBadge} - ${removeBtn} -
`; - }).join(''); + ${escHtml(cal.shared_by || '')} +
` + ).join(''); } // ── iCal subscriptions ───────────────────────────────── @@ -677,8 +687,12 @@ function renderCalendarList() { cacheCalId = calId; // numeric integer in cached events } else if (source === 'local') { const calId = parseInt(cb.dataset.calId); - await api.put(`/local/calendars/${calId}`, { enabled: cb.checked }); const cal = state.localCalendars.find(c => c.id === calId); + // `enabled` is the owner's property — only the owner may PUT it. + // For shared/group calendars just toggle visibility client-side. + if (cal && cal.owned !== false) { + await api.put(`/local/calendars/${calId}`, { enabled: cb.checked }); + } if (cal) cal.enabled = cb.checked; cacheCalId = `local-${calId}`; } else if (source === 'ical') { @@ -2341,6 +2355,25 @@ function bindGroupUI() { }; } +// Radio list of the user's OWN local calendars to pick the one visible to +// group members (plus a "none" option). Selection is read on settings save. +function renderGroupVisibleList(selectedId) { + const el = document.getElementById('cfg-group-visible-list'); + if (!el) return; + const own = state.localCalendars.filter(c => c.owned !== false && !c.group); + const opt = (id, name, color) => { + const checked = (id === null && (selectedId == null)) || id === selectedId; + const dot = color ? `` : ''; + return ``; + }; + el.innerHTML = + opt(null, t('group_visible_none'), null) + + own.map(c => opt(c.id, c.name, c.color)).join(''); +} + // ── Settings Modal ──────────────────────────────────────── function openSettingsModal() { const s = state.settings; @@ -2374,6 +2407,7 @@ function openSettingsModal() { document.getElementById('cfg-dim-past').checked = !!s.dim_past_events; document.getElementById('cfg-language').value = getLang(); document.getElementById('cfg-private-visibility').value = s.private_event_visibility || 'busy'; + renderGroupVisibleList(s.group_visible_calendar_id); // Set active contrast/hour-height buttons [ @@ -2854,6 +2888,8 @@ function bindSettingsModal() { language: document.getElementById('cfg-language').value, private_event_visibility: document.getElementById('cfg-private-visibility').value, }; + const gvSel = document.querySelector('input[name="cfg-group-visible"]:checked'); + settings.group_visible_calendar_id = gvSel && gvSel.value ? parseInt(gvSel.value) : null; try { await api.put('/settings/', settings); state.settings = { ...state.settings, ...settings }; diff --git a/frontend/js/i18n.js b/frontend/js/i18n.js index 61a498d..5823dee 100644 --- a/frontend/js/i18n.js +++ b/frontend/js/i18n.js @@ -121,6 +121,12 @@ const translations = { group_created: 'Gruppe erstellt', group_view_label: 'Gruppenansicht: {name}', group_exit: 'Gruppenansicht verlassen', + upload_too_large: 'Datei zu groß (Server-Limit). Bitte Upload-Limit erhöhen.', + shared_with_me: 'Mit dir geteilt', + settings_calendars: 'Kalender', + settings_group_visible: 'Für Gruppen sichtbarer Kalender', + settings_group_visible_desc: 'Wähle, welcher deiner Kalender für deine Gruppenmitglieder sichtbar ist', + group_visible_none: 'Keiner', settings_hour_height: 'Stundenhöhe (Wochen- & Tagesansicht)', settings_hour_height_desc: 'Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt', hour_compact: 'Kompakt', hour_normal: 'Normal', @@ -373,6 +379,12 @@ const translations = { group_created: 'Group created', group_view_label: 'Group view: {name}', group_exit: 'Exit group view', + upload_too_large: 'File too large (server limit). Please raise the upload limit.', + shared_with_me: 'Shared with me', + settings_calendars: 'Calendars', + settings_group_visible: 'Calendar visible to groups', + settings_group_visible_desc: 'Choose which of your calendars your group members can see', + group_visible_none: 'None', settings_hour_height: 'Hour height (week & day view)', settings_hour_height_desc: 'How much space one hour takes in the time grid', hour_compact: 'Compact', hour_normal: 'Normal', diff --git a/frontend/js/version.js b/frontend/js/version.js index e6a7b0b..c0e691f 100644 --- a/frontend/js/version.js +++ b/frontend/js/version.js @@ -1,2 +1,2 @@ // Increment APP_VERSION with every code change -export const APP_VERSION = 'v25'; +export const APP_VERSION = 'v26';