Skip to content

Commit

Permalink
refactor: make blog and docs work the same
Browse files Browse the repository at this point in the history
  • Loading branch information
PupoSDC committed Jan 27, 2024
1 parent 81a1788 commit 6be5d26
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 173 deletions.
9 changes: 8 additions & 1 deletion apps/next-app/pages/_document.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ export default class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<Head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc"
crossOrigin="anonymous"
/>
</Head>
<body>
{getInitColorSchemeScript()}
<Main />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import * as fs from "node:fs/promises";
import path from "node:path";
import { useEffect } from "react";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import { default as AirplaneTicketIcon } from "@mui/icons-material/AirplaneTicket";
import { default as ChevronRightIcon } from "@mui/icons-material/ChevronRight";
import { default as FlightTakeoffIcon } from "@mui/icons-material/FlightTakeoff";
import { default as KeyboardArrowLeftIcon } from "@mui/icons-material/KeyboardArrowLeft";
import { default as StyleIcon } from "@mui/icons-material/Style";
import { Box, Link, Divider, Typography, GlobalStyles, Stack } from "@mui/joy";
import { Box, Link, Divider, Typography, Stack } from "@mui/joy";
import { getRequiredParam } from "libs/react/containers/src/wraper";
import { DateTime } from "luxon";
import { z } from "zod";
import {
AppHead,
BackgroundFadedImage,
BlogPostChip,
Markdown,
MarkdownClientDemo,
ModuleSelectionButton,
markdownComponents,
} from "@chair-flight/react/components";
import {
AnnexSearch,
Expand All @@ -28,45 +24,22 @@ import {
} from "@chair-flight/react/containers";
import { useUserVoyage } from "@chair-flight/react/containers";
import { staticHandler, staticPathsHandler } from "@chair-flight/trpc/server";
import type { AppRouterOutput } from "@chair-flight/trpc/server";
import type { NextPage } from "next";
import type { MDXRemoteSerializeResult } from "next-mdx-remote";

const metaSchema = z.object({
title: z.string(),
description: z.string(),
author: z.string(),
date: z.string(),
imageUrl: z.string().optional(),
tag: z.enum(["Technical", "Feature", "Content"]),
});
type PageProps = AppRouterOutput["blog"]["getBlogPost"]["post"];
type PageParams = { postId: string };

type PageProps = {
meta: z.infer<typeof metaSchema>;
mdxSource: MDXRemoteSerializeResult;
};

type PageParams = {
articleName: string;
};

export const Page: NextPage<PageProps> = ({ mdxSource, meta }) => {
export const Page: NextPage<PageProps> = (props) => {
useEffect(() => useUserVoyage.markBlogAsVisited, []);

return (
<LayoutPublic background={<BackgroundFadedImage img="article" />}>
<GlobalStyles
styles={{
main: {
"h1, h2, h3, h4, h5": { marginTop: "1em" },
},
}}
/>
<AppHead
title={meta.title}
linkTitle={meta.title}
linkDescription={meta.description}
title={props.title}
linkTitle={props.title}
linkDescription={props.description}
/>

<Link
sx={{ flex: 0, mr: "auto", pb: 2 }}
color="primary"
Expand All @@ -77,32 +50,31 @@ export const Page: NextPage<PageProps> = ({ mdxSource, meta }) => {
/>
<Typography
level="body-sm"
children={DateTime.fromISO(meta.date).toFormat("dd LLL yyyy")}
children={DateTime.fromISO(props.date).toFormat("dd LLL yyyy")}
/>
<Typography
level="h2"
component="h1"
sx={{ fontWeight: "bold" }}
children={meta.title}
children={props.title}
/>
<Divider sx={{ width: "100%", mb: 1 }} />
<Box sx={{ mb: 4 }}>
<BlogPostChip
tag={meta.tag}
tag={props.tag}
variant="soft"
slotProps={{
action: {
component: Link,
href: `/articles/blog?tag=${meta.tag}`,
href: props.tagHref,
},
}}
/>
</Box>

<MDXRemote
{...mdxSource}
<Markdown
document={props.mdxContent}
components={{
...markdownComponents,
Stack,
Link,
AnnexSearch,
Expand All @@ -127,37 +99,20 @@ export const Page: NextPage<PageProps> = ({ mdxSource, meta }) => {
);
};

const MATCH_CODE_BLOCKS = /```tsx eval((?:.|\n)*?)```/g;
const RELATIVE_PATH_TO_BLOG = "./public/content/content-blog/";

export const getStaticProps = staticHandler<PageProps, PageParams>(
async ({ params, helper }) => {
const articleName = getRequiredParam(params, "articleName");
const postId = getRequiredParam(params, "postId");
const { post } = await helper.blog.getBlogPost.fetch({ postId });
await LayoutPublic.getData({ helper, params });

const file = path.join(
process.cwd(),
RELATIVE_PATH_TO_BLOG,
articleName,
"page.md",
);

const source = await fs.readFile(file);
const sourceString = source.toString().replaceAll(MATCH_CODE_BLOCKS, "$1");
const opts = { parseFrontmatter: true };
const mdxSource = await serialize(sourceString, opts);
const meta = metaSchema.parse(mdxSource.frontmatter);
return { props: { mdxSource, meta } };
return { props: post };
},
fs,
);

export const getStaticPaths = staticPathsHandler<PageParams>(
async ({ helper }) => {
const { meta } = await helper.blog.getBlogPostsMeta.fetch();
const paths = meta.map((meta) => ({
params: { articleName: meta.filename },
}));
const paths = meta.map((meta) => ({ params: { postId: meta.filename } }));
return { fallback: false, paths };
},
fs,
Expand Down
6 changes: 2 additions & 4 deletions apps/next-app/pages/blog/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@ import {
} from "@chair-flight/react/components";
import { LayoutPublic, useUserVoyage } from "@chair-flight/react/containers";
import { staticHandler } from "@chair-flight/trpc/server";
import type { BlogMeta } from "@chair-flight/core/blog";
import type { AppRouterOutput } from "@chair-flight/trpc/server";

export type PageProps = {
meta: BlogMeta[];
};
export type PageProps = AppRouterOutput["blog"]["getBlogPostsMeta"];

const Page: FunctionComponent<PageProps> = ({ meta }) => {
useEffect(() => useUserVoyage.markBlogAsVisited, []);
Expand Down
1 change: 1 addition & 0 deletions libs/base/types/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./types.d.ts";

export * from "./lib/blog";
export * from "./lib/analytics";
export * from "./lib/ids";
export * from "./lib/question";
Expand Down
10 changes: 10 additions & 0 deletions libs/base/types/src/lib/blog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type BlogPost = {
title: string;
filename: string;
description: string;
author: string;
date: string;
imageUrl: string | null;
content: string;
tag: "Technical" | "Feature" | "Content";
};
16 changes: 10 additions & 6 deletions libs/core/blog/executors/compile/executor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { parse } from "yaml";
import { blogMetaSchema } from "../../src";
import type { BlogMeta } from "../../src";
import { blogPostSchema } from "../../src";
import type { BlogPost } from "@chair-flight/base/types";
import type { ExecutorContext } from "@nx/devkit";

type ExecutorOptions = Record<string, never>;
Expand All @@ -28,16 +28,20 @@ const runExecutor = async (_: ExecutorOptions, context: ExecutorContext) => {
const outputBlogMeta = path.join(outputDir, "meta.json");

const posts = await fs.readdir(blogPostsFolder);
const parsedPosts: BlogMeta[] = [];
const parsedPosts: BlogPost[] = [];

for (const post of posts) {
/** i.e.: `libs/content/blog/posts/001-post/page.md` */
const postPage = path.join(blogPostsFolder, post, "page.md");
const source = (await fs.readFile(postPage)).toString();
const match = MATTER_REGEX.exec(source);
const matter = match ? parse(match[1]) : {};
matter.filename = post;
const meta = blogMetaSchema.parse(matter);
if (!match) throw new Error(`Missing frontMatter for ${post}`);
const data = parse(match[1]);
const content = source.split("\n---").slice(1).join().trim();
data.filename = post;
data.content = content;
data.imageUrl ??= null;
const meta = blogPostSchema.parse(data);
parsedPosts.push(meta);
}

Expand Down
21 changes: 15 additions & 6 deletions libs/core/blog/src/blog.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import { getUrlPathOnServer } from "@chair-flight/base/env";
import type { BlogMeta } from "./meta-schema";
import { NotFoundError } from "@chair-flight/base/errors";
import type { BlogPost } from "@chair-flight/base/types";

type ReadFile = (path: string, string: "utf-8") => Promise<string>;

interface IBlog {
getAllPostsMeta: () => Promise<BlogMeta[]>;
getAllPosts: () => Promise<BlogPost[]>;
getPost: (postId: string) => Promise<BlogPost>;
preloadForStaticRender: (args: { readFile: ReadFile }) => Promise<void>;
}

export class Blog implements IBlog {
private postMeta: BlogMeta[] | undefined = undefined;
private postMeta: BlogPost[] | undefined = undefined;

async getAllPostsMeta() {
async getAllPosts() {
if (!this.postMeta) {
const urlPath = getUrlPathOnServer();
const blogPath = `/content/content-blog`;
const baseApiPath = `${urlPath}${blogPath}`;
const apiPath = `${baseApiPath}/meta.json`;
const response = await fetch(apiPath);
this.postMeta = (await response.json()) as BlogMeta[];
this.postMeta = (await response.json()) as BlogPost[];
}
return this.postMeta;
}

async getPost(docId: string) {
const allPosts = await this.getAllPosts();
const post = allPosts.find((p) => p.filename === docId);
if (!post) throw new NotFoundError();
return post;
}

async preloadForStaticRender({ readFile }: { readFile: ReadFile }) {
const cwd = process.cwd();
const appPath = "/apps/next-app";
Expand All @@ -32,7 +41,7 @@ export class Blog implements IBlog {
`/public/content/content-blog/meta.json`,
].join("");
const file = JSON.parse(await readFile(path, "utf-8"));
this.postMeta = file as BlogMeta[];
this.postMeta = file as BlogPost[];
}
}

Expand Down
3 changes: 1 addition & 2 deletions libs/core/blog/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { blog } from "./blog";
export { blogMetaSchema } from "./meta-schema";
export { blogPostSchema } from "./meta-schema";

export type { Blog } from "./blog";
export type { BlogMeta } from "./meta-schema";
8 changes: 4 additions & 4 deletions libs/core/blog/src/meta-schema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { z } from "zod";
import type { BlogPost } from "@chair-flight/base/types";

export const blogMetaSchema = z.object({
export const blogPostSchema: z.ZodSchema<BlogPost> = z.object({
title: z.string(),
filename: z.string(),
description: z.string(),
author: z.string(),
date: z.string(),
imageUrl: z.string().optional(),
imageUrl: z.string().nullable(),
content: z.string(),
tag: z.enum(["Technical", "Feature", "Content"]),
});

export type BlogMeta = z.infer<typeof blogMetaSchema>;
2 changes: 1 addition & 1 deletion libs/react/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export * from "./hooks/use-window-resize";
export * from "./image-viewer/image-viewer";
export * from "./input-slider";
export * from "./learning-objectives-list";
export * from "./markdown";
export * from "./markdown-client";
export * from "./markdown-components";
export * from "./module-selection-button";
export * from "./nested-checkbox-select";
export * from "./question-list";
Expand Down
1 change: 0 additions & 1 deletion libs/react/components/src/markdown-components/index.ts

This file was deleted.

2 changes: 2 additions & 0 deletions libs/react/components/src/markdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Markdown } from "./markdown";
export type { MarkdownProps } from "./markdown";
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Box, Divider, Link, Typography } from "@mui/joy";
import type { MDXRemoteProps } from "next-mdx-remote";
import type { MDXProvider } from "@mdx-js/react";
import type { ComponentProps } from "react";

export const markdownComponents: MDXRemoteProps["components"] = {
export type MdxComponents = ComponentProps<typeof MDXProvider>["components"];

export const markdownComponents: MdxComponents = {
h1: () => {
throw new Error("No H1 components in blog posts!");
throw new Error("No H1 components in markdown content!");
},
h2: ({ children }) => (
<Typography
Expand Down Expand Up @@ -37,7 +40,6 @@ export const markdownComponents: MDXRemoteProps["components"] = {
children={children}
/>
),
hr: () => <Divider sx={{ width: "100%", my: 2 }} />,
p: ({ children }) => (
<Typography
sx={{ mt: "0.5em" }}
Expand All @@ -46,6 +48,7 @@ export const markdownComponents: MDXRemoteProps["components"] = {
children={children}
/>
),
hr: () => <Divider sx={{ width: "100%", my: 2 }} />,
a: ({ children, href }) => <Link href={href}>{children}</Link>,
ul: ({ children }) => (
<Box component="ul" sx={{ pl: 3 }}>
Expand Down
Loading

0 comments on commit 6be5d26

Please sign in to comment.