107 lines
3.6 KiB
JavaScript
107 lines
3.6 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const dataElement = document.getElementById('calendar-events-data');
|
|
const modalElement = document.getElementById('dayEventsModal');
|
|
let calendarEvents = {};
|
|
|
|
const escapeHtml = (value) => String(value || '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
|
|
const safeExternalUrl = (value) => {
|
|
if (!value) return '';
|
|
try {
|
|
const url = new URL(value, window.location.origin);
|
|
return ['http:', 'https:'].includes(url.protocol) ? url.href : '';
|
|
} catch (error) {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
if (dataElement) {
|
|
try {
|
|
calendarEvents = JSON.parse(dataElement.textContent);
|
|
} catch (error) {
|
|
calendarEvents = {};
|
|
}
|
|
}
|
|
|
|
if (modalElement && window.bootstrap) {
|
|
const modal = new window.bootstrap.Modal(modalElement);
|
|
const titleNode = modalElement.querySelector('[data-calendar-title]');
|
|
const bodyNode = modalElement.querySelector('[data-calendar-body]');
|
|
|
|
const renderEvents = (isoDate) => {
|
|
const selectedEvents = calendarEvents[isoDate] || [];
|
|
const titleDate = new Date(`${isoDate}T12:00:00`);
|
|
titleNode.textContent = titleDate.toLocaleDateString(undefined, {
|
|
weekday: 'long',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
|
|
if (!selectedEvents.length) {
|
|
bodyNode.innerHTML = `
|
|
<div class="modal-event-card">
|
|
<h4>No event scheduled</h4>
|
|
<p class="mb-0 text-secondary">This date is currently open. Check another day or revisit after the next update.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
bodyNode.innerHTML = selectedEvents
|
|
.map((event) => {
|
|
const name = escapeHtml(event.name);
|
|
const time = escapeHtml(event.time);
|
|
const location = escapeHtml(event.location || 'Location announced soon');
|
|
const summary = event.summary ? `<p>${escapeHtml(event.summary)}</p>` : '';
|
|
const detailUrl = escapeHtml(event.detail_url || '#');
|
|
const eventUrl = safeExternalUrl(event.event_url);
|
|
return `
|
|
<article class="modal-event-card">
|
|
<h4>${name}</h4>
|
|
<div class="modal-event-meta">
|
|
<span>${time}</span>
|
|
<span>${location}</span>
|
|
</div>
|
|
${summary}
|
|
<div class="d-flex gap-2 flex-wrap mt-3">
|
|
<a class="btn btn-sm btn-primary-brand" href="${detailUrl}">View details</a>
|
|
${eventUrl ? `<a class="btn btn-sm btn-ghost" href="${escapeHtml(eventUrl)}" target="_blank" rel="noopener">Event page</a>` : ''}
|
|
</div>
|
|
</article>
|
|
`;
|
|
})
|
|
.join('');
|
|
};
|
|
|
|
document.querySelectorAll('[data-calendar-date]').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
renderEvents(button.dataset.calendarDate);
|
|
modal.show();
|
|
});
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('[data-copy-snippet]').forEach((button) => {
|
|
button.addEventListener('click', async () => {
|
|
const target = document.querySelector(button.dataset.copyTarget);
|
|
if (!target) return;
|
|
try {
|
|
await navigator.clipboard.writeText(target.textContent.trim());
|
|
const original = button.textContent;
|
|
button.textContent = 'Copied';
|
|
window.setTimeout(() => {
|
|
button.textContent = original;
|
|
}, 1600);
|
|
} catch (error) {
|
|
button.textContent = 'Copy manually';
|
|
}
|
|
});
|
|
});
|
|
});
|