39435-vm/static/js/calendar.js
Flatlogic Bot 693107e079 v1
2026-04-02 16:02:38 +00:00

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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
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';
}
});
});
});