Rework birthdays to a single calendar + UI fixes (web)
- one dedicated birthday calendar per user; enable it in Settings > Calendars (or from the New-birthday dialog) — it then shows in the sidebar like any calendar (colour/visibility) - New-birthday dialog: no target picker; day/month selects + a year field that hides when "year unknown"; fixed the year-unknown checkbox layout - create split-button is now one seamless pill; the caret opens the menu - Settings > Calendars gains a Birthdays section (enable + notify-days-before) - removed the birthday toggle from the generic new-calendar modal - profile settings: right-align the section save button for consistency - bump service-worker cache to v25 so assets refresh Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -165,15 +165,28 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
}
|
}
|
||||||
.btn-fab:active { transform: translateY(0) scale(.985); }
|
.btn-fab:active { transform: translateY(0) scale(.985); }
|
||||||
|
|
||||||
/* Split create button: main "Erstellen" action + a caret that opens a small
|
/* Split create button: one seamless pill — main "Erstellen" action on the left,
|
||||||
menu (new event / new birthday). */
|
a caret section on the right that opens a small menu (new event / new birthday). */
|
||||||
.create-split { display: flex; align-items: stretch; gap: 4px; margin: 16px 12px 8px; }
|
.create-split {
|
||||||
.create-split .btn-fab { margin: 0; }
|
display: flex; align-items: stretch; margin: 16px 12px 8px;
|
||||||
.create-split .create-main { flex: 1; justify-content: center; }
|
border-radius: 999px;
|
||||||
.create-split .create-caret { flex: 0 0 auto; padding: 12px 12px; }
|
box-shadow: 0 4px 14px color-mix(in srgb, var(--primary) 35%, transparent);
|
||||||
|
}
|
||||||
|
.create-split .btn-fab { margin: 0; box-shadow: none; border-radius: 0; }
|
||||||
|
.create-split .btn-fab:hover { transform: none; box-shadow: none; filter: brightness(1.08); }
|
||||||
|
.create-split .create-main { flex: 1; justify-content: center; border-radius: 999px 0 0 999px; }
|
||||||
|
.create-split .create-caret {
|
||||||
|
flex: 0 0 auto; padding: 12px 14px; border-radius: 0 999px 999px 0;
|
||||||
|
border-left: 1px solid color-mix(in srgb, #fff 25%, transparent);
|
||||||
|
}
|
||||||
.create-split .create-caret svg { width: 18px; height: 18px; }
|
.create-split .create-caret svg { width: 18px; height: 18px; }
|
||||||
.create-menu { left: 0; right: auto; min-width: 200px; }
|
.create-menu { left: 0; right: auto; min-width: 200px; }
|
||||||
|
|
||||||
|
/* Birthday modal: day / month / year row */
|
||||||
|
.birthday-date-row { display: flex; gap: 8px; }
|
||||||
|
.birthday-date-row select { flex: 1; }
|
||||||
|
.birthday-date-row #birthday-year { width: 90px; flex: 0 0 auto; }
|
||||||
|
|
||||||
/* Circular icon buttons (topbar nav, modal close, etc.) */
|
/* Circular icon buttons (topbar nav, modal close, etc.) */
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
display: inline-flex; align-items: center; justify-content: center;
|
display: inline-flex; align-items: center; justify-content: center;
|
||||||
@@ -1487,6 +1500,10 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
.ct-dav-hint { font-size: 11px; color: var(--text-3); margin-top: 6px; max-width: 640px; }
|
.ct-dav-hint { font-size: 11px; color: var(--text-3); margin-top: 6px; max-width: 640px; }
|
||||||
.app-pw-create { display: flex; gap: 8px; align-items: center; }
|
.app-pw-create { display: flex; gap: 8px; align-items: center; }
|
||||||
.app-pw-create input { flex: 1; }
|
.app-pw-create input { flex: 1; }
|
||||||
|
|
||||||
|
/* Consistent placement for a section's action button: right-aligned, matching
|
||||||
|
the sticky header save and the app-password create row. */
|
||||||
|
.settings-actions { display: flex; justify-content: flex-end; margin-top: 10px; }
|
||||||
.app-pw-new { margin: 8px 0; }
|
.app-pw-new { margin: 8px 0; }
|
||||||
.app-pw-item { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-top: 1px solid var(--border); }
|
.app-pw-item { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-top: 1px solid var(--border); }
|
||||||
.app-pw-name { font-weight: 500; }
|
.app-pw-name { font-weight: 500; }
|
||||||
|
|||||||
@@ -590,17 +590,6 @@
|
|||||||
<div class="ev-color-preview" id="local-cal-color-preview" style="background:#34a853" title="Farbe wählen"></div>
|
<div class="ev-color-preview" id="local-cal-color-preview" style="background:#34a853" title="Farbe wählen"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label class="checkbox-row">
|
|
||||||
<input type="checkbox" id="local-cal-birthday" />
|
|
||||||
<span data-i18n="birthday_calendar">Geburtstagskalender</span>
|
|
||||||
</label>
|
|
||||||
<div class="form-hint" id="local-cal-birthday-hint" data-i18n="birthday_calendar_hint">Ganztägige, jährliche Termine mit Alter und Geburtstags-Icon.</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group hidden" id="local-cal-notify-group">
|
|
||||||
<label data-i18n="birthday_notify">Erinnerung</label>
|
|
||||||
<select id="local-cal-notify"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-ghost" data-modal="modal-local-cal">Abbrechen</button>
|
<button class="btn btn-ghost" data-modal="modal-local-cal">Abbrechen</button>
|
||||||
@@ -617,22 +606,27 @@
|
|||||||
<button class="icon-btn modal-close" data-modal="modal-birthday">×</button>
|
<button class="icon-btn modal-close" data-modal="modal-birthday">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="birthday-no-cals" class="form-hint hidden" data-i18n="birthday_no_calendars"></div>
|
<div id="birthday-no-cal" class="hidden">
|
||||||
|
<div class="form-hint" data-i18n="birthday_activate_hint">Aktiviere den Geburtstagskalender, um Geburtstage anzulegen. Er erscheint als eigener Kalender in der Seitenleiste.</div>
|
||||||
|
<button class="btn btn-primary btn-sm" id="birthday-activate" data-i18n="birthday_activate">Geburtstagskalender aktivieren</button>
|
||||||
|
</div>
|
||||||
|
<div id="birthday-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label data-i18n="birthday_person">Name</label>
|
<label data-i18n="birthday_person">Name</label>
|
||||||
<input type="text" id="birthday-name" data-i18n-ph="birthday_person_ph" placeholder="Name der Person" />
|
<input type="text" id="birthday-name" data-i18n-ph="birthday_person_ph" placeholder="Name der Person" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label data-i18n="birthday_date">Geburtstag</label>
|
<label data-i18n="birthday_date">Geburtstag</label>
|
||||||
<input type="date" id="birthday-date" />
|
<div class="birthday-date-row">
|
||||||
<label class="checkbox-inline" style="margin-top:8px">
|
<select id="birthday-day"></select>
|
||||||
<input type="checkbox" id="birthday-year-unknown" />
|
<select id="birthday-month"></select>
|
||||||
|
<input type="number" id="birthday-year" min="1900" max="2100" data-i18n-ph="birthday_year_ph" placeholder="Jahr" />
|
||||||
|
</div>
|
||||||
|
<label class="checkbox-row" style="margin-top:10px;display:flex;align-items:center;gap:8px;text-transform:none;letter-spacing:normal;font-weight:400;cursor:pointer">
|
||||||
|
<input type="checkbox" id="birthday-year-unknown" style="width:16px;height:16px;flex:none;margin:0" />
|
||||||
<span data-i18n="birthday_year_unknown">Jahr unbekannt</span>
|
<span data-i18n="birthday_year_unknown">Jahr unbekannt</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="birthday-cal-group">
|
|
||||||
<label data-i18n="birthday_target">Geburtstagskalender</label>
|
|
||||||
<select id="birthday-cal"></select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -767,7 +761,9 @@
|
|||||||
<label>E-Mail</label>
|
<label>E-Mail</label>
|
||||||
<input type="email" id="cfg-email" placeholder="Keine E-Mail hinterlegt" />
|
<input type="email" id="cfg-email" placeholder="Keine E-Mail hinterlegt" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-actions">
|
||||||
<button class="btn btn-primary btn-sm" id="cfg-profile-save" data-i18n="save">Speichern</button>
|
<button class="btn btn-primary btn-sm" id="cfg-profile-save" data-i18n="save">Speichern</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="panel-title" style="margin-top:24px" data-i18n="app_pw_title">App-Passwörter (CalDAV)</h4>
|
<h4 class="panel-title" style="margin-top:24px" data-i18n="app_pw_title">App-Passwörter (CalDAV)</h4>
|
||||||
<p class="panel-desc" data-i18n="app_pw_desc">Eigene Passwörter für CalDAV-Clients. Bei aktivem 2FA nötig, da Apps keinen 2FA-Code eingeben können. Jederzeit widerrufbar.</p>
|
<p class="panel-desc" data-i18n="app_pw_desc">Eigene Passwörter für CalDAV-Clients. Bei aktivem 2FA nötig, da Apps keinen 2FA-Code eingeben können. Jederzeit widerrufbar.</p>
|
||||||
@@ -944,6 +940,9 @@
|
|||||||
<button class="btn btn-secondary btn-sm" id="settings-btn-add-google">+ Google</button>
|
<button class="btn btn-secondary btn-sm" id="settings-btn-add-google">+ Google</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="cal-settings-table" style="margin-top:16px;overflow-x:auto"></div>
|
<div id="cal-settings-table" style="margin-top:16px;overflow-x:auto"></div>
|
||||||
|
|
||||||
|
<h4 class="panel-title" style="margin-top:24px" data-i18n="birthday_settings_title">Geburtstage</h4>
|
||||||
|
<div id="birthday-settings"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Benutzerverwaltung -->
|
<!-- Benutzerverwaltung -->
|
||||||
|
|||||||
@@ -2538,22 +2538,12 @@ function openLocalCalModal() {
|
|||||||
document.getElementById('local-cal-name').value = '';
|
document.getElementById('local-cal-name').value = '';
|
||||||
document.getElementById('local-cal-color-hex').value = '#34a853';
|
document.getElementById('local-cal-color-hex').value = '#34a853';
|
||||||
document.getElementById('local-cal-color-preview').style.background = '#34a853';
|
document.getElementById('local-cal-color-preview').style.background = '#34a853';
|
||||||
const bday = document.getElementById('local-cal-birthday');
|
|
||||||
bday.checked = false;
|
|
||||||
document.getElementById('local-cal-notify').value = '-1';
|
|
||||||
document.getElementById('local-cal-notify-group').classList.add('hidden');
|
|
||||||
openModal('modal-local-cal');
|
openModal('modal-local-cal');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindLocalCalModal() {
|
function bindLocalCalModal() {
|
||||||
const preview = document.getElementById('local-cal-color-preview');
|
const preview = document.getElementById('local-cal-color-preview');
|
||||||
const hex = document.getElementById('local-cal-color-hex');
|
const hex = document.getElementById('local-cal-color-hex');
|
||||||
const bday = document.getElementById('local-cal-birthday');
|
|
||||||
const notifyGroup = document.getElementById('local-cal-notify-group');
|
|
||||||
fillBirthdayNotifySelect(document.getElementById('local-cal-notify'));
|
|
||||||
bday.addEventListener('change', () => {
|
|
||||||
notifyGroup.classList.toggle('hidden', !bday.checked);
|
|
||||||
});
|
|
||||||
preview.addEventListener('click', async () => {
|
preview.addEventListener('click', async () => {
|
||||||
const picked = await openColorPicker(preview, hex.value || '#34a853');
|
const picked = await openColorPicker(preview, hex.value || '#34a853');
|
||||||
if (picked) { hex.value = picked.toUpperCase(); preview.style.background = picked; }
|
if (picked) { hex.value = picked.toUpperCase(); preview.style.background = picked; }
|
||||||
@@ -2568,15 +2558,8 @@ function bindLocalCalModal() {
|
|||||||
const name = document.getElementById('local-cal-name').value.trim();
|
const name = document.getElementById('local-cal-name').value.trim();
|
||||||
if (!name) { showToast(t('error_enter_name'), true); return; }
|
if (!name) { showToast(t('error_enter_name'), true); return; }
|
||||||
const color = hex.value;
|
const color = hex.value;
|
||||||
const isBirthday = bday.checked;
|
|
||||||
const notify = parseInt(document.getElementById('local-cal-notify').value, 10);
|
|
||||||
const body = { name, color };
|
|
||||||
if (isBirthday) {
|
|
||||||
body.is_birthday = true;
|
|
||||||
if (notify >= 0) body.birthday_notify_days_before = notify;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const cal = await api.post('/local/calendars', body);
|
const cal = await api.post('/local/calendars', { name, color });
|
||||||
state.localCalendars.push(cal);
|
state.localCalendars.push(cal);
|
||||||
renderCalendarList();
|
renderCalendarList();
|
||||||
closeModal('modal-local-cal');
|
closeModal('modal-local-cal');
|
||||||
@@ -2585,53 +2568,90 @@ function bindLocalCalModal() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Birthdays (single dedicated calendar per user) ─────────
|
||||||
|
const BIRTHDAY_DEFAULT_COLOR = '#e0407f';
|
||||||
|
|
||||||
|
/** The user's single birthday calendar (owned, is_birthday), or null. */
|
||||||
|
function birthdayCalendar() {
|
||||||
|
return (state.localCalendars || []).find(c => c.is_birthday && c.owned !== false) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create the single "Geburtstage" birthday calendar if none exists; returns it. */
|
||||||
|
async function ensureBirthdayCalendar() {
|
||||||
|
const existing = birthdayCalendar();
|
||||||
|
if (existing) return existing;
|
||||||
|
const cal = await api.post('/local/calendars', {
|
||||||
|
name: t('birthday_calendar_name'), color: BIRTHDAY_DEFAULT_COLOR, is_birthday: true,
|
||||||
|
});
|
||||||
|
state.localCalendars.push(cal);
|
||||||
|
renderCalendarList();
|
||||||
|
return cal;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Birthday Modal (manual entry) ─────────────────────────
|
// ── Birthday Modal (manual entry) ─────────────────────────
|
||||||
function birthdayCalendars() {
|
function fillBirthdayDateSelects() {
|
||||||
return (state.localCalendars || []).filter(
|
const daySel = document.getElementById('birthday-day');
|
||||||
c => c.is_birthday && (c.owned !== false || c.permission === 'read_write')
|
const monthSel = document.getElementById('birthday-month');
|
||||||
);
|
if (daySel.options.length === 0) {
|
||||||
|
daySel.innerHTML = Array.from({ length: 31 }, (_, i) =>
|
||||||
|
`<option value="${i + 1}">${i + 1}</option>`).join('');
|
||||||
|
}
|
||||||
|
if (monthSel.options.length === 0) {
|
||||||
|
const months = t('months');
|
||||||
|
monthSel.innerHTML = months.map((m, i) => `<option value="${i + 1}">${escHtml(m)}</option>`).join('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openBirthdayModal() {
|
function openBirthdayModal() {
|
||||||
const cals = birthdayCalendars();
|
const cal = birthdayCalendar();
|
||||||
const sel = document.getElementById('birthday-cal');
|
document.getElementById('birthday-no-cal').classList.toggle('hidden', !!cal);
|
||||||
sel.innerHTML = cals.map(c => `<option value="${c.id}">${escHtml(c.name)}</option>`).join('');
|
document.getElementById('birthday-form').classList.toggle('hidden', !cal);
|
||||||
const none = cals.length === 0;
|
document.getElementById('birthday-save').classList.toggle('hidden', !cal);
|
||||||
document.getElementById('birthday-no-cals').classList.toggle('hidden', !none);
|
if (cal) {
|
||||||
document.getElementById('birthday-cal-group').classList.toggle('hidden', none || cals.length === 1);
|
fillBirthdayDateSelects();
|
||||||
document.getElementById('birthday-name').value = '';
|
|
||||||
document.getElementById('birthday-year-unknown').checked = false;
|
|
||||||
const dateEl = document.getElementById('birthday-date');
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
dateEl.value = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;
|
document.getElementById('birthday-name').value = '';
|
||||||
document.getElementById('birthday-save').disabled = none;
|
document.getElementById('birthday-day').value = String(today.getDate());
|
||||||
|
document.getElementById('birthday-month').value = String(today.getMonth() + 1);
|
||||||
|
document.getElementById('birthday-year').value = String(today.getFullYear());
|
||||||
|
document.getElementById('birthday-year-unknown').checked = false;
|
||||||
|
document.getElementById('birthday-year').classList.remove('hidden');
|
||||||
|
}
|
||||||
openModal('modal-birthday');
|
openModal('modal-birthday');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindBirthdayModal() {
|
function bindBirthdayModal() {
|
||||||
|
const yearUnknown = document.getElementById('birthday-year-unknown');
|
||||||
|
const yearInput = document.getElementById('birthday-year');
|
||||||
|
yearUnknown.addEventListener('change', () => {
|
||||||
|
yearInput.classList.toggle('hidden', yearUnknown.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('birthday-activate').onclick = async () => {
|
||||||
|
try {
|
||||||
|
await ensureBirthdayCalendar();
|
||||||
|
showToast(t('calendar_created', { name: t('birthday_calendar_name') }));
|
||||||
|
openBirthdayModal(); // re-render the modal in form mode
|
||||||
|
} catch (e) { showToast(e.message, true); }
|
||||||
|
};
|
||||||
|
|
||||||
document.getElementById('birthday-save').onclick = async () => {
|
document.getElementById('birthday-save').onclick = async () => {
|
||||||
const cals = birthdayCalendars();
|
const cal = birthdayCalendar();
|
||||||
if (!cals.length) return;
|
if (!cal) return;
|
||||||
const name = document.getElementById('birthday-name').value.trim();
|
const name = document.getElementById('birthday-name').value.trim();
|
||||||
if (!name) { showToast(t('error_enter_name'), true); return; }
|
if (!name) { showToast(t('error_enter_name'), true); return; }
|
||||||
const dateStr = document.getElementById('birthday-date').value; // YYYY-MM-DD
|
const d = parseInt(document.getElementById('birthday-day').value, 10);
|
||||||
if (!dateStr) return;
|
const m = parseInt(document.getElementById('birthday-month').value, 10);
|
||||||
const [y, m, d] = dateStr.split('-').map(n => parseInt(n, 10));
|
const unknown = yearUnknown.checked;
|
||||||
const yearUnknown = document.getElementById('birthday-year-unknown').checked;
|
const y = unknown ? 1970 : parseInt(yearInput.value, 10) || new Date().getFullYear();
|
||||||
const calSel = document.getElementById('birthday-cal');
|
|
||||||
const calId = parseInt(calSel.value || cals[0].id, 10);
|
|
||||||
// All-day anchor: birth year if known, else 1970 so FREQ=YEARLY expands
|
// All-day anchor: birth year if known, else 1970 so FREQ=YEARLY expands
|
||||||
// across any queried range. end = next day.
|
// across any queried range. end = next day.
|
||||||
const anchorYear = yearUnknown ? 1970 : y;
|
|
||||||
const pad = n => String(n).padStart(2, '0');
|
const pad = n => String(n).padStart(2, '0');
|
||||||
const start = `${anchorYear}-${pad(m)}-${pad(d)}`;
|
const start = `${y}-${pad(m)}-${pad(d)}`;
|
||||||
const endDate = new Date(Date.UTC(anchorYear, m - 1, d + 1));
|
const endDate = new Date(Date.UTC(y, m - 1, d + 1));
|
||||||
const end = `${endDate.getUTCFullYear()}-${pad(endDate.getUTCMonth()+1)}-${pad(endDate.getUTCDate())}`;
|
const end = `${endDate.getUTCFullYear()}-${pad(endDate.getUTCMonth() + 1)}-${pad(endDate.getUTCDate())}`;
|
||||||
const body = {
|
const body = { calendar_id: cal.id, title: name, start, end, allDay: true, rrule: 'FREQ=YEARLY' };
|
||||||
calendar_id: calId, title: name, start, end, allDay: true,
|
if (!unknown) body.birth_year = y;
|
||||||
rrule: 'FREQ=YEARLY',
|
|
||||||
};
|
|
||||||
if (!yearUnknown) body.birth_year = y;
|
|
||||||
try {
|
try {
|
||||||
await api.post('/local/events', body);
|
await api.post('/local/events', body);
|
||||||
closeModal('modal-birthday');
|
closeModal('modal-birthday');
|
||||||
@@ -3561,6 +3581,57 @@ function renderAllAccounts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCalendarTable();
|
renderCalendarTable();
|
||||||
|
renderBirthdaySettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Settings › Kalender › Geburtstage: activate the single birthday calendar,
|
||||||
|
* or (when active) tweak its "notify N days before". Colour/visibility live in
|
||||||
|
* the sidebar like any other calendar. */
|
||||||
|
function renderBirthdaySettings() {
|
||||||
|
const container = document.getElementById('birthday-settings');
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = '';
|
||||||
|
const cal = birthdayCalendar();
|
||||||
|
if (!cal) {
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'form-hint';
|
||||||
|
hint.textContent = t('birthday_activate_hint');
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'btn btn-primary btn-sm';
|
||||||
|
btn.textContent = t('birthday_activate');
|
||||||
|
btn.onclick = async () => {
|
||||||
|
try {
|
||||||
|
await ensureBirthdayCalendar();
|
||||||
|
renderBirthdaySettings();
|
||||||
|
renderCalendarTable();
|
||||||
|
showToast(t('calendar_created', { name: t('birthday_calendar_name') }));
|
||||||
|
} catch (e) { showToast(e.message, true); }
|
||||||
|
};
|
||||||
|
container.append(hint, btn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const status = document.createElement('div');
|
||||||
|
status.className = 'form-hint';
|
||||||
|
status.textContent = t('birthday_active');
|
||||||
|
const notifyWrap = document.createElement('div');
|
||||||
|
notifyWrap.className = 'form-group';
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.textContent = t('birthday_notify');
|
||||||
|
const sel = document.createElement('select');
|
||||||
|
fillBirthdayNotifySelect(sel);
|
||||||
|
sel.value = String(cal.birthday_notify_days_before ?? -1);
|
||||||
|
sel.onchange = async () => {
|
||||||
|
const v = parseInt(sel.value, 10);
|
||||||
|
try {
|
||||||
|
await api.put(`/local/calendars/${cal.id}`, { birthday_notify_days_before: v });
|
||||||
|
cal.birthday_notify_days_before = v < 0 ? null : v;
|
||||||
|
} catch (e) { showToast(e.message, true); }
|
||||||
|
};
|
||||||
|
notifyWrap.append(label, sel);
|
||||||
|
const colorHint = document.createElement('div');
|
||||||
|
colorHint.className = 'form-hint';
|
||||||
|
colorHint.textContent = t('birthday_color_hint');
|
||||||
|
container.append(status, notifyWrap, colorHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHiddenCalendars() {
|
function renderHiddenCalendars() {
|
||||||
|
|||||||
@@ -290,6 +290,13 @@ const translations = {
|
|||||||
birthday_target: 'Geburtstagskalender',
|
birthday_target: 'Geburtstagskalender',
|
||||||
birthday_no_calendars: 'Kein Geburtstagskalender vorhanden. Erstelle zuerst einen Kalender und aktiviere „Geburtstagskalender".',
|
birthday_no_calendars: 'Kein Geburtstagskalender vorhanden. Erstelle zuerst einen Kalender und aktiviere „Geburtstagskalender".',
|
||||||
birthday_created: 'Geburtstag „{name}" hinzugefügt',
|
birthday_created: 'Geburtstag „{name}" hinzugefügt',
|
||||||
|
birthday_calendar_name: 'Geburtstage',
|
||||||
|
birthday_activate: 'Geburtstagskalender aktivieren',
|
||||||
|
birthday_activate_hint: 'Aktiviere den Geburtstagskalender, um Geburtstage anzulegen. Er erscheint als eigener Kalender in der Seitenleiste.',
|
||||||
|
birthday_active: 'Geburtstagskalender aktiv',
|
||||||
|
birthday_year_ph: 'Jahr',
|
||||||
|
birthday_settings_title: 'Geburtstage',
|
||||||
|
birthday_color_hint: 'Farbe und Sichtbarkeit änderst du in der Seitenleiste.',
|
||||||
error_name_url: 'Bitte Name und URL eingeben',
|
error_name_url: 'Bitte Name und URL eingeben',
|
||||||
ical_subscribed: '"{name}" abonniert',
|
ical_subscribed: '"{name}" abonniert',
|
||||||
google_synced: 'Kalender synchronisiert',
|
google_synced: 'Kalender synchronisiert',
|
||||||
@@ -625,6 +632,13 @@ const translations = {
|
|||||||
birthday_target: 'Birthday calendar',
|
birthday_target: 'Birthday calendar',
|
||||||
birthday_no_calendars: 'No birthday calendar yet. Create a calendar and enable “Birthday calendar” first.',
|
birthday_no_calendars: 'No birthday calendar yet. Create a calendar and enable “Birthday calendar” first.',
|
||||||
birthday_created: 'Birthday “{name}” added',
|
birthday_created: 'Birthday “{name}” added',
|
||||||
|
birthday_calendar_name: 'Birthdays',
|
||||||
|
birthday_activate: 'Enable birthday calendar',
|
||||||
|
birthday_activate_hint: 'Enable the birthday calendar to add birthdays. It appears as its own calendar in the sidebar.',
|
||||||
|
birthday_active: 'Birthday calendar active',
|
||||||
|
birthday_year_ph: 'Year',
|
||||||
|
birthday_settings_title: 'Birthdays',
|
||||||
|
birthday_color_hint: 'Change colour and visibility in the sidebar.',
|
||||||
error_name_url: 'Please enter a name and URL',
|
error_name_url: 'Please enter a name and URL',
|
||||||
ical_subscribed: '"{name}" subscribed',
|
ical_subscribed: '"{name}" subscribed',
|
||||||
google_synced: 'Calendar synced',
|
google_synced: 'Calendar synced',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// the entry HTML / version files). New releases take effect on the next
|
// the entry HTML / version files). New releases take effect on the next
|
||||||
// reload, no manual SW unregister required.
|
// reload, no manual SW unregister required.
|
||||||
|
|
||||||
const CACHE_VERSION = 'calendarr-v24';
|
const CACHE_VERSION = 'calendarr-v25';
|
||||||
const OFFLINE_SHELL = ['/', '/index.html'];
|
const OFFLINE_SHELL = ['/', '/index.html'];
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
|
|||||||
Reference in New Issue
Block a user