added details and pop-up to event calendar
All checks were successful
publish / publish (push) Successful in 26s
All checks were successful
publish / publish (push) Successful in 26s
This commit is contained in:
parent
e905c11f70
commit
2264cd53b5
7 changed files with 234 additions and 146 deletions
|
@ -14,6 +14,7 @@
|
||||||
--color-off-dark: #1D1F29;
|
--color-off-dark: #1D1F29;
|
||||||
--color-light: #F8F9Fa;
|
--color-light: #F8F9Fa;
|
||||||
--color-dark: #191A23;
|
--color-dark: #191A23;
|
||||||
|
--color-today-highlight: #757F8A44;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
--color-link: #BBE04C;
|
--color-link: #BBE04C;
|
||||||
--color-off: #1D1F29;
|
--color-off: #1D1F29;
|
||||||
--color-off-dark: #F3F3F3;
|
--color-off-dark: #F3F3F3;
|
||||||
|
--color-today-highlight: #28303F;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
|
@ -383,7 +385,7 @@ li .dropdown-item-mobile {
|
||||||
Button
|
Button
|
||||||
======================== */
|
======================== */
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 1.15rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
|
@ -451,7 +453,7 @@ footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.95rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav ul {
|
.footer-nav ul {
|
||||||
|
@ -493,7 +495,7 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
column-count: 2;
|
column-count: 3;
|
||||||
column-gap: 2rem;
|
column-gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +605,10 @@ footer {
|
||||||
color: var(--color-dark) !important;
|
color: var(--color-dark) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-event-title {
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fc-toolbar-title {
|
.fc-toolbar-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
|
@ -621,11 +627,11 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-day-today {
|
.fc-day-today {
|
||||||
background-color: var(--color-accent) !important;
|
background-color: var(--color-today-highlight) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-day-today .fc-daygrid-day-number {
|
.fc-day-today .fc-daygrid-day-number {
|
||||||
color: var(--color-dark) !important;
|
color: var(--color-text) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event {
|
.fc-event {
|
||||||
|
@ -637,6 +643,7 @@ footer {
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event.fc-event-start,
|
.fc-event.fc-event-start,
|
||||||
|
@ -654,22 +661,39 @@ footer {
|
||||||
|
|
||||||
.fc-event:hover,
|
.fc-event:hover,
|
||||||
.fc-event:active {
|
.fc-event:active {
|
||||||
white-space: normal;
|
|
||||||
overflow: visible;
|
|
||||||
text-overflow: clip;
|
|
||||||
z-index: 10;
|
|
||||||
background-color: var(--color-accent);
|
background-color: var(--color-accent);
|
||||||
color: var(--color-dark);
|
color: var(--color-dark);
|
||||||
position: relative;
|
|
||||||
word-break: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event:hover .fc-event-time, .fc-event:hover .fc-daygrid-event-dot,
|
.fc-daygrid-event-dot {
|
||||||
.fc-event:active .fc-event-time, .fc-event:active .fc-daygrid-event-dot {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#eventModal .modal-content {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventModal .modal-header,
|
||||||
|
#eventModal .modal-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventModal .modal-content {
|
||||||
|
background-color: var(--color-light);
|
||||||
|
border: 1px solid var(--color-text);
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 4px 0 0 var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #eventModal .modal-content {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
border: 1px solid var(--color-text);
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 4px 0 0 var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
404 Error Page
|
404 Error Page
|
||||||
======================== */
|
======================== */
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: "Barrierefreiheit"
|
title: "Accessibility"
|
||||||
draft: false
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: "Impressum"
|
title: "Imprint"
|
||||||
draft: false
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
121
layouts/_partials/event-calendar.html
Normal file
121
layouts/_partials/event-calendar.html
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{{ if or (eq .RelPermalink "/events/") (eq .RelPermalink "/en/events/") }}
|
||||||
|
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.css' rel='stylesheet'/>
|
||||||
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.js'></script>
|
||||||
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5/locales/de.js'></script>
|
||||||
|
<script src='https://cdn.jsdelivr.net/npm/ical.js@1.4.0/build/ical.min.js'></script>
|
||||||
|
<script>
|
||||||
|
/* global bootstrap */
|
||||||
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
|
const calendarEl = document.getElementById('calendar');
|
||||||
|
const calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
|
initialView: 'dayGridMonth',
|
||||||
|
dayMaxEventRows: true,
|
||||||
|
height: 'auto',
|
||||||
|
locale: 'de',
|
||||||
|
firstDay: 1,
|
||||||
|
eventClick: function(info) {
|
||||||
|
const event = info.event;
|
||||||
|
const title = event.title;
|
||||||
|
const location = event.extendedProps.location || 'Nicht angegeben';
|
||||||
|
|
||||||
|
const start = event.start;
|
||||||
|
const end = event.end;
|
||||||
|
let dateStr = '';
|
||||||
|
|
||||||
|
if (start && end) {
|
||||||
|
dateStr = start.toLocaleString() + ' – ' + end.toLocaleString();
|
||||||
|
} else if (start) {
|
||||||
|
dateStr = start.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('modalEventTitle').textContent = title;
|
||||||
|
document.getElementById('modalEventDate').textContent = dateStr;
|
||||||
|
document.getElementById('modalEventLocation').textContent = location;
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('eventModal'));
|
||||||
|
modal.show();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
calendar.render();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://nc.ifsr.de/remote.php/dav/public-calendars/W5Sk7zLD28n6ze44/?export');
|
||||||
|
const icsData = await response.text();
|
||||||
|
|
||||||
|
const jcalData = ICAL.parse(icsData);
|
||||||
|
const comp = new ICAL.Component(jcalData, null);
|
||||||
|
const events = comp.getAllSubcomponents('vevent');
|
||||||
|
|
||||||
|
console.log("ICS enthält", events.length, "Events");
|
||||||
|
|
||||||
|
const fcEvents = [];
|
||||||
|
|
||||||
|
events.forEach(event => {
|
||||||
|
try {
|
||||||
|
const icalEvent = new ICAL.Event(event);
|
||||||
|
|
||||||
|
if (icalEvent.isRecurring()) {
|
||||||
|
const expand = new ICAL.RecurExpansion({
|
||||||
|
component: event,
|
||||||
|
dtstart: icalEvent.startDate
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
if (!expand.next()) break;
|
||||||
|
|
||||||
|
const next = expand.last;
|
||||||
|
fcEvents.push({
|
||||||
|
title: icalEvent.summary || "Ohne Titel",
|
||||||
|
start: next.toJSDate(),
|
||||||
|
allDay: icalEvent.startDate.isDate,
|
||||||
|
location: icalEvent.location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const start = icalEvent.startDate?.toJSDate();
|
||||||
|
const end = icalEvent.endDate?.toJSDate();
|
||||||
|
if (!start) return;
|
||||||
|
|
||||||
|
fcEvents.push({
|
||||||
|
title: icalEvent.summary || "Ohne Titel",
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
allDay: icalEvent.startDate.isDate,
|
||||||
|
location: icalEvent.location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Fehler beim Parsen eines Events:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Nach dem Mapping:", fcEvents.length, "Events");
|
||||||
|
|
||||||
|
calendar.addEventSource(fcEvents);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden oder Parsen der ICS-Datei:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<!-- Event Details Modal -->
|
||||||
|
<div class="modal fade" id="eventModal" tabindex="-1" aria-labelledby="eventModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title" id="eventModalLabel">Event Details</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h4 id="modalEventTitle"></h4>
|
||||||
|
<p><strong>Datum:</strong> <span id="modalEventDate"></span></p>
|
||||||
|
<p><strong>Ort:</strong> <span id="modalEventLocation"></span></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary btn-lg" data-bs-dismiss="modal">Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,19 +1,36 @@
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<p>© 2025 iFSR. Alle Rechte vorbehalten.</p>
|
{{ if eq .Site.Language.Lang "de" }}
|
||||||
<nav class="footer-nav">
|
<p>© 2025 iFSR. Alle Rechte vorbehalten.</p>
|
||||||
<ul>
|
<nav class="footer-nav">
|
||||||
<li><a href="/barrierefreiheit">Barrierefreiheit</a></li>
|
<ul>
|
||||||
<li><a href="/datenschutz">Datenschutz</a></li>
|
<li><a href="/barrierefreiheit">Barrierefreiheit</a></li>
|
||||||
<li><a href="/impressum">Impressum</a></li>
|
<li><a href="/datenschutz">Datenschutz</a></li>
|
||||||
</ul>
|
<li><a href="/impressum">Impressum</a></li>
|
||||||
<hr>
|
</ul>
|
||||||
<ul>
|
<hr>
|
||||||
<li><a href="/de" onclick="localStorage.setItem('language', 'de');">Deutsch</a></li>
|
<ul>
|
||||||
<li>|</li>
|
<li><a href="/de" onclick="localStorage.setItem('language', 'de');">Deutsch</a></li>
|
||||||
<li><a href="/en" onclick="localStorage.setItem('language', 'en');">English</a></li>
|
<li>|</li>
|
||||||
</ul>
|
<li><a href="/en" onclick="localStorage.setItem('language', 'en');">English</a></li>
|
||||||
</nav>
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{ else }}
|
||||||
|
<p>© 2025 iFSR. All rights reserved.</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/en/barrierefreiheit">Accessibility</a></li>
|
||||||
|
<li><a href="/en/datenschutz">Privacy</a></li>
|
||||||
|
<li><a href="/en/impressum">Legal Notice</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/de" onclick="localStorage.setItem('language', 'de');">German</a></li>
|
||||||
|
<li>|</li>
|
||||||
|
<li><a href="/en" onclick="localStorage.setItem('language', 'en');">English</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
|
@ -20,125 +20,13 @@
|
||||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
<!-- Darkmode JS -->
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem("theme");
|
|
||||||
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
||||||
const isDark = stored ? stored === "dark" : systemPrefersDark;
|
|
||||||
if (isDark) {
|
|
||||||
document.documentElement.classList.add("dark");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore errors
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<!-- Localization Auto Redirect -->
|
|
||||||
{{ if .IsHome }}
|
|
||||||
<script>
|
|
||||||
const cachedLang = localStorage.getItem("language");
|
|
||||||
const path = window.location.pathname;
|
|
||||||
|
|
||||||
if (cachedLang) {
|
|
||||||
if (cachedLang === "de" && path !== "/") {
|
|
||||||
window.location.href = "/";
|
|
||||||
} else if (cachedLang === "en" && path !== "/en/") {
|
|
||||||
window.location.href = "/en/";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const lang = navigator.language;
|
|
||||||
|
|
||||||
if (lang.startsWith("de")) {
|
|
||||||
localStorage.setItem("language", "de");
|
|
||||||
} else {
|
|
||||||
localStorage.setItem("language", "en");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{{ end }}
|
|
||||||
<!-- Exo 2 Font -->
|
<!-- Exo 2 Font -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600&display=swap" rel="stylesheet"/>
|
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600&display=swap" rel="stylesheet"/>
|
||||||
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
|
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
|
||||||
|
|
||||||
<!-- Event Calendar -->
|
<!-- JS Partials -->
|
||||||
{{ if or (eq .RelPermalink "/events/") (eq .RelPermalink "/en/events/") }}
|
{{ partial "scripts.html" . }}
|
||||||
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.css' rel='stylesheet'/>
|
{{ partial "event-calendar.html" . }}
|
||||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.js'></script>
|
|
||||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5/locales/de.js'></script>
|
|
||||||
<script src='https://cdn.jsdelivr.net/npm/ical.js@1.4.0/build/ical.min.js'></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
|
||||||
const calendarEl = document.getElementById('calendar');
|
|
||||||
const calendar = new FullCalendar.Calendar(calendarEl, {
|
|
||||||
initialView: 'dayGridMonth',
|
|
||||||
dayMaxEventRows: true,
|
|
||||||
height: 'auto',
|
|
||||||
locale: 'de',
|
|
||||||
firstDay: 1
|
|
||||||
});
|
|
||||||
calendar.render();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://nc.ifsr.de/remote.php/dav/public-calendars/W5Sk7zLD28n6ze44/?export');
|
|
||||||
const icsData = await response.text();
|
|
||||||
|
|
||||||
const jcalData = ICAL.parse(icsData);
|
|
||||||
const comp = new ICAL.Component(jcalData, null);
|
|
||||||
const events = comp.getAllSubcomponents('vevent');
|
|
||||||
|
|
||||||
console.log("ICS enthält", events.length, "Events");
|
|
||||||
|
|
||||||
const fcEvents = [];
|
|
||||||
|
|
||||||
events.forEach(event => {
|
|
||||||
try {
|
|
||||||
const icalEvent = new ICAL.Event(event);
|
|
||||||
|
|
||||||
if (icalEvent.isRecurring()) {
|
|
||||||
const expand = new ICAL.RecurExpansion({
|
|
||||||
component: event,
|
|
||||||
dtstart: icalEvent.startDate
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < 30; i++) {
|
|
||||||
if (!expand.next()) break;
|
|
||||||
|
|
||||||
const next = expand.last;
|
|
||||||
fcEvents.push({
|
|
||||||
title: icalEvent.summary || "Ohne Titel",
|
|
||||||
start: next.toJSDate(),
|
|
||||||
allDay: icalEvent.startDate.isDate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const start = icalEvent.startDate?.toJSDate();
|
|
||||||
const end = icalEvent.endDate?.toJSDate();
|
|
||||||
if (!start) return;
|
|
||||||
|
|
||||||
fcEvents.push({
|
|
||||||
title: icalEvent.summary || "Ohne Titel",
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
allDay: icalEvent.startDate.isDate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Fehler beim Parsen eines Events:", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Nach dem Mapping:", fcEvents.length, "Events");
|
|
||||||
|
|
||||||
calendar.addEventSource(fcEvents);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Laden oder Parsen der ICS-Datei:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ partialCached "head/css.html" . }}
|
{{ partialCached "head/css.html" . }}
|
||||||
{{ partialCached "head/js.html" . }}
|
{{ partialCached "head/js.html" . }}
|
38
layouts/_partials/scripts.html
Normal file
38
layouts/_partials/scripts.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<!-- Darkmode JS -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
const isDark = stored ? stored === "dark" : systemPrefersDark;
|
||||||
|
if (isDark) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<!-- Localization Auto Redirect -->
|
||||||
|
{{ if .IsHome }}
|
||||||
|
<script>
|
||||||
|
const cachedLang = localStorage.getItem("language");
|
||||||
|
const path = window.location.pathname;
|
||||||
|
|
||||||
|
if (cachedLang) {
|
||||||
|
if (cachedLang === "de" && path !== "/") {
|
||||||
|
window.location.href = "/";
|
||||||
|
} else if (cachedLang === "en" && path !== "/en/") {
|
||||||
|
window.location.href = "/en/";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const lang = navigator.language;
|
||||||
|
|
||||||
|
if (lang.startsWith("de")) {
|
||||||
|
localStorage.setItem("language", "de");
|
||||||
|
} else {
|
||||||
|
localStorage.setItem("language", "en");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
Loading…
Add table
Add a link
Reference in a new issue