Einige kleine verbesserungen #1
@@ -1841,3 +1841,21 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
.group-member-item:hover { background: var(--bg-surface); }
|
.group-member-item:hover { background: var(--bg-surface); }
|
||||||
.group-member-item input[type="checkbox"] { flex: 0 0 auto; margin: 0; }
|
.group-member-item input[type="checkbox"] { flex: 0 0 auto; margin: 0; }
|
||||||
.group-member-name { flex: 1 1 auto; color: var(--text-1); }
|
.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; }
|
||||||
|
|||||||
@@ -757,6 +757,13 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4 class="panel-title" style="margin-top:24px" data-i18n="settings_calendars">Kalender</h4>
|
||||||
|
<p class="panel-desc" data-i18n="settings_group_visible_desc">Wähle, welcher deiner Kalender für deine Gruppenmitglieder sichtbar ist</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label data-i18n="settings_group_visible">Für Gruppen sichtbarer Kalender</label>
|
||||||
|
<div id="cfg-group-visible-list" class="cal-radio-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="panel-title" style="margin-top:24px" data-i18n="settings_hour_height">Stundenhöhe (Wochen- & Tagesansicht)</h4>
|
<h4 class="panel-title" style="margin-top:24px" data-i18n="settings_hour_height">Stundenhöhe (Wochen- & Tagesansicht)</h4>
|
||||||
<p class="panel-desc" data-i18n="settings_hour_height_desc">Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt</p>
|
<p class="panel-desc" data-i18n="settings_hour_height_desc">Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt</p>
|
||||||
<div class="contrast-selector" id="cfg-hour-height" data-setting="hour_height">
|
<div class="contrast-selector" id="cfg-hour-height" data-setting="hour_height">
|
||||||
|
|||||||
@@ -50,8 +50,13 @@ async function uploadRequest(path, formData) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json().catch(() => ({ detail: t('unknown_error') }));
|
// Upload errors may be non-JSON (e.g. an nginx 413/502 HTML page); fall back
|
||||||
throw new Error(err.detail || `HTTP ${res.status}`);
|
// 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;
|
if (res.status === 204) return null;
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
@@ -576,16 +576,12 @@ function renderCalendarList() {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Local calendars (own + shared with me) ─────────────
|
// ── Local calendars: own ones, then a separate "shared with me" group ──
|
||||||
if (state.localCalendars.length) {
|
const ownLocal = state.localCalendars.filter(c => c.owned !== false);
|
||||||
html += `<div class="cal-account-name">${t('cal_local')}</div>`;
|
const sharedLocal = state.localCalendars.filter(c => c.owned === false && !c.group);
|
||||||
html += state.localCalendars.map(cal => {
|
|
||||||
const owned = cal.owned !== false;
|
const renderLocalItem = (cal, withRemove) => {
|
||||||
// Shared calendars get an owner badge and no delete button (owner-only).
|
const removeBtn = withRemove
|
||||||
const sharedBadge = !owned
|
|
||||||
? `<span class="cal-badge cal-badge-shared" title="${t('shared_by', { name: cal.shared_by || '' })}">${escHtml(cal.shared_by || '')}</span>`
|
|
||||||
: '';
|
|
||||||
const removeBtn = owned
|
|
||||||
? `<button class="icon-btn mini-btn cal-item-remove" data-cal-id="${cal.id}" data-source="local" title="${t('remove_cal')}">
|
? `<button class="icon-btn mini-btn cal-item-remove" data-cal-id="${cal.id}" data-source="local" title="${t('remove_cal')}">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
||||||
</button>`
|
</button>`
|
||||||
@@ -594,10 +590,24 @@ function renderCalendarList() {
|
|||||||
<input type="checkbox" ${cal.enabled ? 'checked' : ''} data-cal-id="${cal.id}" data-source="local" />
|
<input type="checkbox" ${cal.enabled ? 'checked' : ''} data-cal-id="${cal.id}" data-source="local" />
|
||||||
<div class="cal-item-dot" style="background:${cal.color}" data-cal-id="${cal.id}" data-source="local" title="${t('change_color')}"></div>
|
<div class="cal-item-dot" style="background:${cal.color}" data-cal-id="${cal.id}" data-source="local" title="${t('change_color')}"></div>
|
||||||
<span class="cal-item-name" data-source="local">${escHtml(cal.name)}</span>
|
<span class="cal-item-name" data-source="local">${escHtml(cal.name)}</span>
|
||||||
${sharedBadge}
|
|
||||||
${removeBtn}
|
${removeBtn}
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
};
|
||||||
|
|
||||||
|
if (ownLocal.length) {
|
||||||
|
html += `<div class="cal-account-name">${t('cal_local')}</div>`;
|
||||||
|
html += ownLocal.map(c => renderLocalItem(c, true)).join('');
|
||||||
|
}
|
||||||
|
if (sharedLocal.length) {
|
||||||
|
html += `<div class="cal-account-name">${t('shared_with_me')}</div>`;
|
||||||
|
html += sharedLocal.map(cal =>
|
||||||
|
`<div class="cal-item" data-cal-id="${cal.id}" data-source="local">
|
||||||
|
<input type="checkbox" ${cal.enabled ? 'checked' : ''} data-cal-id="${cal.id}" data-source="local" />
|
||||||
|
<div class="cal-item-dot" style="background:${cal.color}" data-cal-id="${cal.id}" data-source="local" title="${t('change_color')}"></div>
|
||||||
|
<span class="cal-item-name" data-source="local">${escHtml(cal.name)}</span>
|
||||||
|
<span class="cal-badge cal-badge-shared" title="${t('shared_by', { name: cal.shared_by || '' })}">${escHtml(cal.shared_by || '')}</span>
|
||||||
|
</div>`
|
||||||
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── iCal subscriptions ─────────────────────────────────
|
// ── iCal subscriptions ─────────────────────────────────
|
||||||
@@ -677,8 +687,12 @@ function renderCalendarList() {
|
|||||||
cacheCalId = calId; // numeric integer in cached events
|
cacheCalId = calId; // numeric integer in cached events
|
||||||
} else if (source === 'local') {
|
} else if (source === 'local') {
|
||||||
const calId = parseInt(cb.dataset.calId);
|
const calId = parseInt(cb.dataset.calId);
|
||||||
await api.put(`/local/calendars/${calId}`, { enabled: cb.checked });
|
|
||||||
const cal = state.localCalendars.find(c => c.id === calId);
|
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;
|
if (cal) cal.enabled = cb.checked;
|
||||||
cacheCalId = `local-${calId}`;
|
cacheCalId = `local-${calId}`;
|
||||||
} else if (source === 'ical') {
|
} 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 ? `<span class="cal-item-dot" style="background:${color};width:12px;height:12px"></span>` : '';
|
||||||
|
return `<label class="cal-radio-item">
|
||||||
|
<input type="radio" name="cfg-group-visible" value="${id == null ? '' : id}" ${checked ? 'checked' : ''} />
|
||||||
|
${dot}<span>${escHtml(name)}</span>
|
||||||
|
</label>`;
|
||||||
|
};
|
||||||
|
el.innerHTML =
|
||||||
|
opt(null, t('group_visible_none'), null) +
|
||||||
|
own.map(c => opt(c.id, c.name, c.color)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Settings Modal ────────────────────────────────────────
|
// ── Settings Modal ────────────────────────────────────────
|
||||||
function openSettingsModal() {
|
function openSettingsModal() {
|
||||||
const s = state.settings;
|
const s = state.settings;
|
||||||
@@ -2374,6 +2407,7 @@ function openSettingsModal() {
|
|||||||
document.getElementById('cfg-dim-past').checked = !!s.dim_past_events;
|
document.getElementById('cfg-dim-past').checked = !!s.dim_past_events;
|
||||||
document.getElementById('cfg-language').value = getLang();
|
document.getElementById('cfg-language').value = getLang();
|
||||||
document.getElementById('cfg-private-visibility').value = s.private_event_visibility || 'busy';
|
document.getElementById('cfg-private-visibility').value = s.private_event_visibility || 'busy';
|
||||||
|
renderGroupVisibleList(s.group_visible_calendar_id);
|
||||||
|
|
||||||
// Set active contrast/hour-height buttons
|
// Set active contrast/hour-height buttons
|
||||||
[
|
[
|
||||||
@@ -2854,6 +2888,8 @@ function bindSettingsModal() {
|
|||||||
language: document.getElementById('cfg-language').value,
|
language: document.getElementById('cfg-language').value,
|
||||||
private_event_visibility: document.getElementById('cfg-private-visibility').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 {
|
try {
|
||||||
await api.put('/settings/', settings);
|
await api.put('/settings/', settings);
|
||||||
state.settings = { ...state.settings, ...settings };
|
state.settings = { ...state.settings, ...settings };
|
||||||
|
|||||||
@@ -121,6 +121,12 @@ const translations = {
|
|||||||
group_created: 'Gruppe erstellt',
|
group_created: 'Gruppe erstellt',
|
||||||
group_view_label: 'Gruppenansicht: {name}',
|
group_view_label: 'Gruppenansicht: {name}',
|
||||||
group_exit: 'Gruppenansicht verlassen',
|
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: 'Stundenhöhe (Wochen- & Tagesansicht)',
|
||||||
settings_hour_height_desc: 'Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt',
|
settings_hour_height_desc: 'Wie viel Platz eine Stunde in der Zeitrasteransicht einnimmt',
|
||||||
hour_compact: 'Kompakt', hour_normal: 'Normal',
|
hour_compact: 'Kompakt', hour_normal: 'Normal',
|
||||||
@@ -373,6 +379,12 @@ const translations = {
|
|||||||
group_created: 'Group created',
|
group_created: 'Group created',
|
||||||
group_view_label: 'Group view: {name}',
|
group_view_label: 'Group view: {name}',
|
||||||
group_exit: 'Exit group view',
|
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: 'Hour height (week & day view)',
|
||||||
settings_hour_height_desc: 'How much space one hour takes in the time grid',
|
settings_hour_height_desc: 'How much space one hour takes in the time grid',
|
||||||
hour_compact: 'Compact', hour_normal: 'Normal',
|
hour_compact: 'Compact', hour_normal: 'Normal',
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Increment APP_VERSION with every code change
|
// Increment APP_VERSION with every code change
|
||||||
export const APP_VERSION = 'v25';
|
export const APP_VERSION = 'v26';
|
||||||
|
|||||||
Reference in New Issue
Block a user