diff --git a/package-lock.json b/package-lock.json index d826181..bfe450d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "express": "^4.17.2", "hydrogen-view-sdk": "^0.0.4", - "linkedom": "^0.13.2", + "linkedom": "^0.14.1", "matrix-public-archive-shared": "file:./shared/", "node-fetch": "^2.6.7" }, @@ -1255,9 +1255,9 @@ } }, "node_modules/linkedom": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.13.2.tgz", - "integrity": "sha512-lQPdDnml1Rl/T8QW3j10jJ37LMRcZqryy5kwHDIw9AYMabeE4P6kMp2mqJXWjjeXxJ4ebJC05Qx9Xxs4jWhNMw==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.1.tgz", + "integrity": "sha512-yEHdtGdIwOhQPatnBqq9URPSibrYXe3xE6sUdZraQSJmV3wTGEdkpPYM4SX9/m7t6GvUkEDeRpX7MPdUBdhs9w==", "dependencies": { "css-select": "^4.2.1", "cssom": "^0.5.0", @@ -2844,9 +2844,9 @@ } }, "linkedom": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.13.2.tgz", - "integrity": "sha512-lQPdDnml1Rl/T8QW3j10jJ37LMRcZqryy5kwHDIw9AYMabeE4P6kMp2mqJXWjjeXxJ4ebJC05Qx9Xxs4jWhNMw==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.1.tgz", + "integrity": "sha512-yEHdtGdIwOhQPatnBqq9URPSibrYXe3xE6sUdZraQSJmV3wTGEdkpPYM4SX9/m7t6GvUkEDeRpX7MPdUBdhs9w==", "requires": { "css-select": "^4.2.1", "cssom": "^0.5.0", diff --git a/package.json b/package.json index 24400e4..5066e67 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "dependencies": { "express": "^4.17.2", "hydrogen-view-sdk": "^0.0.4", - "linkedom": "^0.13.2", - "node-fetch": "^2.6.7", - "matrix-public-archive-shared": "file:./shared/" + "linkedom": "^0.14.1", + "matrix-public-archive-shared": "file:./shared/", + "node-fetch": "^2.6.7" } } diff --git a/public/styles/styles.css b/public/styles/styles.css index 8dcf0da..07f1f8b 100644 --- a/public/styles/styles.css +++ b/public/styles/styles.css @@ -14,3 +14,48 @@ min-height: 0; min-width: 0; } + +.CalendarView { + max-width: 280px; + font: 100% system-ui; +} + +.CalendarView_heading { + display: flex; + justify-content: space-between; +} + +.CalendarView_heading_prevButton, +.CalendarView_heading_nextButton { + padding-left: 2em; + padding-right: 2em; + + background: none; + border: 0; +} + +.CalendarView_heading_prevButton:hover, +.CalendarView_heading_nextButton:hover { + cursor: pointer; +} + +.CalendarView_heading_text { +} + +.CalendarView_calendar { + display: grid; + grid-template-columns: repeat(7, 1fr); + + list-style: none; + margin: 0; + padding: 0; + text-align: center; +} + +.CalendarView_dayName { + background: #eee; +} + +.CalendarView_day { + padding: 2px; +} diff --git a/shared/CalendarView.js b/shared/CalendarView.js new file mode 100644 index 0000000..ebf2164 --- /dev/null +++ b/shared/CalendarView.js @@ -0,0 +1,95 @@ +const { TemplateView } = require('hydrogen-view-sdk'); + +// 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 +// month. +// +// via https://stackoverflow.com/a/1184359/796832 +function numDaysInMonthForDate(date) { + return new Date(date.getYear(), date.getMonth() + 1, 0).getDate(); +} + +// 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); + lookupDate.setDate(i + 1); + + const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short' }); + const localized = lookupDate.toLocaleString('default', { weekday: 'short' }); + + DAYS_OF_WEEK[lookup] = localized; +} + +class CalendarView extends TemplateView { + render(t, vm) { + const date = vm.date; + return t.div({ className: { CalendarView: true } }, [ + t.div({ className: { CalendarView_heading: true } }, [ + t.button( + { + className: { CalendarView_heading_prevButton: true }, + onClick: () => vm.prevMonth(), + }, + ['\u276E'] + ), + t.h4({ className: { CalendarView_heading_text: true } }, [ + date.toLocaleString('default', { year: 'numeric', month: 'long' }), + ]), + t.button( + { + className: { CalendarView_heading_nextButton: true }, + onClick: () => vm.nextMonth(), + }, + ['\u276F'] + ), + ]), + 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]]); + }), + (() => { + let dayNodes = []; + for (let i = 0; i < numDaysInMonthForDate(date); i++) { + // We only need to calculate the day offset for the first day of the month + let dayNumber; + if (i === 0) { + const dayNumberDate = new Date(date); + dayNumberDate.setDate(i + 1); + // day number from 0 (monday) to 6 (sunday) + dayNumber = dayNumberDate.getDay(); + } + + dayNodes.push( + t.li( + { + className: { CalendarView_day: true }, + style: i === 0 ? `grid-column-start: ${dayNumber + 1};` : null, + }, + [String(i + 1)] + ) + ); + } + + return dayNodes; + })() + ) + ), + ]); + } +} + +module.exports = CalendarView; diff --git a/shared/RightPanelContentView.js b/shared/RightPanelContentView.js index 026f19b..fb266b1 100644 --- a/shared/RightPanelContentView.js +++ b/shared/RightPanelContentView.js @@ -1,5 +1,7 @@ const { TemplateView } = require('hydrogen-view-sdk'); +const CalendarView = require('matrix-public-archive-shared/CalendarView'); + class RightPanelContentView extends TemplateView { render(t, vm) { return t.div( @@ -8,7 +10,7 @@ class RightPanelContentView extends TemplateView { todo: true, }, }, - [t.div('test')] + [t.div('test'), t.view(new CalendarView(vm.calendarViewModel))] ); } } diff --git a/shared/hydrogen-vm-render-script.js b/shared/hydrogen-vm-render-script.js index b58d913..e2d1f17 100644 --- a/shared/hydrogen-vm-render-script.js +++ b/shared/hydrogen-vm-render-script.js @@ -185,6 +185,20 @@ async function mountHydrogen() { activeViewModel: { type: 'custom', customView: RightPanelContentView, + + calendarViewModel: { + date: new Date(), + prevMonth: function () { + const prevMonthDate = new Date(this.date); + prevMonthDate.setMonth(displayedDate.getMonth() - 1); + this.date = prevMonthDate; + }, + nextMOnth: function () { + const nextMonthDate = new Date(this.date); + nextMonthDate.setMonth(displayedDate.getMonth() + 1); + this.date = nextMonthDate; + }, + }, }, }, };