diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index 34bac93..960210e 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -533,14 +533,16 @@ function filterEvents(events) { return oid == null || !hidden.has(oid); }); } - // Normal view: honour per-device hide for calendars shared with me (their - // events keep coming from the server since `enabled` is the owner's flag). + // Normal view: honour per-device hides for calendars shared with me (their + // events keep coming from the server since `enabled` is the owner's flag) — + // both the checkbox hide and the sidebar hide remove their events. const hiddenLocal = state.activeGroupId ? null : loadHiddenLocalCals(); - if (hiddenLocal && hiddenLocal.size) { + const sharedHidden = state.activeGroupId ? null : loadSidebarHiddenShared(); + if ((hiddenLocal && hiddenLocal.size) || (sharedHidden && sharedHidden.size)) { events = events.filter(ev => { if (ev.source !== 'local') return true; const id = parseInt(String(ev.calendar_id).replace('local-', '')); - return !hiddenLocal.has(id); + return !(hiddenLocal && hiddenLocal.has(id)) && !(sharedHidden && sharedHidden.has(id)); }); } // If dimPast is enabled, events are still shown but CSS handles opacity via .past class @@ -710,6 +712,20 @@ function setHiddenLocalCal(id, hidden) { try { localStorage.setItem(HIDDEN_LOCAL_KEY, JSON.stringify([...s])); } catch (e) { /* ignore */ } } +// Calendars shared with me can't carry a server `sidebar_hidden` flag (I don't +// own them), so "hide from sidebar" for those is a per-device set — the same +// idea as caldav/google sidebar_hidden, just kept client-side. +const SIDEBAR_HIDDEN_SHARED_KEY = 'sidebarHiddenShared'; +function loadSidebarHiddenShared() { + try { return new Set(JSON.parse(localStorage.getItem(SIDEBAR_HIDDEN_SHARED_KEY) || '[]')); } + catch (e) { return new Set(); } +} +function setSidebarHiddenShared(id, hidden) { + const s = loadSidebarHiddenShared(); + if (hidden) s.add(id); else s.delete(id); + try { localStorage.setItem(SIDEBAR_HIDDEN_SHARED_KEY, JSON.stringify([...s])); } catch (e) { /* ignore */ } +} + // Drag & drop reordering of the flat calendar list (persisted per device). // The dragged row is moved live among its siblings during dragover, so the // list visibly "makes space" and you can see where it will land. @@ -759,9 +775,8 @@ function bindCalDragReorder(container) { function renderCalendarList() { const container = document.getElementById('cal-list-items'); - // Eye-off (hide external calendar) and trash (delete local/ical) icons. + // Eye-off icon — every sidebar row can now be hidden (delete lives in Settings). const EYE_OFF = ``; - const TRASH = ``; const BELL = ``; const BELL_OFF = ``; const SHARE_ICON = ``; @@ -771,6 +786,7 @@ function renderCalendarList() { // whole list can be freely reordered via drag & drop. const entries = []; const hiddenLocal = loadHiddenLocalCals(); // per-device hide for shared-with-me calendars + const sharedHidden = loadSidebarHiddenShared(); // per-device sidebar hide (shared cals) state.accounts.forEach(acc => { (acc.calendars || []).filter(c => !c.sidebar_hidden).forEach(cal => { entries.push({ key: `caldav:${cal.id}`, source: 'caldav', dataId: `data-cal-id="${cal.id}"`, @@ -785,16 +801,17 @@ function renderCalendarList() { name: cal.name, color: cal.color, enabled: cal.enabled, reminders: true, remindersEnabled: cal.reminders_enabled !== false, sourceLabel: t('cal_local'), groupVisible: cal.id === groupVisibleId, - remove: { icon: TRASH, title: t('remove_cal') } }); + remove: { icon: EYE_OFF, title: t('hide_cal') } }); }); - state.localCalendars.filter(c => c.owned === false && !c.group).forEach(cal => { + state.localCalendars.filter(c => c.owned === false && !c.group && !sharedHidden.has(c.id)).forEach(cal => { const readOnly = cal.permission !== 'read_write'; // A shared calendar is shown to the recipient under the OWNER's name // (e.g. Guido's "Persönlich" appears as "Guido"); the original calendar // name stays in the sub-label so it's still identifiable. entries.push({ key: `local:${cal.id}`, source: 'local', dataId: `data-cal-id="${cal.id}"`, name: cal.shared_by || cal.name, color: cal.color, enabled: !hiddenLocal.has(cal.id), readOnly, - sourceLabel: `${t('shared_with_me')} · ${cal.name}${readOnly ? ' · ' + t('perm_read') : ''}`, remove: null }); + sourceLabel: `${t('shared_with_me')} · ${cal.name}${readOnly ? ' · ' + t('perm_read') : ''}`, + remove: { icon: EYE_OFF, title: t('hide_cal') } }); }); // Group calendars (owned by the creator or reached via membership) — shown so // they can be toggled/recoloured. Owned group calendars can also be muted @@ -811,7 +828,7 @@ function renderCalendarList() { entries.push({ key: `ical:${sub.id}`, source: 'ical', dataId: `data-sub-id="${sub.id}"`, name: sub.name, color: sub.color, enabled: sub.enabled, reminders: true, remindersEnabled: sub.reminders_enabled !== false, - sourceLabel: t('cal_ical'), remove: { icon: TRASH, title: t('remove_ical_sub') } }); + sourceLabel: t('cal_ical'), remove: { icon: EYE_OFF, title: t('hide_cal') } }); }); state.googleAccounts.forEach(acc => { (acc.calendars || []).filter(c => !c.sidebar_hidden).forEach(cal => { @@ -1100,16 +1117,22 @@ function renderCalendarList() { } cacheCalId = calId; } else if (source === 'local') { - if (!await confirmModal(t('confirm_delete_local_cal'), { title: t('confirm_delete_cal_title'), okLabel: t('delete') })) return; + // Hide from the sidebar (non-destructive; delete lives in Settings). + // Owned calendars use the server flag; shared ones a per-device hide. const calId = parseInt(btn.dataset.calId); - await api.delete(`/local/calendars/${calId}`); - state.localCalendars = state.localCalendars.filter(c => c.id !== calId); + const cal = state.localCalendars.find(c => c.id === calId); + if (cal && cal.owned === false) { + setSidebarHiddenShared(calId, true); + } else { + await api.put(`/local/calendars/${calId}`, { enabled: false, sidebar_hidden: true }); + if (cal) { cal.enabled = false; cal.sidebar_hidden = true; } + } cacheCalId = `local-${calId}`; } else if (source === 'ical') { - if (!await confirmModal(t('confirm_remove_ical'), { title: t('confirm_remove_ical_title'), okLabel: t('delete') })) return; const subId = parseInt(btn.dataset.subId); - await api.delete(`/ical/subscriptions/${subId}`); - state.icalSubscriptions = state.icalSubscriptions.filter(s => s.id !== subId); + await api.put(`/ical/subscriptions/${subId}`, { enabled: false, sidebar_hidden: true }); + const sub = state.icalSubscriptions.find(s => s.id === subId); + if (sub) { sub.enabled = false; sub.sidebar_hidden = true; } cacheCalId = `ical-${subId}`; } else if (source === 'google') { const calId = parseInt(btn.dataset.calId); @@ -4200,13 +4223,14 @@ function renderCalendarTable() { // management actions since I don't own them. Shown under the owner's name. const sharedWithMe = state.localCalendars.filter(c => c.owned === false && !c.group); if (sharedWithMe.length) { + const sharedHidden = loadSidebarHiddenShared(); rows += `