207 lines
7.0 KiB
JavaScript
207 lines
7.0 KiB
JavaScript
'use strict';
|
|
|
|
// Be mindful to do all date operations in UTC (the archive is all in UTC date/times)
|
|
|
|
const { TemplateView } = require('hydrogen-view-sdk');
|
|
const {
|
|
areTimestampsFromSameUtcDay,
|
|
} = require('matrix-public-archive-shared/lib/timestamp-utilities');
|
|
|
|
// Get the number of days in the given month where the `inputDate` lies.
|
|
//
|
|
// via https://stackoverflow.com/a/1184359/796832
|
|
function numDaysInMonthForDate(inputDate) {
|
|
// Month in JavaScript is 0-indexed (January is 0, February is 1, etc),
|
|
// but by using 0 as the day it will give us the last day of the prior
|
|
const lastDayOfTheMonthDate = new Date(
|
|
Date.UTC(inputDate.getUTCFullYear(), inputDate.getUTCMonth() + 1, 0)
|
|
);
|
|
const lastDayNumberOfTheMonth = lastDayOfTheMonthDate.getUTCDate();
|
|
// The last day number in the month is a proxy for how many days there are in that month
|
|
return lastDayNumberOfTheMonth;
|
|
}
|
|
|
|
// Map from day of week to the localized name of the day
|
|
const DAYS_OF_WEEK = {
|
|
Sun: null,
|
|
Mon: null,
|
|
Tue: null,
|
|
Wed: null,
|
|
Thu: null,
|
|
Fri: null,
|
|
Sat: null,
|
|
};
|
|
|
|
// Generate the localized days of the week names
|
|
const today = new Date();
|
|
for (let i = 0; i < 7; i++) {
|
|
const lookupDate = new Date(today);
|
|
// Date is a 1-based number
|
|
lookupDate.setUTCDate(i + 1);
|
|
|
|
const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short', timeZone: 'UTC' });
|
|
const localized = lookupDate.toLocaleString('default', { weekday: 'short', timeZone: 'UTC' });
|
|
|
|
DAYS_OF_WEEK[lookup] = localized;
|
|
}
|
|
|
|
class CalendarView extends TemplateView {
|
|
render(t, vm) {
|
|
return t.div({ className: { CalendarView: true } }, [
|
|
t.div({ className: { CalendarView_header: true } }, [
|
|
t.button(
|
|
{
|
|
className: { CalendarView_header_prevButton: true },
|
|
onClick: () => vm.prevMonth(),
|
|
},
|
|
['\u276E']
|
|
),
|
|
t.map(
|
|
(vm) => vm.calendarDate,
|
|
(calendarDate, t) => {
|
|
return t.h4({ className: { CalendarView_heading: true } }, [
|
|
t.span({ className: { CalendarView_heading_text: true } }, [
|
|
calendarDate.toLocaleString('default', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
timeZone: 'UTC',
|
|
}),
|
|
// Dropdown arrow
|
|
t.svg(
|
|
{
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
width: '18',
|
|
height: '18',
|
|
viewBox: '0 0 18 18',
|
|
fill: 'none',
|
|
},
|
|
[
|
|
t.path({
|
|
d: 'M6 7.5L9 10.5L12 7.5',
|
|
stroke: 'currentColor',
|
|
'stroke-width': '1.5',
|
|
'stroke-linecap': 'round',
|
|
'stroke-linejoin': 'round',
|
|
}),
|
|
]
|
|
),
|
|
]),
|
|
|
|
t.input({
|
|
type: 'month',
|
|
className: { CalendarView_heading_monthInput: true },
|
|
value: `${calendarDate.getUTCFullYear()}-${
|
|
// ('0' + '2') -> '02'
|
|
// ('0' + '12') -> '12'
|
|
('0' + (calendarDate.getUTCMonth() + 1)).slice(-2)
|
|
}`,
|
|
onChange: (e) => vm.onMonthInputChange(e),
|
|
}),
|
|
|
|
t.select(
|
|
{
|
|
className: {
|
|
CalendarView_heading_yearSelectFallback: true,
|
|
},
|
|
onChange: (e) => vm.onYearFallbackSelectChange(e),
|
|
},
|
|
[].concat(
|
|
(() => {
|
|
let yearSelectNodes = [];
|
|
const today = new Date();
|
|
for (let year = today.getUTCFullYear(); year > 1960; year--) {
|
|
yearSelectNodes.push(
|
|
t.option(
|
|
{
|
|
value: year,
|
|
selected: year === calendarDate.getUTCFullYear(),
|
|
},
|
|
[`${year}`]
|
|
)
|
|
);
|
|
}
|
|
|
|
return yearSelectNodes;
|
|
})()
|
|
)
|
|
),
|
|
]);
|
|
}
|
|
),
|
|
t.button(
|
|
{
|
|
className: { CalendarView_header_nextButton: true },
|
|
onClick: () => vm.nextMonth(),
|
|
},
|
|
['\u276F']
|
|
),
|
|
]),
|
|
t.map(
|
|
(vm) => vm.calendarDate,
|
|
(calendarDate, t) => {
|
|
return t.ol(
|
|
{ className: { CalendarView_calendar: true } },
|
|
[].concat(
|
|
Object.keys(DAYS_OF_WEEK).map((dayKey) => {
|
|
return t.li({ className: { CalendarView_dayName: true } }, [DAYS_OF_WEEK[dayKey]]);
|
|
}),
|
|
(() => {
|
|
const todayTs = Date.now();
|
|
const numberOfDaysInMonth = numDaysInMonthForDate(calendarDate);
|
|
|
|
let dayNodes = [];
|
|
for (let i = 0; i < numberOfDaysInMonth; i++) {
|
|
const dayNumberDate = new Date(calendarDate);
|
|
// Date is a 1-based number
|
|
dayNumberDate.setUTCDate(i + 1);
|
|
const isDayInFuture = dayNumberDate.getTime() - todayTs > 0;
|
|
|
|
// The current day displayed in the archive
|
|
const isActive = areTimestampsFromSameUtcDay(
|
|
dayNumberDate.getTime(),
|
|
vm.activeDate.getTime()
|
|
);
|
|
|
|
// day number from 0 (monday) to 6 (sunday)
|
|
const dayNumber = dayNumberDate.getUTCDay();
|
|
|
|
// +1 because we're going from 0-based day to 1-based `grid-column-start`
|
|
const gridColumnStart = dayNumber + 1;
|
|
|
|
dayNodes.push(
|
|
t.li(
|
|
{
|
|
className: { CalendarView_day: true },
|
|
// Offset the first day of the month to the proper day of the week
|
|
style: i === 0 ? `grid-column-start: ${gridColumnStart};` : null,
|
|
},
|
|
[
|
|
t.a(
|
|
{
|
|
className: {
|
|
CalendarView_dayLink: true,
|
|
CalendarView_dayLink_active: isActive,
|
|
CalendarView_dayLink_disabled: isDayInFuture,
|
|
},
|
|
// Disable navigation to future days
|
|
href: isDayInFuture ? null : vm.archiveUrlForDate(dayNumberDate),
|
|
},
|
|
[String(dayNumberDate.getUTCDate())]
|
|
),
|
|
]
|
|
)
|
|
);
|
|
}
|
|
|
|
return dayNodes;
|
|
})()
|
|
)
|
|
);
|
|
}
|
|
),
|
|
]);
|
|
}
|
|
}
|
|
|
|
module.exports = CalendarView;
|