Files
bDS/src/main/engine/assets/calendarRuntime.ts

295 lines
9.1 KiB
TypeScript

export const CALENDAR_RUNTIME_JS = String.raw`(() => {
const button = document.querySelector('[data-blog-calendar-toggle]');
const panel = document.querySelector('[data-blog-calendar-panel]');
const closeButton = document.querySelector('[data-blog-calendar-close]');
const calendarRoot = document.querySelector('[data-blog-calendar-root]');
const status = document.querySelector('[data-blog-calendar-status]');
if (!button || !panel || !calendarRoot || !status) {
return;
}
const labels = {
loading: panel.getAttribute('data-i18n-loading') || 'Loading calendar…',
error: panel.getAttribute('data-i18n-error') || 'Calendar data could not be loaded.',
};
let isInitialized = false;
let years = {};
let months = {};
let days = {};
let maxYearCount = 0;
let maxMonthCount = 0;
let maxDayCount = 0;
function pad2(value) {
return String(value).padStart(2, '0');
}
function normalizeCountMap(value) {
if (!value || typeof value !== 'object') {
return {};
}
const map = {};
for (const [key, rawCount] of Object.entries(value)) {
const count = Number(rawCount);
if (!Number.isFinite(count) || count <= 0) {
continue;
}
map[key] = Math.floor(count);
}
return map;
}
function computeMaxCount(value) {
const counts = Object.values(value || {});
if (counts.length === 0) {
return 0;
}
return Math.max(...counts.map((count) => Number(count) || 0));
}
function applyHeatStyle(target, count, maxCount) {
if (!(target instanceof HTMLElement) || !Number.isFinite(count) || count <= 0 || !Number.isFinite(maxCount) || maxCount <= 0) {
target?.style?.setProperty('--blog-calendar-heat-alpha', '0');
target?.style?.setProperty('--blog-calendar-heat-hue', '210');
return;
}
const normalized = Math.min(1, count / maxCount);
const hue = Math.round(210 - (210 * normalized));
const alpha = (0.30 + normalized * 0.65).toFixed(3);
target.style.setProperty('--blog-calendar-heat-hue', String(hue));
target.style.setProperty('--blog-calendar-heat-alpha', alpha);
}
function navigateTo(pathname) {
if (!pathname) {
return;
}
window.location.assign(pathname);
}
function parseInitialYearMonth() {
const initialYearRaw = button.getAttribute('data-blog-calendar-year');
const initialMonthRaw = button.getAttribute('data-blog-calendar-month');
const initialYear = Number(initialYearRaw);
const initialMonth = Number(initialMonthRaw);
let selectedYear = Number.isInteger(initialYear) && initialYear > 0 ? initialYear : null;
let selectedMonth = Number.isInteger(initialMonth) && initialMonth >= 1 && initialMonth <= 12
? (initialMonth - 1)
: null;
if (!Number.isInteger(selectedYear) || !Number.isInteger(selectedMonth)) {
const pathname = window.location.pathname || '';
const parts = pathname.split('/').filter(Boolean);
const pathYear = Number(parts[0]);
const pathMonth = Number(parts[1]);
if (!Number.isInteger(selectedYear) && Number.isInteger(pathYear) && pathYear > 0 && String(parts[0]).length === 4) {
selectedYear = pathYear;
}
if (!Number.isInteger(selectedMonth) && Number.isInteger(pathMonth) && pathMonth >= 1 && pathMonth <= 12) {
selectedMonth = pathMonth - 1;
}
}
return { selectedYear, selectedMonth };
}
async function loadCalendarData() {
const response = await fetch('/calendar.json', { cache: 'no-store' });
if (!response.ok) {
throw new Error('calendar.json request failed');
}
const parsed = await response.json();
years = normalizeCountMap(parsed?.years);
months = normalizeCountMap(parsed?.months);
days = normalizeCountMap(parsed?.days);
maxYearCount = computeMaxCount(years);
maxMonthCount = computeMaxCount(months);
maxDayCount = computeMaxCount(days);
}
function getDateFromClickEvent(event) {
if (!(event?.target instanceof Element)) {
return '';
}
const dateEl = event.target.closest('[data-vc-date]');
if (!(dateEl instanceof HTMLElement)) {
return '';
}
return dateEl.dataset.vcDate || '';
}
async function initializeCalendar() {
if (isInitialized) {
return;
}
status.textContent = labels.loading;
try {
await loadCalendarData();
const Calendar = window.VanillaCalendarPro?.Calendar;
if (typeof Calendar !== 'function') {
throw new Error('Vanilla Calendar Pro is unavailable');
}
const initialYearMonth = parseInitialYearMonth();
const calendarOptions = {
...(Number.isInteger(initialYearMonth.selectedYear) ? { selectedYear: initialYearMonth.selectedYear } : {}),
...(Number.isInteger(initialYearMonth.selectedMonth) ? { selectedMonth: initialYearMonth.selectedMonth } : {}),
onCreateDateEls(_self, dateEl) {
const dateKey = dateEl.dataset.vcDate || '';
const count = Number(days[dateKey] || 0);
const buttonEl = dateEl.querySelector('[data-vc-date-btn]');
if (!(buttonEl instanceof HTMLElement)) {
return;
}
if (count <= 0) {
dateEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(buttonEl, 0, maxDayCount);
return;
}
dateEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(buttonEl, count, maxDayCount);
},
onCreateMonthEls(self, monthEl) {
if (!(monthEl instanceof HTMLElement)) {
return;
}
const monthIndex = Number(monthEl.dataset.vcMonthsMonth);
const selectedYear = Number(self?.context?.selectedYear);
if (!Number.isInteger(monthIndex) || !Number.isInteger(selectedYear)) {
monthEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(monthEl, 0, maxMonthCount);
return;
}
const monthKey = String(selectedYear) + '-' + pad2(monthIndex + 1);
const count = Number(months[monthKey] || 0);
if (count <= 0) {
monthEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(monthEl, 0, maxMonthCount);
return;
}
monthEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(monthEl, count, maxMonthCount);
},
onCreateYearEls(_self, yearEl) {
if (!(yearEl instanceof HTMLElement)) {
return;
}
const yearValue = Number(yearEl.dataset.vcYearsYear);
if (!Number.isInteger(yearValue)) {
yearEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(yearEl, 0, maxYearCount);
return;
}
const yearKey = String(yearValue);
const count = Number(years[yearKey] || 0);
if (count <= 0) {
yearEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(yearEl, 0, maxYearCount);
return;
}
yearEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(yearEl, count, maxYearCount);
},
onClickDate(_self, event) {
const dateKey = getDateFromClickEvent(event);
if (!dateKey || !days[dateKey]) {
return;
}
const [year, month, day] = dateKey.split('-');
if (!year || !month || !day) {
return;
}
navigateTo('/' + year + '/' + month + '/' + day + '/');
},
onClickMonth(self) {
const selectedYear = Number(self?.context?.selectedYear);
const selectedMonth = Number(self?.context?.selectedMonth);
if (!Number.isInteger(selectedYear) || !Number.isInteger(selectedMonth)) {
return;
}
const monthKey = String(selectedYear) + '-' + pad2(selectedMonth + 1);
if (!months[monthKey]) {
return;
}
navigateTo('/' + String(selectedYear) + '/' + pad2(selectedMonth + 1) + '/');
},
onClickYear(self) {
const selectedYear = Number(self?.context?.selectedYear);
if (!Number.isInteger(selectedYear)) {
return;
}
const yearKey = String(selectedYear);
if (!years[yearKey]) {
return;
}
navigateTo('/' + String(selectedYear) + '/');
},
};
const calendar = new Calendar('[data-blog-calendar-root]', calendarOptions);
calendar.init();
status.textContent = '';
status.setAttribute('hidden', 'hidden');
isInitialized = true;
} catch {
status.textContent = labels.error;
status.removeAttribute('hidden');
}
}
function setPanelOpen(isOpen) {
if (isOpen) {
panel.removeAttribute('hidden');
void initializeCalendar();
return;
}
panel.setAttribute('hidden', 'hidden');
}
button.addEventListener('click', () => {
const isHidden = panel.hasAttribute('hidden');
setPanelOpen(isHidden);
});
if (closeButton) {
closeButton.addEventListener('click', () => {
setPanelOpen(false);
});
}
})();`;