39435-vm/staticfiles/js/calendar.js
Flatlogic Bot 242b727260 v5
2026-04-02 20:58:36 +00:00

158 lines
5.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) {
if (modalElement.parentElement !== document.body) {
document.body.appendChild(modalElement);
}
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';
}
});
});
const escapeAttribute = (value) => String(value || '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
document.querySelectorAll('[data-embed-settings]').forEach((form) => {
const baseUrl = form.dataset.embedBaseUrl || '';
const urlOutput = document.getElementById('dashboardEmbedUrl');
const iframeOutput = document.getElementById('dashboardIframeSnippet');
const previewLink = document.querySelector('[data-embed-preview]');
if (!baseUrl || !urlOutput || !iframeOutput) return;
const updateEmbedOutput = () => {
const formData = new FormData(form);
const params = new URLSearchParams();
const month = String(formData.get('month') || '').trim();
const header = String(formData.get('header') || '1');
const title = String(formData.get('title') || 'Where to find us calendar').trim() || 'Where to find us calendar';
const width = String(formData.get('width') || '100%').trim() || '100%';
const height = Math.max(parseInt(String(formData.get('height') || '760'), 10) || 760, 360);
const radius = Math.max(parseInt(String(formData.get('radius') || '24'), 10) || 24, 0);
if (/^\d{4}-\d{2}$/.test(month)) {
params.set('month', month);
}
if (header === '0') {
params.set('header', '0');
}
const widgetUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
const iframeSnippet = `<iframe src="${escapeAttribute(widgetUrl)}" title="${escapeAttribute(title)}" width="${escapeAttribute(width)}" height="${height}" style="border:0;border-radius:${radius}px;overflow:hidden;" loading="lazy"></iframe>`;
urlOutput.textContent = widgetUrl;
iframeOutput.textContent = iframeSnippet;
if (previewLink) {
previewLink.href = widgetUrl;
}
};
['input', 'change'].forEach((eventName) => {
form.addEventListener(eventName, updateEmbedOutput);
});
updateEmbedOutput();
});
});