Skip to content

Commit 38cbd6d

Browse files
committed
[fix] Unicode Markdown path is 404 with Next.js MDX plugin
1 parent a32fcda commit 38cbd6d

File tree

5 files changed

+80
-16
lines changed

5 files changed

+80
-16
lines changed

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[submodule "pages/wiki/policy"]
2-
path = pages/wiki/policy
1+
[submodule "wiki/policy"]
2+
path = wiki/policy
33
url = https://github.com/fpsig/open-source-policy

pages/api/core.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'core-js/full/array/from-async';
2+
13
import Router, { RouterParamContext } from '@koa/router';
24
import { Context, Middleware } from 'koa';
35
import { HTTPError } from 'koajax';
@@ -58,14 +60,25 @@ export interface ArticleMeta {
5860

5961
const MDX_pattern = /\.mdx?$/;
6062

61-
export async function frontMatterOf(path: string) {
63+
export async function splitFrontMatter(path: string) {
6264
const { readFile } = await import('fs/promises');
6365

6466
const file = await readFile(path, 'utf-8');
6567

66-
const [, frontMatter] = file.match(/^---[\r\n]([\s\S]+?[\r\n])---/) || [];
68+
const [, frontMatter, markdown] =
69+
file.trim().match(/^---[\r\n]([\s\S]+?[\r\n])---[\r\n]([\s\S]*)/) || [];
70+
71+
if (!frontMatter) return { markdown: file };
72+
73+
try {
74+
const meta = parse(frontMatter) as DataObject;
75+
76+
return { markdown, meta };
77+
} catch (error) {
78+
console.error(`Error reading front matter for ${path}:`, error);
6779

68-
return frontMatter && parse(frontMatter);
80+
return { markdown };
81+
}
6982
}
7083

7184
export async function* pageListOf(path: string, prefix = 'pages'): AsyncGenerator<ArticleMeta> {
@@ -85,13 +98,11 @@ export async function* pageListOf(path: string, prefix = 'pages'): AsyncGenerato
8598

8699
if (node.isFile() && isMDX) {
87100
const article: ArticleMeta = { name, path, subs: [] };
88-
try {
89-
const meta = await frontMatterOf(`${node.path}/${node.name}`);
90101

91-
if (meta) article.meta = meta;
92-
} catch (error) {
93-
console.error(`Error reading front matter for ${node.path}/${node.name}:`, error);
94-
}
102+
const { meta } = await splitFrontMatter(`${node.path}/${node.name}`);
103+
104+
if (meta) article.meta = meta;
105+
95106
yield article;
96107
}
97108
if (!node.isDirectory()) continue;
@@ -106,9 +117,12 @@ export type TreeNode<K extends string> = {
106117
[key in K]: TreeNode<K>[];
107118
};
108119

109-
export function* traverseTree<K extends string>(tree: TreeNode<K>, key: K): Generator<TreeNode<K>> {
120+
export function* traverseTree<K extends string, N extends TreeNode<K>>(
121+
tree: N,
122+
key: K,
123+
): Generator<N> {
110124
for (const node of tree[key] || []) {
111-
yield node;
112-
yield* traverseTree(node, key);
125+
yield node as N;
126+
yield* traverseTree(node as N, key);
113127
}
114128
}

pages/wiki/[...slug].tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { marked } from 'marked';
2+
import { DataObject } from 'mobx-restful';
3+
import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
4+
import { ParsedUrlQuery } from 'querystring';
5+
6+
import { pageListOf, splitFrontMatter, traverseTree } from '../api/core';
7+
8+
interface WikiPageParams extends ParsedUrlQuery {
9+
slug: string[];
10+
}
11+
12+
export const getStaticPaths: GetStaticPaths<WikiPageParams> = async () => {
13+
const tree = await Array.fromAsync(pageListOf('wiki', ''));
14+
const list = tree.map(root => [...traverseTree(root, 'subs')]).flat();
15+
const paths = list
16+
.map(({ path }) => path && { params: { slug: path.split('/') } })
17+
.filter(Boolean) as { params: WikiPageParams }[];
18+
19+
return { paths, fallback: 'blocking' };
20+
};
21+
22+
interface WikiPageProps {
23+
meta?: DataObject;
24+
markup: string;
25+
}
26+
27+
export const getStaticProps: GetStaticProps<WikiPageProps, WikiPageParams> = async ({ params }) => {
28+
const { slug } = params!;
29+
// https://github.com/vercel/next.js/issues/12851
30+
if (slug[0] !== 'wiki') slug.unshift('wiki');
31+
32+
const { meta, markdown } = await splitFrontMatter(`${slug.join('/')}.md`);
33+
34+
const markup = marked(markdown) as string;
35+
36+
return { props: JSON.parse(JSON.stringify({ meta, markup })) };
37+
};
38+
39+
const WikiPage: NextPage<WikiPageProps> = ({ meta, markup }) => (
40+
<>
41+
{meta && (
42+
<blockquote>
43+
<a target="_blank" href={meta.url} rel="noreferrer">
44+
{meta.url}
45+
</a>
46+
</blockquote>
47+
)}
48+
<article dangerouslySetInnerHTML={{ __html: markup }} />
49+
</>
50+
);
51+
export default WikiPage;

pages/wiki/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { i18n } from '../../models/Translation';
77
import { ArticleMeta, pageListOf, traverseTree } from '../api/core';
88

99
export const getStaticProps = async () => {
10-
const tree = await Array.fromAsync(pageListOf('/wiki'));
10+
const tree = await Array.fromAsync(pageListOf('wiki', ''));
1111
const list = tree.map(root => [...traverseTree(root, 'subs')]).flat();
1212

1313
return { props: { tree, list } };

pages/wiki/policy

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)