Add calendar downloads for events
This commit is contained in:
@@ -949,6 +949,39 @@ section {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.date-card.cancelled {
|
||||
opacity: 0.65;
|
||||
background: #f0efec;
|
||||
}
|
||||
|
||||
.date-card.cancelled .date-number {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.date-status {
|
||||
color: #a33a2b;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.calendar-link {
|
||||
align-items: center;
|
||||
color: var(--secondary-color);
|
||||
display: inline-flex;
|
||||
font-size: 0.8rem;
|
||||
gap: 6px;
|
||||
margin-top: 9px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.calendar-link:hover,
|
||||
.calendar-link:focus-visible {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.events-cta {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
+114
-1
@@ -409,7 +409,7 @@
|
||||
<div class="dates-grid">
|
||||
<div class="date-card wine-event"><div class="date-icon"><i class="fas fa-wine-glass-alt" aria-hidden="true"></i></div><div class="date-info"><span class="date-day">Samstag</span><span class="date-number">19. September</span></div></div>
|
||||
<div class="date-card wine-event"><div class="date-icon"><i class="fas fa-wine-glass-alt" aria-hidden="true"></i></div><div class="date-info"><span class="date-day">Samstag</span><span class="date-number">17. Oktober</span></div></div>
|
||||
<div class="date-card wine-event"><div class="date-icon"><i class="fas fa-wine-glass-alt" aria-hidden="true"></i></div><div class="date-info"><span class="date-day">Donnerstag</span><span class="date-number">30. Oktober</span></div></div>
|
||||
<div class="date-card wine-event cancelled"><div class="date-icon"><i class="fas fa-wine-glass-alt" aria-hidden="true"></i></div><div class="date-info"><span class="date-day">Donnerstag</span><span class="date-number">30. Oktober</span><span class="date-status">CANCELLED</span></div></div>
|
||||
<div class="date-card wine-event"><div class="date-icon"><i class="fas fa-wine-glass-alt" aria-hidden="true"></i></div><div class="date-info"><span class="date-day">Dienstag</span><span class="date-number">29. Dezember</span></div></div>
|
||||
</div>
|
||||
</details>
|
||||
@@ -675,6 +675,119 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Create one downloadable iCalendar file for each scheduled event
|
||||
const monthNumbers = {
|
||||
'Januar': '01',
|
||||
'Februar': '02',
|
||||
'März': '03',
|
||||
'April': '04',
|
||||
'Mai': '05',
|
||||
'Juni': '06',
|
||||
'Juli': '07',
|
||||
'August': '08',
|
||||
'September': '09',
|
||||
'Oktober': '10',
|
||||
'November': '11',
|
||||
'Dezember': '12'
|
||||
};
|
||||
|
||||
const escapeIcsText = text => String(text)
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/;/g, '\\;')
|
||||
.replace(/,/g, '\\,')
|
||||
.replace(/\r?\n/g, '\\n');
|
||||
|
||||
const formatIcsDateTime = date => [
|
||||
date.getFullYear(),
|
||||
String(date.getMonth() + 1).padStart(2, '0'),
|
||||
String(date.getDate()).padStart(2, '0')
|
||||
].join('') + 'T' + [
|
||||
String(date.getHours()).padStart(2, '0'),
|
||||
String(date.getMinutes()).padStart(2, '0'),
|
||||
'00'
|
||||
].join('');
|
||||
|
||||
const formatIcsUtcDateTime = date => [
|
||||
date.getUTCFullYear(),
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0'),
|
||||
String(date.getUTCDate()).padStart(2, '0')
|
||||
].join('') + 'T' + [
|
||||
String(date.getUTCHours()).padStart(2, '0'),
|
||||
String(date.getUTCMinutes()).padStart(2, '0'),
|
||||
'00'
|
||||
].join('');
|
||||
|
||||
document.querySelectorAll('.date-card:not(.cancelled)').forEach(card => {
|
||||
const dateNumber = card.querySelector('.date-number')?.textContent.trim();
|
||||
const yearText = card.closest('[data-year-accordion]')?.querySelector('summary')?.textContent;
|
||||
const dateMatch = dateNumber?.match(/^(\d{1,2})\.\s+(.+)$/);
|
||||
const yearMatch = yearText?.match(/\d{4}/);
|
||||
const month = dateMatch ? monthNumbers[dateMatch[2]] : null;
|
||||
|
||||
if (!dateMatch || !yearMatch || !month) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = `${yearMatch[0]}-${month}-${dateMatch[1].padStart(2, '0')}`;
|
||||
const timeText = card.querySelector('.date-time')?.textContent.trim();
|
||||
const timeMatch = timeText?.match(/^(\d{1,2}):(\d{2})/);
|
||||
const isWineEvent = card.classList.contains('wine-event');
|
||||
const title = isWineEvent ? 'Weinreise bei Frida & Fred' : 'Käsetasting bei Frida & Fred';
|
||||
const slug = `${isWineEvent ? 'weinreise' : 'kaesetasting'}-${date}`;
|
||||
const calendarLink = document.createElement('a');
|
||||
calendarLink.className = 'calendar-link';
|
||||
calendarLink.href = '#';
|
||||
calendarLink.setAttribute('aria-label', `${title} am ${dateNumber} ${yearMatch[0]} in den Kalender eintragen`);
|
||||
calendarLink.innerHTML = '<i class="fas fa-calendar-plus" aria-hidden="true"></i><span>In Kalender speichern</span>';
|
||||
|
||||
calendarLink.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
let calendarContent;
|
||||
|
||||
if (timeMatch) {
|
||||
const start = new Date(`${date}T${timeMatch[1].padStart(2, '0')}:${timeMatch[2]}:00`);
|
||||
const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
|
||||
calendarContent = [
|
||||
`DTSTART;TZID=Europe/Berlin:${formatIcsDateTime(start)}`,
|
||||
`DTEND;TZID=Europe/Berlin:${formatIcsDateTime(end)}`
|
||||
];
|
||||
} else {
|
||||
const start = new Date(`${date}T12:00:00`);
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
calendarContent = [
|
||||
`DTSTART;VALUE=DATE:${date.replace(/-/g, '')}`,
|
||||
`DTEND;VALUE=DATE:${end.getFullYear()}${String(end.getMonth() + 1).padStart(2, '0')}${String(end.getDate()).padStart(2, '0')}`
|
||||
];
|
||||
}
|
||||
|
||||
const ics = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//Frida & Fred//Events//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'BEGIN:VEVENT',
|
||||
`UID:${slug}@frida-fred.com`,
|
||||
`DTSTAMP:${formatIcsUtcDateTime(new Date())}Z`,
|
||||
...calendarContent,
|
||||
`SUMMARY:${escapeIcsText(title)}`,
|
||||
'LOCATION:Frida & Fred',
|
||||
`DESCRIPTION:${escapeIcsText('Anmeldung: info@frida-fred.com oder 0151 19426530')}`,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR'
|
||||
].join('\r\n');
|
||||
const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = downloadUrl;
|
||||
downloadLink.download = `${slug}.ics`;
|
||||
downloadLink.click();
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
});
|
||||
|
||||
card.querySelector('.date-info')?.appendChild(calendarLink);
|
||||
});
|
||||
|
||||
// Smooth scrolling for internal links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
|
||||
Reference in New Issue
Block a user