Create members.json api #489
|
@ -37,6 +37,13 @@ steps:
|
|||
commands:
|
||||
- npm run build:calendar
|
||||
|
||||
- name: generate-api
|
||||
image: node:16
|
||||
depends_on:
|
||||
- install-deps
|
||||
commands:
|
||||
- npm run build:api
|
||||
|
||||
- name: build
|
||||
image: node:16
|
||||
depends_on:
|
||||
|
@ -47,6 +54,7 @@ steps:
|
|||
- name: export
|
||||
image: node:16
|
||||
depends_on:
|
||||
- generate-api
|
||||
- generate-calendar
|
||||
- build
|
||||
commands:
|
||||
|
|
|
@ -28,4 +28,8 @@ yarn-error.log*
|
|||
/public/events.ics
|
||||
|
||||
# Images should be optimized
|
||||
/public/images
|
||||
/public/images
|
||||
|
||||
# APIs should be automatically generated, schema should be checked in
|
||||
/public/api/*
|
||||
!/public/api/schema
|
|
@ -19,7 +19,7 @@ interface EventCardProps {
|
|||
permaLink: string;
|
||||
showDescription?: boolean;
|
||||
children: ReactNode;
|
||||
year: string;
|
||||
year: number;
|
||||
term: string;
|
||||
slug: string;
|
||||
titleLinked: boolean;
|
||||
|
|
|
@ -14,7 +14,7 @@ interface MiniEventCardProps {
|
|||
startDate: Date;
|
||||
endDate?: Date;
|
||||
background: "dark-bg" | "normal-bg";
|
||||
year: string;
|
||||
year: number;
|
||||
term: string;
|
||||
slug: string;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { parse } from "date-fns";
|
||||
import React from "react";
|
||||
|
||||
import { DATE_FORMAT, getLocalDateFromEST } from "@/utils";
|
||||
|
||||
import warnings from "../content/warnings/warnings.json";
|
||||
import { DATE_FORMAT, getLocalDateFromEST } from "../utils";
|
||||
|
||||
import styles from "./WarningHeader.module.css";
|
||||
|
||||
|
|
176
lib/events.ts
176
lib/events.ts
|
@ -6,28 +6,33 @@ import matter from "gray-matter";
|
|||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
|
||||
import type { Props } from "../pages/events/[year]/[term]/index";
|
||||
// do not use alias "@/utils" as generate-calendar imports a function from this file and ts-node is not compatible
|
||||
import {
|
||||
Term,
|
||||
TERMS,
|
||||
isTerm,
|
||||
DATE_FORMAT,
|
||||
getLocalDateFromEST,
|
||||
} from "../utils";
|
||||
TermYear,
|
||||
getTermYear,
|
||||
getCurrentTermYear,
|
||||
} from "@/utils";
|
||||
|
||||
import type { Props } from "../pages/events/[year]/[term]";
|
||||
|
||||
const EVENTS_PATH = path.join("content", "events");
|
||||
|
||||
export async function getEventYears(): Promise<string[]> {
|
||||
export async function getEventYears(): Promise<number[]> {
|
||||
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
.map((dirent) => parseInt(dirent.name))
|
||||
.sort();
|
||||
}
|
||||
|
||||
export async function getEventTermsByYear(year: string): Promise<Term[]> {
|
||||
export async function getEventTermsByYear(year: number): Promise<Term[]> {
|
||||
return (
|
||||
await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true })
|
||||
await fs.readdir(path.join(EVENTS_PATH, year.toString()), {
|
||||
withFileTypes: true,
|
||||
})
|
||||
)
|
||||
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
|
||||
.map((dirent) => dirent.name as Term)
|
||||
|
@ -55,7 +60,7 @@ interface Metadata {
|
|||
location: string;
|
||||
permaLink: string;
|
||||
registerLink?: string;
|
||||
year: string;
|
||||
year: number;
|
||||
term: string;
|
||||
slug: string;
|
||||
}
|
||||
|
@ -66,12 +71,12 @@ export interface Event {
|
|||
}
|
||||
|
||||
export async function getEventBySlug(
|
||||
year: string,
|
||||
year: number,
|
||||
term: Term,
|
||||
slug: string
|
||||
): Promise<Event> {
|
||||
const file = await fs.readFile(
|
||||
path.join(EVENTS_PATH, year, term, `${slug}.md`),
|
||||
path.join(EVENTS_PATH, year.toString(), term, `${slug}.md`),
|
||||
"utf-8"
|
||||
);
|
||||
const { content, data } = matter(file);
|
||||
|
@ -95,11 +100,11 @@ export async function getEventBySlug(
|
|||
}
|
||||
|
||||
export async function getEventsByTerm(
|
||||
year: string,
|
||||
year: number,
|
||||
term: Term
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
return (await fs.readdir(path.join(EVENTS_PATH, year, term)))
|
||||
return (await fs.readdir(path.join(EVENTS_PATH, year.toString(), term)))
|
||||
.filter((name) => name.endsWith(".md"))
|
||||
.map((name) => name.slice(0, -".md".length));
|
||||
} catch {
|
||||
|
@ -108,22 +113,24 @@ export async function getEventsByTerm(
|
|||
}
|
||||
|
||||
export async function getUpcomingEvents(): Promise<Event[]> {
|
||||
const today = new Date();
|
||||
const currentYear = today.getFullYear();
|
||||
const currentTerm = Math.trunc(today.getMonth() / 4);
|
||||
const nextYear = currentTerm < 2 ? currentYear : currentYear + 1;
|
||||
const nextTerm = (currentTerm + 1) % 3;
|
||||
const terms: TermYear[] = [];
|
||||
|
||||
// Get events for the next two terms
|
||||
for (const termYear of getTermYear()) {
|
||||
if (terms.length >= 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
terms.push(termYear);
|
||||
}
|
||||
|
||||
const events: Event[] = (
|
||||
await Promise.all(
|
||||
[
|
||||
{ year: currentYear.toString(), term: currentTerm },
|
||||
{ year: nextYear.toString(), term: nextTerm },
|
||||
].map(async ({ year, term }) => {
|
||||
terms.map(async ({ year, term }) => {
|
||||
try {
|
||||
const eventsInTerm = await getEventsByTerm(year, TERMS[term]);
|
||||
const eventsInTerm = await getEventsByTerm(year, term);
|
||||
return await Promise.all(
|
||||
eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug))
|
||||
eventsInTerm.map((slug) => getEventBySlug(year, term, slug))
|
||||
);
|
||||
} catch (error) {
|
||||
return [];
|
||||
|
@ -162,12 +169,9 @@ export async function getAllEvents(): Promise<Event[]> {
|
|||
}
|
||||
|
||||
export async function getEventsPageProps({
|
||||
year,
|
||||
term,
|
||||
}: {
|
||||
year: string;
|
||||
term: Term;
|
||||
}): Promise<Props> {
|
||||
year,
|
||||
}: TermYear): Promise<Props> {
|
||||
const eventNames = await getEventsByTerm(year, term);
|
||||
|
||||
const events: Event[] = (
|
||||
|
@ -198,40 +202,43 @@ export async function getEventsPageProps({
|
|||
currentDate
|
||||
);
|
||||
|
||||
const current = getCurrentTerm();
|
||||
|
||||
const eventYears = await getEventYears();
|
||||
|
||||
const minYear = eventYears[0];
|
||||
const pastTerms: { year: string; term: Term }[] = [];
|
||||
let curPastYear = year;
|
||||
let curPastTerm = term;
|
||||
while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) {
|
||||
const pastTerm = getPastTerm(curPastYear, curPastTerm);
|
||||
curPastYear = pastTerm.year;
|
||||
curPastTerm = pastTerm.term;
|
||||
if ((await getEventsByTerm(curPastYear, curPastTerm)).length !== 0) {
|
||||
pastTerms.push(pastTerm);
|
||||
const pastTerms: TermYear[] = [];
|
||||
|
||||
for (const current of getTermYear(
|
||||
{ year, term },
|
||||
{ goBackwards: true, skipCurrent: true }
|
||||
)) {
|
||||
if (pastTerms.length >= 2 || current.year < minYear) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
|
||||
pastTerms.push(current);
|
||||
}
|
||||
}
|
||||
pastTerms.reverse();
|
||||
|
||||
const maxYear = eventYears[eventYears.length - 1];
|
||||
const futureTerms: { year: string; term: Term }[] = [];
|
||||
let curFutureYear = year;
|
||||
let curFutureTerm = term;
|
||||
while (
|
||||
parseInt(curFutureYear) <= parseInt(maxYear) &&
|
||||
futureTerms.length < 2
|
||||
) {
|
||||
const futureTerm = getFutureTerm(curFutureYear, curFutureTerm);
|
||||
curFutureYear = futureTerm.year;
|
||||
curFutureTerm = futureTerm.term;
|
||||
if ((await getEventsByTerm(curFutureYear, curFutureTerm)).length !== 0) {
|
||||
futureTerms.push(futureTerm);
|
||||
const futureTerms: TermYear[] = [];
|
||||
|
||||
for (const current of getTermYear(
|
||||
{ year, term },
|
||||
{ goBackwards: false, skipCurrent: true }
|
||||
)) {
|
||||
if (futureTerms.length >= 2 || maxYear < current.year) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
|
||||
futureTerms.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
const current = getCurrentTermYear();
|
||||
|
||||
return {
|
||||
year: year,
|
||||
term: term,
|
||||
|
@ -242,70 +249,3 @@ export async function getEventsPageProps({
|
|||
futureTerms: futureTerms,
|
||||
};
|
||||
}
|
||||
|
||||
export function getCurrentTerm(): { year: string; term: Term } {
|
||||
const today = new Date().toLocaleDateString("en-CA", {
|
||||
timeZone: "EST",
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const [year] = today.split("-");
|
||||
|
||||
let term = "";
|
||||
|
||||
if (`${year}-01-01` <= today) {
|
||||
term = "winter";
|
||||
}
|
||||
|
||||
if (`${year}-05-01` <= today) {
|
||||
term = "spring";
|
||||
}
|
||||
|
||||
if (`${year}-09-01` <= today) {
|
||||
term = "fall";
|
||||
}
|
||||
|
||||
if (!isTerm(term)) {
|
||||
throw new Error("Error setting the current term");
|
||||
}
|
||||
|
||||
return { year, term };
|
||||
}
|
||||
|
||||
function getPastTerm(year: string, term: Term): { year: string; term: Term } {
|
||||
const index = TERMS.indexOf(term);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error(`[getPastTerm] Not a valid term: "${term}" "${year}"`);
|
||||
}
|
||||
|
||||
return index === 0
|
||||
? {
|
||||
year: (parseInt(year) - 1).toString(),
|
||||
term: TERMS[TERMS.length - 1],
|
||||
}
|
||||
: {
|
||||
year: year,
|
||||
term: TERMS[index - 1],
|
||||
};
|
||||
}
|
||||
|
||||
function getFutureTerm(year: string, term: Term): { year: string; term: Term } {
|
||||
const index = TERMS.indexOf(term);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error(`[getFutureTerm] Not a valid term: "${term}" "${year}"`);
|
||||
}
|
||||
|
||||
return index === TERMS.length - 1
|
||||
? {
|
||||
year: (parseInt(year) + 1).toString(),
|
||||
term: TERMS[0],
|
||||
}
|
||||
: {
|
||||
year: year,
|
||||
term: TERMS[index + 1],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ export interface Member {
|
|||
program: string;
|
||||
}
|
||||
|
||||
export async function getMembers(year: string, term: Term): Promise<Member[]> {
|
||||
export async function getMembers(year: number, term: Term): Promise<Member[]> {
|
||||
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
||||
return dummyMembers;
|
||||
}
|
||||
|
|
39
lib/news.ts
39
lib/news.ts
|
@ -7,9 +7,16 @@ import truncateMarkdown from "markdown-truncate";
|
|||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
|
||||
import { isTerm, Term, TERMS } from "@/utils";
|
||||
|
||||
import { DATE_FORMAT, getLocalDateFromEST } from "../utils";
|
||||
import {
|
||||
isTerm,
|
||||
Term,
|
||||
TERMS,
|
||||
DATE_FORMAT,
|
||||
getLocalDateFromEST,
|
||||
TermYear,
|
||||
getTermYear,
|
||||
getCurrentTermYear,
|
||||
} from "@/utils";
|
||||
|
||||
export const NEWS_PATH = path.join("content", "news");
|
||||
|
||||
|
@ -96,23 +103,27 @@ export async function getNewsBySlug(
|
|||
}
|
||||
|
||||
export async function getRecentNews(): Promise<News[]> {
|
||||
const today = new Date();
|
||||
const currentYear = today.getFullYear();
|
||||
const currentTerm = Math.trunc(today.getMonth() / 4);
|
||||
const prevYear = currentTerm > 0 ? currentYear : currentYear - 1;
|
||||
const prevTerm = (currentTerm - 1 + 3) % 3;
|
||||
const terms: TermYear[] = [];
|
||||
|
||||
// Get news for the last two terms
|
||||
for (const termYear of getTermYear(getCurrentTermYear(), {
|
||||
goBackwards: true,
|
||||
})) {
|
||||
if (terms.length >= 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
terms.push(termYear);
|
||||
}
|
||||
|
||||
const news: News[] = (
|
||||
await Promise.all(
|
||||
[
|
||||
{ year: currentYear.toString(), term: currentTerm },
|
||||
{ year: prevYear.toString(), term: prevTerm },
|
||||
].map(async ({ year, term }) => {
|
||||
terms.map(async ({ year, term }) => {
|
||||
try {
|
||||
const newsInTerm = await getNewsByTerm(year, TERMS[term]);
|
||||
const newsInTerm = await getNewsByTerm(year.toString(), term);
|
||||
return await Promise.all(
|
||||
newsInTerm.map((slug) => {
|
||||
return getNewsBySlug(year, TERMS[term], slug, true);
|
||||
return getNewsBySlug(year.toString(), term, slug, true);
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
89
lib/team.ts
89
lib/team.ts
|
@ -5,12 +5,10 @@ import matter from "gray-matter";
|
|||
import { Client } from "ldapts";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
|
||||
import { getCurrentTerm } from "@/lib/events";
|
||||
import { capitalize } from "@/utils";
|
||||
import { capitalize, TermYear } from "@/utils";
|
||||
|
||||
const EXECS_PATH = path.join("content", "team", "execs");
|
||||
const FILETYPE = ".md";
|
||||
const { year, term } = getCurrentTerm();
|
||||
|
||||
const execPositions: { [position: string]: string } = {
|
||||
president: "President",
|
||||
|
@ -34,7 +32,54 @@ export interface Metadata {
|
|||
image: string;
|
||||
}
|
||||
|
||||
export async function getExecNamePosPairs() {
|
||||
export async function getExecs(termYear: TermYear) {
|
||||
const execNamePosPairs = await getExecNamePosPairs(termYear);
|
||||
|
||||
return await Promise.all(
|
||||
execNamePosPairs.map((namePosPair) =>
|
||||
getExec(namePosPair[0], namePosPair[1])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function getExec(name: string, pos: string) {
|
||||
let content, metadata;
|
||||
|
||||
try {
|
||||
const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`));
|
||||
({ content, data: metadata } = matter(raw));
|
||||
|
||||
const image = await getMemberImagePath(metadata.name as string);
|
||||
|
||||
return {
|
||||
content: await serialize(content),
|
||||
metadata: { ...metadata, image } as Metadata,
|
||||
};
|
||||
} catch (err) {
|
||||
// Capitalize the first letter of the first name and last name
|
||||
const firstName = capitalize(name.split("-")[0]);
|
||||
const lastName = capitalize(name.split("-")[1]);
|
||||
|
||||
const posName = execPositions[pos];
|
||||
content = "Coming Soon!";
|
||||
metadata = {
|
||||
name: `${firstName} ${lastName}`,
|
||||
role: `${posName}`,
|
||||
};
|
||||
|
||||
const image = await getMemberImagePath(metadata.name);
|
||||
|
||||
return {
|
||||
content: await serialize(content),
|
||||
metadata: { ...metadata, image } as Metadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getExecNamePosPairs({
|
||||
term,
|
||||
year,
|
||||
}: TermYear): Promise<[person: string, position: string][]> {
|
||||
if (process.env.USE_LDAP?.toLowerCase() !== "true") {
|
||||
return [["codey", "mascot"]];
|
||||
}
|
||||
|
@ -45,7 +90,7 @@ export async function getExecNamePosPairs() {
|
|||
|
||||
// position: name
|
||||
const execMembers: { [position: string]: string } = {};
|
||||
let formattedExec: [string, string][] = [];
|
||||
let formattedExec: [person: string, position: string][] = [];
|
||||
|
||||
try {
|
||||
await client.bind("", "");
|
||||
|
@ -89,40 +134,6 @@ export async function getExecNamePosPairs() {
|
|||
return formattedExec;
|
||||
}
|
||||
|
||||
export async function getExec(name: string, pos: string, convert = true) {
|
||||
let content, metadata;
|
||||
|
||||
try {
|
||||
const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`));
|
||||
({ content, data: metadata } = matter(raw));
|
||||
|
||||
const image = await getMemberImagePath(metadata.name as string);
|
||||
|
||||
return {
|
||||
content: convert ? await serialize(content) : content,
|
||||
metadata: { ...metadata, image } as Metadata,
|
||||
};
|
||||
} catch (err) {
|
||||
// Capitalize the first letter of the first name and last name
|
||||
const firstName = capitalize(name.split("-")[0]);
|
||||
const lastName = capitalize(name.split("-")[1]);
|
||||
|
||||
const posName = execPositions[pos];
|
||||
content = "Coming Soon!";
|
||||
metadata = {
|
||||
name: `${firstName} ${lastName}`,
|
||||
role: `${posName}`,
|
||||
};
|
||||
|
||||
const image = await getMemberImagePath(metadata.name);
|
||||
|
||||
return {
|
||||
content: convert ? await serialize(content) : content,
|
||||
metadata: { ...metadata, image } as Metadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getImage(imgPath: string) {
|
||||
try {
|
||||
await access(path.join("public", imgPath));
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-preset-env": "^7.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1075,7 +1076,7 @@
|
|||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
|
@ -2883,6 +2884,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json5": "^0.0.29",
|
||||
"json5": "^1.0.1",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
|
||||
|
@ -7153,15 +7166,29 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
|
||||
"integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json5": "^0.0.29",
|
||||
"json5": "^1.0.1",
|
||||
"json5": "^2.2.1",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
|
@ -8550,7 +8577,7 @@
|
|||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mdast": {
|
||||
|
@ -10025,6 +10052,18 @@
|
|||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json5": "^0.0.29",
|
||||
"json5": "^1.0.1",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -12975,15 +13014,22 @@
|
|||
"integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw=="
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
|
||||
"integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json5": "^0.0.29",
|
||||
"json5": "^1.0.1",
|
||||
"json5": "^2.2.1",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "npm run build:images && npm run build:web && npm run build:calendar",
|
||||
"build:images": "ts-node ./scripts/optimize-images",
|
||||
"build": "npm run build:images && npm run build:web && npm run build:calendar && npm run build:api",
|
||||
"build:web": "next build",
|
||||
"build:calendar": "ts-node ./scripts/generate-calendar",
|
||||
"build:images": "ts-node -r tsconfig-paths/register ./scripts/optimize-images",
|
||||
"build:calendar": "ts-node -r tsconfig-paths/register ./scripts/generate-calendar",
|
||||
"build:api": "ts-node -r tsconfig-paths/register ./scripts/api/members",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
"lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet",
|
||||
|
@ -56,6 +57,7 @@
|
|||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-preset-env": "^7.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,14 @@ import React from "react";
|
|||
import { Link } from "@/components/Link";
|
||||
import { Table } from "@/components/Table";
|
||||
import { Title } from "@/components/Title";
|
||||
import { getCurrentTerm } from "@/lib/events";
|
||||
import { getMembers, Member } from "@/lib/members";
|
||||
import { Term, capitalize } from "@/utils";
|
||||
import { Term, capitalize, getCurrentTermYear } from "@/utils";
|
||||
|
||||
import styles from "./members.module.css";
|
||||
|
||||
interface Props {
|
||||
members: Member[];
|
||||
year: string;
|
||||
year: number;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
|
@ -56,7 +55,7 @@ export default function Members(props: Props) {
|
|||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
const curTerm = getCurrentTerm();
|
||||
const curTerm = getCurrentTermYear();
|
||||
return {
|
||||
props: {
|
||||
year: curTerm.year,
|
||||
|
|
|
@ -10,11 +10,11 @@ import { TeamMember } from "@/components/TeamMember";
|
|||
import { TeamMemberCard } from "@/components/TeamMemberCard";
|
||||
import { Title } from "@/components/Title";
|
||||
import {
|
||||
getExec,
|
||||
getExecNamePosPairs,
|
||||
Metadata,
|
||||
getExecs,
|
||||
Metadata as TeamMemberData,
|
||||
getMemberImagePath,
|
||||
} from "@/lib/team";
|
||||
import { getCurrentTermYear } from "@/utils";
|
||||
|
||||
import designData from "../../content/team/design-team.json";
|
||||
import discordData from "../../content/team/discord-team.json";
|
||||
|
@ -31,88 +31,21 @@ import styles from "./team.module.css";
|
|||
|
||||
interface SerializedExec {
|
||||
content: MDXRemoteSerializeResult;
|
||||
metadata: Metadata;
|
||||
metadata: TeamMemberData;
|
||||
}
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
members: TeamMemberData[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
execs: SerializedExec[];
|
||||
design: Metadata[];
|
||||
discord: Metadata[];
|
||||
events: Metadata[];
|
||||
external: Metadata[];
|
||||
marketing: Metadata[];
|
||||
photography: Metadata[];
|
||||
representatives: Metadata[];
|
||||
website: Metadata[];
|
||||
systems: Metadata[];
|
||||
terminal: Metadata[];
|
||||
teams: Team[];
|
||||
}
|
||||
|
||||
export default function Team({
|
||||
execs,
|
||||
design,
|
||||
discord,
|
||||
events,
|
||||
external,
|
||||
marketing,
|
||||
photography,
|
||||
representatives,
|
||||
website,
|
||||
systems,
|
||||
terminal,
|
||||
}: Props) {
|
||||
const teams = [
|
||||
{
|
||||
id: "reps",
|
||||
name: "Community Representatives",
|
||||
members: representatives,
|
||||
},
|
||||
{
|
||||
id: "design",
|
||||
name: "Design Team",
|
||||
members: design,
|
||||
},
|
||||
{
|
||||
id: "discord",
|
||||
name: "Discord Team",
|
||||
members: discord,
|
||||
},
|
||||
{
|
||||
id: "events",
|
||||
name: "Events Team",
|
||||
members: events,
|
||||
},
|
||||
{
|
||||
id: "external",
|
||||
name: "External Affairs Team",
|
||||
members: external,
|
||||
},
|
||||
{
|
||||
id: "marketing",
|
||||
name: "Marketing Team",
|
||||
members: marketing,
|
||||
},
|
||||
{
|
||||
id: "photography",
|
||||
name: "Photography Team",
|
||||
members: photography,
|
||||
},
|
||||
{
|
||||
id: "website",
|
||||
name: "Web Committee",
|
||||
members: website,
|
||||
},
|
||||
{
|
||||
id: "system",
|
||||
name: "Systems Committee",
|
||||
members: systems,
|
||||
},
|
||||
{
|
||||
id: "terminal",
|
||||
name: "Terminal Committee",
|
||||
members: terminal,
|
||||
},
|
||||
];
|
||||
export default function Team({ execs, teams }: Props) {
|
||||
return (
|
||||
<>
|
||||
<Title>Team</Title>
|
||||
|
@ -122,16 +55,11 @@ export default function Team({
|
|||
<h1 className={styles.header}>Meet the Team!</h1>
|
||||
<div className={styles.nav}>
|
||||
<Link href="#execs">The Executives</Link>
|
||||
<Link href="#reps">Community Representatives</Link>
|
||||
<Link href="#design">Design</Link>
|
||||
<Link href="#discord">Discord</Link>
|
||||
<Link href="#events">Events</Link>
|
||||
<Link href="#external">External Affairs</Link>
|
||||
<Link href="#marketing">Marketing</Link>
|
||||
<Link href="#photography">Photography</Link>
|
||||
<Link href="#website">Web Committee</Link>
|
||||
<Link href="#system">Systems Committee</Link>
|
||||
<Link href="#terminal">Terminal Committee</Link>
|
||||
{teams.map((team) => (
|
||||
<Link href={`#${team.id}`} key={team.id}>
|
||||
{team.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<h2
|
||||
className={styles.subheading}
|
||||
|
@ -180,7 +108,7 @@ Team.Layout = function TeamLayout(props: { children: React.ReactNode }) {
|
|||
};
|
||||
|
||||
interface MembersProps {
|
||||
team: Metadata[];
|
||||
team: TeamMemberData[];
|
||||
}
|
||||
|
||||
function MembersList(props: MembersProps) {
|
||||
|
@ -193,93 +121,104 @@ function MembersList(props: MembersProps) {
|
|||
);
|
||||
}
|
||||
|
||||
type TeamMember = Omit<Metadata, "image"> & { image?: string };
|
||||
|
||||
async function getTeamWithImages(team: TeamMember[]) {
|
||||
return await Promise.all(
|
||||
team.map(async (member) => {
|
||||
const image = member.image ?? (await getMemberImagePath(member.name));
|
||||
return {
|
||||
...member,
|
||||
image,
|
||||
};
|
||||
})
|
||||
);
|
||||
async function getTeamWithImages(team: Team): Promise<Team> {
|
||||
return {
|
||||
...team,
|
||||
members: await Promise.all(
|
||||
team.members.map(async (member) => {
|
||||
const image = member.image ?? (await getMemberImagePath(member.name));
|
||||
return {
|
||||
...member,
|
||||
image,
|
||||
};
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function memberComparer(a: Metadata, b: Metadata) {
|
||||
function memberComparer(a: TeamMemberData, b: TeamMemberData) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
function sortTeam(team: Metadata[]): Metadata[] {
|
||||
const leads = team
|
||||
function sortTeamMembers(team: Team): Team {
|
||||
const leads = team.members
|
||||
.filter(({ role }) => role === "Team Lead")
|
||||
.sort(memberComparer);
|
||||
const general = team.filter(({ role }) => !role).sort(memberComparer);
|
||||
const others = team
|
||||
|
||||
const general = team.members.filter(({ role }) => !role).sort(memberComparer);
|
||||
|
||||
const others = team.members
|
||||
.filter(({ role }) => !!role && role !== "Team Lead")
|
||||
.sort(memberComparer);
|
||||
|
||||
return [...leads, ...general, ...others];
|
||||
return { ...team, members: [...leads, ...general, ...others] };
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
const execNamePosPairs = await getExecNamePosPairs();
|
||||
const execs = await getExecs(getCurrentTermYear());
|
||||
|
||||
const execs = (await Promise.all(
|
||||
execNamePosPairs.map((namePosPair) =>
|
||||
getExec(namePosPair[0], namePosPair[1])
|
||||
)
|
||||
)) as SerializedExec[];
|
||||
// Note that rawTeams do not contain image paths of members, even though
|
||||
// TypeScript thinks that it does. It's just to simplify some code.
|
||||
const rawTeams = [
|
||||
{
|
||||
id: "reps",
|
||||
name: "Community Representatives",
|
||||
members: repsData,
|
||||
},
|
||||
{
|
||||
id: "design",
|
||||
name: "Design",
|
||||
members: designData,
|
||||
},
|
||||
{
|
||||
id: "discord",
|
||||
name: "Discord",
|
||||
members: discordData,
|
||||
},
|
||||
{
|
||||
id: "events",
|
||||
name: "Events",
|
||||
members: eventsData,
|
||||
},
|
||||
{
|
||||
id: "external",
|
||||
name: "External Affairs",
|
||||
members: externalData,
|
||||
},
|
||||
{
|
||||
id: "marketing",
|
||||
name: "Marketing",
|
||||
members: marketingData,
|
||||
},
|
||||
{
|
||||
id: "photography",
|
||||
name: "Photography",
|
||||
members: photographyData,
|
||||
},
|
||||
{
|
||||
id: "website",
|
||||
name: "Web Committee",
|
||||
members: webData,
|
||||
},
|
||||
{
|
||||
id: "system",
|
||||
name: "Systems Committee",
|
||||
members: systemsData,
|
||||
},
|
||||
{
|
||||
id: "terminal",
|
||||
name: "Terminal Committee",
|
||||
members: terminalData,
|
||||
},
|
||||
] as Team[];
|
||||
|
||||
let [
|
||||
design,
|
||||
discord,
|
||||
events,
|
||||
external,
|
||||
marketing,
|
||||
photography,
|
||||
representatives,
|
||||
website,
|
||||
systems,
|
||||
terminal,
|
||||
] = await Promise.all([
|
||||
getTeamWithImages(designData),
|
||||
getTeamWithImages(discordData),
|
||||
getTeamWithImages(eventsData),
|
||||
getTeamWithImages(externalData),
|
||||
getTeamWithImages(marketingData),
|
||||
getTeamWithImages(photographyData),
|
||||
getTeamWithImages(repsData),
|
||||
getTeamWithImages(webData),
|
||||
getTeamWithImages(systemsData),
|
||||
getTeamWithImages(terminalData),
|
||||
]);
|
||||
|
||||
design = sortTeam(design);
|
||||
discord = sortTeam(discord);
|
||||
events = sortTeam(events);
|
||||
external = sortTeam(external);
|
||||
marketing = sortTeam(marketing);
|
||||
representatives = sortTeam(representatives);
|
||||
photography = sortTeam(photography);
|
||||
website = sortTeam(website);
|
||||
systems = sortTeam(systems);
|
||||
terminal = sortTeam(terminal);
|
||||
const teamsWithImages = await Promise.all(rawTeams.map(getTeamWithImages));
|
||||
const teamsAfterSorting = teamsWithImages.map(sortTeamMembers);
|
||||
|
||||
return {
|
||||
props: {
|
||||
execs,
|
||||
design,
|
||||
discord,
|
||||
events,
|
||||
external,
|
||||
marketing,
|
||||
photography,
|
||||
representatives,
|
||||
website,
|
||||
systems,
|
||||
terminal,
|
||||
teams: teamsAfterSorting,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -63,7 +63,11 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
|||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { year, term, event } = context.params!;
|
||||
return {
|
||||
props: { year, term, event: await getEventBySlug(year, term, event) },
|
||||
props: {
|
||||
year,
|
||||
term,
|
||||
event: await getEventBySlug(parseInt(year), term, event),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -77,7 +81,7 @@ export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
|||
termsInYear.map(async (term) => {
|
||||
const eventsInTerm = await getEventsByTerm(year, term);
|
||||
return eventsInTerm.map((event) => ({
|
||||
year,
|
||||
year: year.toString(),
|
||||
term,
|
||||
event,
|
||||
}));
|
||||
|
|
|
@ -10,22 +10,22 @@ import { MiniEventCard } from "@/components/MiniEventCard";
|
|||
import { Title } from "@/components/Title";
|
||||
import {
|
||||
Event,
|
||||
getEventsPageProps,
|
||||
getEventYears,
|
||||
getEventTermsByYear,
|
||||
getEventsPageProps,
|
||||
} from "@/lib/events";
|
||||
import { capitalize, Term } from "@/utils";
|
||||
import { capitalize, Term, TermYear } from "@/utils";
|
||||
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export interface Props {
|
||||
year: string;
|
||||
year: number;
|
||||
term: Term;
|
||||
pastEvents: Event[];
|
||||
futureEvents: Event[];
|
||||
isCurrentTerm: boolean;
|
||||
pastTerms: { year: string; term: Term }[];
|
||||
futureTerms: { year: string; term: Term }[];
|
||||
pastTerms: TermYear[];
|
||||
futureTerms: TermYear[];
|
||||
}
|
||||
|
||||
export default function TermPage(props: Props) {
|
||||
|
@ -61,7 +61,7 @@ export default function TermPage(props: Props) {
|
|||
<HeaderLink
|
||||
{...link}
|
||||
isCurrentTerm={link.year === props.year && link.term === props.term}
|
||||
key={link.year + link.term}
|
||||
key={`${link.year}${link.term}`}
|
||||
/>
|
||||
))}
|
||||
<Link href="/events/archive">Archive</Link>
|
||||
|
@ -129,7 +129,7 @@ export default function TermPage(props: Props) {
|
|||
}
|
||||
|
||||
function HeaderLink(props: {
|
||||
year: string;
|
||||
year: number;
|
||||
term: Term;
|
||||
isCurrentTerm?: boolean;
|
||||
}) {
|
||||
|
@ -151,7 +151,14 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
|||
context
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return { props: await getEventsPageProps(context.params!) };
|
||||
const params = context.params!;
|
||||
|
||||
return {
|
||||
props: await getEventsPageProps({
|
||||
year: parseInt(params.year),
|
||||
term: params.term,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||
|
@ -161,7 +168,7 @@ export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
|||
years.map(async (year) => {
|
||||
const terms = await getEventTermsByYear(year);
|
||||
return terms.map((curTerm) => ({
|
||||
params: { year: year, term: curTerm },
|
||||
params: { year: year.toString(), term: curTerm },
|
||||
}));
|
||||
})
|
||||
)
|
||||
|
|
|
@ -47,7 +47,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
|||
return {
|
||||
props: {
|
||||
year: year,
|
||||
terms: await getEventTermsByYear(year),
|
||||
terms: await getEventTermsByYear(parseInt(year)),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -55,7 +55,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
|
|||
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||
const years = await getEventYears();
|
||||
const paths = years.map((curYear) => ({
|
||||
params: { year: curYear },
|
||||
params: { year: curYear.toString() },
|
||||
}));
|
||||
return {
|
||||
paths: paths,
|
||||
|
|
|
@ -9,7 +9,7 @@ export const getStaticProps: GetStaticProps<Props> = async () => {
|
|||
const years = (await getEventYears()).reverse();
|
||||
const yearsWithTerms = await Promise.all(
|
||||
years.map(async (year) => ({
|
||||
year,
|
||||
year: year.toString(),
|
||||
terms: (await getEventTermsByYear(year)).reverse(),
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { GetStaticProps } from "next";
|
||||
|
||||
import { getCurrentTerm, getEventsPageProps } from "@/lib/events";
|
||||
import { getEventsPageProps } from "@/lib/events";
|
||||
import { getCurrentTermYear } from "@/utils";
|
||||
|
||||
import TermPage, { Props } from "./[year]/[term]";
|
||||
|
||||
export default TermPage;
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
return { props: await getEventsPageProps(getCurrentTerm()) };
|
||||
return { props: await getEventsPageProps(getCurrentTermYear()) };
|
||||
};
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
a3thakra marked this conversation as resolved
|
||||
"title": "Member list",
|
||||
"description": "List of all current members of the Computer Science Club of the University of Waterloo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"program": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { writeFile } from "fs/promises";
|
||||
|
||||
import { getMembers } from "@/lib/members";
|
||||
import { getCurrentTermYear } from "@/utils";
|
||||
|
||||
async function createMembersApi() {
|
||||
const { term, year } = getCurrentTermYear();
|
||||
const members = await getMembers(year, term);
|
||||
|
||||
const result = {
|
||||
$schema: "https://json-schema.org/draft/2020-12/schema",
|
||||
$id: "schema/members.json",
|
||||
members,
|
||||
};
|
||||
|
||||
await writeFile("public/api/members.json", JSON.stringify(result));
|
||||
a3thakra marked this conversation as resolved
a258wang
commented
Just wanted to note, I think NextJS has some kind of API Routing thing which maps files in (This probably isn't relevant though, as we are aiming to keep the site static so there's no reason to be using the API Routing shenanigans.) Just wanted to note, I think NextJS has some kind of API Routing thing which maps files in `pages/api` to `/api/*` - I wonder how that would interact with us hosting a static file like this at `/api/*`?
(This probably isn't relevant though, as we are aiming to keep the site static so there's no reason to be using the API Routing shenanigans.)
https://nextjs.org/docs/api-routes/introduction
a3thakra
commented
yep, that one is only for a non-static website. yep, that one is only for a non-static website.
a3thakra
commented
I think it's okay for now, even if we add nextjs api later on, it should still work. I think it's okay for now, even if we add nextjs api later on, it should still work.
|
||||
}
|
||||
|
||||
void createMembersApi();
|
|
@ -1,60 +0,0 @@
|
|||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { DATE_FORMAT } from "@/utils";
|
||||
|
||||
/*
|
||||
Note:
|
||||
This script will not work for events by default anymore, since events now have startDate instead of endDate
|
||||
*/
|
||||
import {
|
||||
getEventsByTerm,
|
||||
getEventTermsByYear,
|
||||
getEventYears,
|
||||
} from "../lib/events";
|
||||
import {
|
||||
getNewsByTerm,
|
||||
getNewsTermsByYear,
|
||||
getNewsYears,
|
||||
NEWS_PATH,
|
||||
} from "../lib/news";
|
||||
const EVENTS_PATH = path.join("content", "events");
|
||||
|
||||
export async function main() {
|
||||
for (const year of await getEventYears()) {
|
||||
for (const term of await getEventTermsByYear(year)) {
|
||||
for (const slug of await getEventsByTerm(year, term)) {
|
||||
const filePath = path.join(EVENTS_PATH, year, term, `${slug}.md`);
|
||||
const file = await fs.readFile(filePath, "utf-8");
|
||||
|
||||
await fs.writeFile(filePath, replaceDate(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const year of await getNewsYears()) {
|
||||
for (const term of await getNewsTermsByYear(year)) {
|
||||
for (const slug of await getNewsByTerm(year, term)) {
|
||||
const filePath = path.join(NEWS_PATH, year, term, `${slug}.md`);
|
||||
const file = await fs.readFile(filePath, "utf-8");
|
||||
|
||||
await fs.writeFile(filePath, replaceDate(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replaceDate(file: string) {
|
||||
const lines = file.split("\n");
|
||||
const dateLineIdx = lines.findIndex((line) => line.startsWith("date: "));
|
||||
const dateLine = lines[dateLineIdx];
|
||||
const date = new Date(dateLine.slice("date: ".length + 1, -1));
|
||||
|
||||
lines[dateLineIdx] = `date: '${format(date, DATE_FORMAT)}'`;
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
void main();
|
|
@ -4,7 +4,7 @@ import path from "path";
|
|||
import { addHours } from "date-fns";
|
||||
import ical, { ICalCalendarMethod } from "ical-generator";
|
||||
|
||||
import { getAllEvents } from "../lib/events";
|
||||
import { getAllEvents } from "@/lib/events";
|
||||
|
||||
export async function generateCalendar() {
|
||||
const events = await getAllEvents();
|
||||
|
|
65
utils.ts
65
utils.ts
|
@ -21,3 +21,68 @@ export function getLocalDateFromEST(date: Date): Date {
|
|||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
);
|
||||
}
|
||||
|
||||
export interface TermYear {
|
||||
term: Term;
|
||||
year: number;
|
||||
}
|
||||
|
||||
export interface GetTermYearOptions {
|
||||
goBackwards?: boolean;
|
||||
skipCurrent?: boolean;
|
||||
}
|
||||
|
||||
export function* getTermYear(
|
||||
a3thakra marked this conversation as resolved
a258wang
commented
This is very cool, did not know it was a thing 👀 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* This is very cool, did not know it was a thing 👀
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
|
||||
start?: number | TermYear,
|
||||
{ goBackwards = false, skipCurrent = false }: GetTermYearOptions = {}
|
||||
) {
|
||||
const allTerms = [...TERMS];
|
||||
|
||||
if (goBackwards) {
|
||||
allTerms.reverse();
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const todayYear = today.getFullYear();
|
||||
const todayTerm = TERMS[Math.trunc(today.getMonth() / 4)];
|
||||
|
||||
start ??= { term: todayTerm, year: todayYear };
|
||||
|
||||
if (typeof start === "number") {
|
||||
start = { term: allTerms[0], year: start };
|
||||
}
|
||||
|
||||
let currentYear = start.year;
|
||||
while (0 <= currentYear && currentYear <= Number.MAX_SAFE_INTEGER) {
|
||||
for (const currentTerm of allTerms) {
|
||||
if (
|
||||
currentYear === start.year &&
|
||||
allTerms.indexOf(currentTerm) < allTerms.indexOf(start.term)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
skipCurrent &&
|
||||
currentYear === start.year &&
|
||||
currentTerm === start.term
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield { term: currentTerm, year: currentYear };
|
||||
}
|
||||
|
||||
currentYear = currentYear + (goBackwards ? -1 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentTermYear() {
|
||||
const result = getTermYear().next();
|
||||
|
||||
if (result.done === true) {
|
||||
throw new Error("Cannot get current term. Iterator is done.");
|
||||
}
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Dropping this here for personal reference: https://json-schema.org/