Add calendar downloads for events
This commit is contained in:
@@ -949,6 +949,39 @@ section {
|
|||||||
margin-top: 4px;
|
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 {
|
.events-cta {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-1
@@ -409,7 +409,7 @@
|
|||||||
<div class="dates-grid">
|
<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">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">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 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>
|
</div>
|
||||||
</details>
|
</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
|
// Smooth scrolling for internal links
|
||||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||||
anchor.addEventListener('click', function (e) {
|
anchor.addEventListener('click', function (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user