diff --git a/components/NewsCard.module.css b/components/NewsCard.module.css
index fd83dd67..735ab630 100644
--- a/components/NewsCard.module.css
+++ b/components/NewsCard.module.css
@@ -3,6 +3,7 @@
max-width: calc(524rem / 16);
background-color: var(--primary-background);
border-radius: calc(20rem / 16);
+ margin-bottom: 1rem;
}
.fit.card {
@@ -30,6 +31,7 @@
padding: 0;
max-width: unset;
background-color: transparent;
+ border-radius: 0;
}
.date {
diff --git a/components/NewsCard.tsx b/components/NewsCard.tsx
index 16a31be3..5aac03e8 100644
--- a/components/NewsCard.tsx
+++ b/components/NewsCard.tsx
@@ -1,11 +1,14 @@
import React, { ReactNode } from "react";
+import { Link } from "./Link";
+
import styles from "./NewsCard.module.css";
interface NewsCardProps {
date: Date;
author: string;
children: ReactNode;
+ permalink: string;
fit?: boolean;
}
@@ -13,7 +16,8 @@ export const NewsCard: React.FC = ({
date,
author,
children,
- fit = false,
+ permalink,
+ fit = false, // resizes the article to fit the parent container if it's not a mini card
}) => {
const classes = fit ? [styles.card, styles.fit] : [styles.card];
@@ -30,6 +34,11 @@ export const NewsCard: React.FC = ({
{author}
{children}
+ {!fit && (
+
+ Learn more
+
+ )}
);
};
diff --git a/lib/news.ts b/lib/news.ts
index 69812ec5..1f3769a5 100644
--- a/lib/news.ts
+++ b/lib/news.ts
@@ -3,6 +3,7 @@ import path from "path";
import { parse } from "date-fns";
import matter from "gray-matter";
+import truncateMarkdown from "markdown-truncate";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
@@ -15,6 +16,7 @@ export const NEWS_PATH = path.join("content", "news");
export interface Metadata {
author: string;
date: string;
+ permalink: string;
}
export interface News {
@@ -40,6 +42,15 @@ export async function getNewsTermsByYear(year: string): Promise {
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
}
+export async function getNewsDateByTerm(
+ year: string,
+ term: Term
+): Promise {
+ return (await getNewsByTerm(year, term)).map(
+ (news) => news.split("-").slice(0, 3).join("-") // retrieves date from filename
+ );
+}
+
export async function getNewsByTerm(
year: string,
term: Term
@@ -56,13 +67,21 @@ export async function getNewsByTerm(
export async function getNewsBySlug(
year: string,
term: Term,
- slug: string
+ slug: string,
+ shortened = false
): Promise {
const raw = await fs.readFile(
path.join(NEWS_PATH, year, term, `${slug}.md`),
"utf-8"
);
- const { content, data: metadata } = matter(raw);
+ const { content: rawContent, data: metadata } = matter(raw);
+ const slugDate = slug.split("-").slice(0, 3).join("-");
+ const content: string = shortened
+ ? truncateMarkdown(rawContent, {
+ limit: 150,
+ ellipsis: true,
+ })
+ : rawContent;
return {
content: await serialize(content),
@@ -71,6 +90,7 @@ export async function getNewsBySlug(
date: getLocalDateFromEST(
parse(metadata.date, DATE_FORMAT, new Date())
).toString(),
+ permalink: `/news/${year}/${term}/${slugDate}`,
} as Metadata,
};
}
@@ -91,7 +111,9 @@ export async function getRecentNews(): Promise {
try {
const newsInTerm = await getNewsByTerm(year, TERMS[term]);
return await Promise.all(
- newsInTerm.map((slug) => getNewsBySlug(year, TERMS[term], slug))
+ newsInTerm.map((slug) => {
+ return getNewsBySlug(year, TERMS[term], slug, true);
+ })
);
} catch (error) {
return [];
diff --git a/package-lock.json b/package-lock.json
index 02ae0943..eca67f2e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"fs-extra": "^10.0.0",
"image-size": "^1.0.0",
"ldapts": "^3.1.0",
+ "markdown-truncate": "^1.0.4",
"next": "11.0.1",
"next-mdx-remote": "3.0.4",
"prettier": "^2.3.0",
@@ -4719,6 +4720,11 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/markdown-truncate": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-truncate/-/markdown-truncate-1.0.4.tgz",
+ "integrity": "sha512-sojm7PWqbgIfUoSVyKyyUN3glbwEgfXqL75HYvGjBHQuCkNaEHglyYt3biEIZG81H/CxhTtf2DEu4tLGWoK65Q=="
+ },
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -11576,6 +11582,11 @@
"repeat-string": "^1.0.0"
}
},
+ "markdown-truncate": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-truncate/-/markdown-truncate-1.0.4.tgz",
+ "integrity": "sha512-sojm7PWqbgIfUoSVyKyyUN3glbwEgfXqL75HYvGjBHQuCkNaEHglyYt3biEIZG81H/CxhTtf2DEu4tLGWoK65Q=="
+ },
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
diff --git a/package.json b/package.json
index 1050c9f3..cbb202ab 100644
--- a/package.json
+++ b/package.json
@@ -24,9 +24,10 @@
"@squoosh/lib": "^0.4.0",
"date-fns": "^2.11.1",
"date-fns-tz": "^1.1.6",
- "ldapts": "^3.1.0",
"fs-extra": "^10.0.0",
"image-size": "^1.0.0",
+ "ldapts": "^3.1.0",
+ "markdown-truncate": "^1.0.4",
"next": "11.0.1",
"next-mdx-remote": "3.0.4",
"prettier": "^2.3.0",
diff --git a/pages/index.tsx b/pages/index.tsx
index e6ced0e6..dcd01727 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -20,7 +20,7 @@ import styles from "./index.module.css";
interface Props {
moreEvents: boolean; // true if there are more than 2 upcoming events
events: Event[]; // array of 0 - 2 events
- news: News;
+ news: News[]; // array of 3 news items
}
export default function Home(props: Props) {
@@ -86,14 +86,17 @@ export default function Home(props: Props) {
See past news here
- {
-
-
-
- }
+ {props.news.length > 0
+ ? props.news.map((news, idx) => (
+
+
+
+ ))
+ : null}
@@ -116,7 +119,7 @@ export const getStaticProps: GetStaticProps = async () => {
props: {
moreEvents: upcomingEvents.length > 2,
events: upcomingEvents.slice(0, 2),
- news: recentNews[0],
+ news: recentNews.slice(0, 3),
},
};
};
diff --git a/pages/news/[year]/[term]/[date].module.css b/pages/news/[year]/[term]/[date].module.css
new file mode 100644
index 00000000..e752a424
--- /dev/null
+++ b/pages/news/[year]/[term]/[date].module.css
@@ -0,0 +1,8 @@
+.page {
+ padding-bottom: calc(30rem / 16);
+}
+
+.page > h1 {
+ padding-bottom: calc(16rem / 16);
+ border-bottom: calc(1rem / 16) solid var(--primary-heading);
+}
diff --git a/pages/news/[year]/[term]/[date].tsx b/pages/news/[year]/[term]/[date].tsx
new file mode 100644
index 00000000..65fa47a1
--- /dev/null
+++ b/pages/news/[year]/[term]/[date].tsx
@@ -0,0 +1,108 @@
+import { ParsedUrlQuery } from "querystring";
+
+import { GetStaticPaths, GetStaticProps } from "next";
+import { MDXRemote } from "next-mdx-remote";
+import React from "react";
+
+import { NewsCard } from "@/components/NewsCard";
+import {
+ ShapesConfig,
+ defaultGetShapesConfig,
+ GetShapesConfig,
+} from "@/components/ShapesBackground";
+import { Title } from "@/components/Title";
+import {
+ getNewsBySlug,
+ getNewsByTerm,
+ getNewsTermsByYear,
+ getNewsDateByTerm,
+ getNewsYears,
+ News,
+} from "@/lib/news";
+import { Term } from "@/utils";
+
+import styles from "./[date].module.css";
+
+interface Props {
+ year: string;
+ term: Term;
+ news: News[];
+}
+
+export default function DateNews({ news }: Props) {
+ const date = new Date(news[0].metadata.date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+ return (
+
+
{["News", `${date}`]}
+ News: {date}
+ {news.map(({ content, metadata }, idx) => (
+
+
+
+ ))}
+
+ );
+}
+
+DateNews.getShapesConfig = ((width, height) => {
+ return window.innerWidth <= 768
+ ? ({} as ShapesConfig)
+ : defaultGetShapesConfig(width, height);
+}) as GetShapesConfig;
+
+export const getStaticProps: GetStaticProps = async (
+ context
+) => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const { year, term, date } = context.params!;
+ const slugs = (await getNewsByTerm(year, term)).filter((slug) =>
+ slug.includes(date)
+ );
+
+ const news = await Promise.all(
+ slugs.map((slug) => getNewsBySlug(year, term, slug))
+ );
+ // Reverse so that we are displaying the most recent news
+ // of term first
+ return { props: { year, term, news: news.reverse() } };
+};
+
+interface Params extends ParsedUrlQuery {
+ year: string;
+ term: Term;
+ date: string;
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const years = await getNewsYears();
+ const terms = await Promise.all(
+ years.map(async (year) => {
+ const termsInYear = await getNewsTermsByYear(year);
+ return termsInYear.map((term) => ({ year, term }));
+ })
+ );
+ const dates = await Promise.all(
+ terms.map(async (termInYear) => {
+ const datesInTerm: Params[] = [];
+ for (const { year, term } of termInYear) {
+ const dates = await getNewsDateByTerm(year, term);
+ dates.map((date) => datesInTerm.push({ year, term, date }));
+ }
+ return datesInTerm.flat();
+ })
+ );
+
+ return {
+ paths: dates.flat().map((params) => ({ params })),
+ fallback: false,
+ };
+};
diff --git a/types.d.ts b/types.d.ts
index 1a8e2879..17bdcfd8 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -16,3 +16,10 @@ declare module "*.md" {
export default ReactComponent;
}
+
+declare module "markdown-truncate" {
+ export default function truncateMarkdown(
+ inputText: string,
+ options: { limit: number; ellipsis: boolean }
+ ): string;
+}