document.addEventListener('DOMContentLoaded', function() { (function(){ let events = []; let eventsByDate = {}; // Funktion zum Parsen der ICS-Datei function parseICS(icsText) { let events = []; let lines = icsText.split(/\r?\n/); let event = null; lines.forEach(line => { if (line.startsWith("BEGIN:VEVENT")) { event = {}; } else if (line.startsWith("END:VEVENT")) { if (event) events.push(event); event = null; } else if (event) { let colonIndex = line.indexOf(":"); if (colonIndex > -1) { let key = line.substring(0, colonIndex); let value = line.substring(colonIndex + 1); // Handle properties with parameters (like TZID) const baseKey = key.split(";")[0]; if (baseKey === "DTSTART") { event.start = value; event.startParams = key.includes(";") ? key.substring(key.indexOf(";") + 1) : null; } else if (baseKey === "DTEND") { event.end = value; event.endParams = key.includes(";") ? key.substring(key.indexOf(";") + 1) : null; } else if (baseKey === "SUMMARY") { event.summary = value; } else if (baseKey === "DESCRIPTION") { event.description = value; } else if (baseKey === "RRULE") { event.rrule = value; } } } }); return events; } // Hilfsfunktion: Parst einen ICS-Datum-String ins Format "YYYY-MM-DD" function parseDateString(icsDateStr) { // Handle different date formats if (!icsDateStr) return null; // For basic date format: YYYYMMDD if (icsDateStr.length === 8) { let year = icsDateStr.substring(0, 4); let month = icsDateStr.substring(4, 6); let day = icsDateStr.substring(6, 8); return `${year}-${month}-${day}`; } // For datetime formats: YYYYMMDDTHHmmssZ or YYYYMMDDTHHmmss else if (icsDateStr.includes("T")) { let year = icsDateStr.substring(0, 4); let month = icsDateStr.substring(4, 6); let day = icsDateStr.substring(6, 8); return `${year}-${month}-${day}`; } return null; } // Extract date components from different date formats function getDateComponents(icsDateStr) { if (!icsDateStr) return null; // Basic handling - extract YYYY, MM, DD regardless of format const year = parseInt(icsDateStr.substring(0, 4)); const month = parseInt(icsDateStr.substring(4, 6)) - 1; // 0-based months const day = parseInt(icsDateStr.substring(6, 8)); return { year, month, day }; } function expandRecurringEvents(event, year, month) { if (!event.rrule) return [event]; const rruleStr = event.rrule; // Get start date components const startComponents = getDateComponents(event.start); if (!startComponents) return [event]; const startDate = new Date( startComponents.year, startComponents.month, startComponents.day ); const rangeStart = new Date(year, month, 1); const rangeEnd = new Date(year, month + 1, 0); const expandedEvents = []; if (rruleStr.includes("FREQ=WEEKLY") && rruleStr.includes("BYDAY")) { const bydayMatch = rruleStr.match(/BYDAY=([^;]+)/); if (bydayMatch) { const dayCode = bydayMatch[1]; const dayMap = { 'MO': 1, 'TU': 2, 'WE': 3, 'TH': 4, 'FR': 5, 'SA': 6, 'SU': 0 }; const targetDay = dayMap[dayCode]; if (targetDay !== undefined) { // Create events for each matching day in the month let day = 1; while (day <= rangeEnd.getDate()) { const testDate = new Date(year, month, day); if (testDate.getDay() === targetDay && testDate >= startDate) { const newEvent = {...event}; const eventDate = formatDateForICS(testDate); // Preserve time portion from original event const timePart = event.start.includes('T') ? event.start.substring(event.start.indexOf('T')) : ''; const endTimePart = event.end.includes('T') ? event.end.substring(event.end.indexOf('T')) : ''; newEvent.start = eventDate + timePart; newEvent.end = eventDate + endTimePart; expandedEvents.push(newEvent); } day++; } } } } else if (rruleStr.includes("FREQ=MONTHLY") && rruleStr.includes("BYDAY")) { const bydayMatch = rruleStr.match(/BYDAY=([^;]+)/); if (bydayMatch) { const bydays = bydayMatch[1].split(','); const dayMap = { 'MO': 1, 'TU': 2, 'WE': 3, 'TH': 4, 'FR': 5, 'SA': 6, 'SU': 0 }; bydays.forEach(byday => { const occurrence = parseInt(byday) || 1; const dayCode = byday.slice(-2); const dayIndex = dayMap[dayCode]; let day = 1; let count = 0; while (day <= rangeEnd.getDate()) { const testDate = new Date(year, month, day); if (testDate.getDay() === dayIndex) { count++; if (count === occurrence || (occurrence < 0 && day > rangeEnd.getDate() + occurrence * 7)) { const newEvent = {...event}; const eventDate = new Date(year, month, day); // Preserve time portion from original event const timePart = event.start.includes('T') ? event.start.substring(event.start.indexOf('T')) : ''; const endTimePart = event.end.includes('T') ? event.end.substring(event.end.indexOf('T')) : ''; newEvent.start = formatDateForICS(eventDate) + timePart; newEvent.end = formatDateForICS(eventDate) + endTimePart; expandedEvents.push(newEvent); } } day++; } }); } } return expandedEvents.length > 0 ? expandedEvents : [event]; } // Kalender initialisieren let currentYear, currentMonth; const currentMonthElem = document.getElementById("current-month"); const calendarBody = document.getElementById("calendar-body"); const eventPanel = document.getElementById("event-panel"); const eventDateElem = document.getElementById("event-date"); const eventDetailsElem = document.getElementById("event-details"); document.getElementById("prev-month").addEventListener("click", function(){ currentMonth--; if (currentMonth < 0) { currentMonth = 11; currentYear--; } updateEventsForMonth(currentYear, currentMonth); }); document.getElementById("next-month").addEventListener("click", function(){ currentMonth++; if (currentMonth > 11) { currentMonth = 0; currentYear++; } updateEventsForMonth(currentYear, currentMonth); }); function updateEventsForMonth(year, month) { // Clear existing events for this month view eventsByDate = {}; // Process each event, expanding recurring ones events.forEach(ev => { if (ev.rrule) { // For recurring events, expand them for current month const expandedEvents = expandRecurringEvents(ev, year, month); expandedEvents.forEach(expandedEv => { let dateKey = parseDateString(expandedEv.start); if (dateKey) { if (!eventsByDate[dateKey]) { eventsByDate[dateKey] = []; } eventsByDate[dateKey].push(expandedEv); } }); } else { // For regular events, check if they fall in current month let dateKey = parseDateString(ev.start); if (dateKey) { // Check if this event belongs to current month view const eventYear = parseInt(dateKey.split('-')[0]); const eventMonth = parseInt(dateKey.split('-')[1]) - 1; if (eventYear === year && eventMonth === month) { if (!eventsByDate[dateKey]) { eventsByDate[dateKey] = []; } eventsByDate[dateKey].push(ev); } } } }); renderCalendar(year, month); } function renderCalendar(year, month) { // Setze die Monatsbeschriftung (in Deutsch) const monthNames = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]; currentMonthElem.textContent = monthNames[month] + " " + year; calendarBody.innerHTML = ""; let firstDay = new Date(year, month, 1); let firstDayIndex = (firstDay.getDay() + 6) % 7; // Montag = 0, Dienstag = 1, etc. let daysInMonth = new Date(year, month + 1, 0).getDate(); let row = document.createElement("tr"); // Leere Zellen vor dem 1. Tag for (let i = 0; i < firstDayIndex; i++){ let cell = document.createElement("td"); row.appendChild(cell); } // Tage hinzufügen for (let day = 1; day <= daysInMonth; day++){ if (row.children.length === 7) { calendarBody.appendChild(row); row = document.createElement("tr"); } let cell = document.createElement("td"); cell.innerHTML = "" + day + ""; let dayStr = day < 10 ? "0" + day : day; let monthStr = (month + 1) < 10 ? "0" + (month + 1) : (month + 1); let dateKey = year + "-" + monthStr + "-" + dayStr; if (eventsByDate[dateKey]) { let dotsContainer = document.createElement("div"); dotsContainer.className = "event-dots-container"; cell.classList.add("has-event"); // Gruppe Events nach Typ const events = eventsByDate[dateKey]; const hasMembersOnly = events.some(e => e.summary.toLowerCase().includes("members only")); const hasSubbotnik = events.some(e => e.summary.toLowerCase().includes("subbotnik")); const hasBastelabend = events.some(e => e.summary.toLowerCase().includes("bastelabend")); const hasSpieleabend = events.some(e => e.summary.toLowerCase().includes("spieleabend")); const hasRegular = events.some(e => { const title = e.summary.toLowerCase(); return !title.includes("members only") && !title.includes("subbotnik") && !title.includes("bastelabend") && !title.includes("spieleabend"); }); // Füge Dots entsprechend der Event-Typen hinzu if (hasMembersOnly) { let dot = document.createElement("div"); dot.className = "event-dot event-dot-red"; dotsContainer.appendChild(dot); } if (hasSubbotnik || hasBastelabend || hasSpieleabend) { let dot = document.createElement("div"); dot.className = "event-dot event-dot-orange"; dotsContainer.appendChild(dot); } if (hasRegular) { let dot = document.createElement("div"); dot.className = "event-dot event-dot-greenyellow"; dotsContainer.appendChild(dot); } cell.appendChild(dotsContainer); cell.dataset.dateKey = dateKey; cell.addEventListener("click", function() { // Clear previous selections document.querySelectorAll('.selected-day').forEach(el => { el.classList.remove('selected-day'); }); cell.classList.add('selected-day'); showEventDetails(dateKey); }); } row.appendChild(cell); } // Falls die letzte Zeile nicht komplett ist while (row.children.length < 7) { let cell = document.createElement("td"); row.appendChild(cell); } calendarBody.appendChild(row); } function createEventLink(eventTitle) { if (eventTitle.startsWith("Datengarten")) { // Extract the number after "Datengarten " const match = eventTitle.match(/Datengarten\s+(\d+)/i); if (match && match[1]) { return `https://berlin.ccc.de/datengarten/${match[1]}/`; } } // For other titles, convert to lowercase and use as path const slug = eventTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, ''); return `https://berlin.ccc.de/page/${slug}/`; } function showEventDetails(dateKey) { const events = eventsByDate[dateKey]; eventDateElem.textContent = formatDate(dateKey); eventDetailsElem.innerHTML = ""; if (events && events.length > 0) { events.forEach(ev => { let eventItem = document.createElement("div"); eventItem.className = "event-item"; let eventTitle = document.createElement("div"); eventTitle.className = "event-title"; // Create a link for the event title let titleLink = document.createElement("h"); titleLink.textContent = ev.summary; titleLink.target = "_blank"; eventTitle.appendChild(titleLink); eventItem.appendChild(eventTitle); let eventTime = document.createElement("div"); eventTime.className = "event-time"; eventTime.textContent = `Start: ${formatTime(ev.start)}, End: ${formatTime(ev.end)}`; eventItem.appendChild(eventTime); if (ev.description) { let eventDescription = document.createElement("div"); eventDescription.className = "event-description"; // Check if the description is a URL and make it a clickable link if (ev.description.trim().startsWith('http')) { let linkElement = document.createElement("a"); linkElement.href = ev.description.trim(); linkElement.textContent = ev.description.trim(); linkElement.target = "_blank"; eventDescription.innerHTML = ''; eventDescription.appendChild(linkElement); } else { eventDescription.textContent = ev.description; } eventItem.appendChild(eventDescription); } eventDetailsElem.appendChild(eventItem); }); } else { let noEvents = document.createElement("div"); noEvents.className = "no-events"; noEvents.textContent = "Keine Veranstaltungen an diesem Tag."; eventDetailsElem.appendChild(noEvents); } eventPanel.style.display = "block"; } function formatDate(dateStr) { // Convert YYYY-MM-DD to DD.MM.YYYY const parts = dateStr.split("-"); return `${parts[2]}.${parts[1]}.${parts[0]}`; } function formatTime(icsTimeStr) { // Format time for display if (!icsTimeStr) return ""; if (icsTimeStr.length === 8) { // All-day event return "Ganztägig"; } else if (icsTimeStr.includes("T")) { // Time-specific event (with or without timezone) const timeStart = icsTimeStr.indexOf("T") + 1; const hour = icsTimeStr.substring(timeStart, timeStart + 2); const minute = icsTimeStr.substring(timeStart + 2, timeStart + 4); return `${hour}:${minute}`; } return icsTimeStr; } function formatDateForICS(date) { const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); return `${year}${month}${day}`; } // ICS-Datei abrufen und Events verarbeiten fetch('/all.ics') .then(response => response.text()) .then(data => { events = parseICS(data); // Initialize with current date let today = new Date(); currentYear = today.getFullYear(); currentMonth = today.getMonth(); // Process events for current month updateEventsForMonth(currentYear, currentMonth); }) .catch(err => console.error('Fehler beim Laden der ICS-Datei:', err)); })(); });