Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
7 changes: 7 additions & 0 deletions instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { NEXT_RUNTIME } = process.env;

export async function register() {
if (NEXT_RUNTIME === 'nodejs') await import('./sentry.server.config');

if (NEXT_RUNTIME === 'edge') await import('./sentry.edge.config');
}
67 changes: 38 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,70 +17,79 @@
"@editorjs/quote": "^2.7.6",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.2.2",
"@sentry/nextjs": "^9.5.0",
"@next/mdx": "^15.3.1",
"@sentry/nextjs": "^9.13.0",
"copy-webpack-plugin": "^13.0.0",
"core-js": "^3.41.0",
"editorjs-html": "^4.0.5",
"idea-react": "^2.0.0-rc.8",
"koajax": "^3.1.1",
"less": "^4.2.2",
"idea-react": "^2.0.0-rc.10",
"koajax": "^3.1.2",
"less": "^4.3.0",
"less-loader": "^12.2.0",
"lodash": "^4.17.21",
"mobx": "^6.13.6",
"mobx": "^6.13.7",
"mobx-github": "^0.3.5",
"mobx-i18n": "^0.6.0",
"mobx-react": "^9.2.0",
"mobx-restful": "^2.1.0",
"mobx-restful-table": "^2.0.2",
"next": "^15.2.2",
"mobx-restful-table": "^2.1.1",
"next": "^15.3.1",
"next-pwa": "~5.6.0",
"next-ssr-middleware": "^0.8.9",
"next-ssr-middleware": "^0.9.2",
"next-with-less": "^3.0.1",
"prismjs": "^1.30.0",
"react": "^19.0.0",
"react": "^19.1.0",
"react-bootstrap": "^2.10.9",
"react-bootstrap-editor": "^2.0.7",
"react-dom": "^19.0.0",
"react-dom": "^19.1.0",
"react-editor-js": "^2.1.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.0.0",
"undici": "^7.5.0",
"remark-mdx-frontmatter": "^5.1.0",
"undici": "^7.8.0",
"web-utility": "^4.4.3",
"webpack": "^5.98.0"
"webpack": "^5.99.6",
"yaml": "^2.7.1"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.25.9",
"@babel/plugin-transform-typescript": "^7.26.8",
"@babel/plugin-transform-typescript": "^7.27.0",
"@babel/preset-react": "^7.26.3",
"@cspell/eslint-plugin": "^8.17.5",
"@eslint/compat": "^1.2.7",
"@eslint/eslintrc": "^3.3.0",
"@eslint/js": "^9.22.0",
"@next/eslint-plugin-next": "^15.2.2",
"@cspell/eslint-plugin": "^8.19.2",
"@eslint/compat": "^1.2.8",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.25.0",
"@next/eslint-plugin-next": "^15.3.1",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^4.2.0",
"@types/eslint-config-prettier": "^6.11.3",
"@types/lodash": "^4.17.16",
"@types/next-pwa": "^5.6.9",
"@types/node": "^22.13.10",
"@types/react": "^19.0.10",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-react": "^7.37.4",
"@types/node": "^22.14.1",
"@types/react": "^19.1.2",
"eslint": "^9.25.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^16.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"jiti": "^2.4.2",
"lint-staged": "^15.5.1",
"prettier": "^3.5.3",
"prettier-plugin-css-order": "^2.1.2",
"typescript": "~5.8.2",
"typescript-eslint": "^8.26.1"
"typescript": "~5.8.3",
"typescript-eslint": "^8.30.1"
},
"resolutions": {
"next": "$next"
},
"pnpm": {
"onlyBuiltDependencies": [
"@sentry/cli",
"core-js",
"sharp"
]
},
"prettier": {
"singleQuote": true,
"trailingComma": "all",
Expand All @@ -100,7 +109,7 @@
"build": "next build",
"export": "next build && next export",
"start": "next start",
"lint": "next lint && tsc --noEmit",
"lint": "next lint --fix && tsc --noEmit",
"lint:all": "eslint --fix .",
"lint:inspect": "eslint --inspect-config",
"test": "lint-staged && npm run lint",
Expand Down
74 changes: 74 additions & 0 deletions pages/api/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { HTTPError } from 'koajax';
import { DataObject } from 'mobx-restful';
import { NextApiRequest, NextApiResponse } from 'next';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { parse } from 'yaml';

const { HTTP_PROXY } = process.env;

Expand Down Expand Up @@ -43,3 +45,75 @@ export function safeAPI(handler: NextAPI): NextAPI {
}
};
}

export interface ArticleMeta {
name: string;
path?: string;
meta?: DataObject;
subs: ArticleMeta[];
}

const MDX_pattern = /\.mdx?$/;

export async function frontMatterOf(path: string) {
const { readFile } = await import('fs/promises');

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

const [, frontMatter] = file.match(/^---[\r\n]([\s\S]+?[\r\n])---/) || [];

return frontMatter && parse(frontMatter);
}

export async function* pageListOf(
path: string,
prefix = 'pages',
): AsyncGenerator<ArticleMeta> {
const { readdir } = await import('fs/promises');

const list = await readdir(prefix + path, { withFileTypes: true });

for (const node of list) {
let { name, path } = node;

if (name.startsWith('.')) continue;

const isMDX = MDX_pattern.test(name);

name = name.replace(MDX_pattern, '');
path = `${path}/${name}`.replace(new RegExp(`^${prefix}`), '');

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

if (meta) article.meta = meta;
} catch (error) {
console.error(error);
}
yield article;
} else continue;

if (!node.isDirectory()) continue;

const subs = await Array.fromAsync(pageListOf(path, prefix));

if (subs[0]) yield { name, subs };
}
}

export type TreeNode<K extends string> = {
[key in K]: TreeNode<K>[];
};

export function* traverseTree<K extends string>(
tree: TreeNode<K>,
key: K,
): Generator<TreeNode<K>> {
for (const node of tree[key] || []) {
yield node;
yield* traverseTree(node, key);
}
}
68 changes: 20 additions & 48 deletions pages/article/index.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,33 @@
import { observer } from 'mobx-react';
import { GetStaticProps, InferGetStaticPropsType } from 'next';
import { InferGetStaticPropsType } from 'next';
import { FC } from 'react';

import { MDXLayout } from '../../components/MDXLayout';
import { i18n } from '../../models/Translation';
import { ArticleMeta, pageListOf, traverseTree } from '../api/core';

interface ArticleMeta {
name: string;
path?: string;
subs: ArticleMeta[];
}
export const getStaticProps = async () => {
const tree = await Array.fromAsync(pageListOf('/article'));
const list = tree.map(root => [...traverseTree(root, 'subs')]).flat();

const MDX_pattern = /\.mdx?$/;

export const getStaticProps: GetStaticProps<{
list: ArticleMeta[];
}> = async () => {
const { readdirSync } = await import('fs');

const pageListOf = (path: string, prefix = 'pages'): ArticleMeta[] =>
readdirSync(prefix + path, { withFileTypes: true })
.map(node => {
let { name, path } = node;

if (name.startsWith('.')) return;

const isMDX = MDX_pattern.test(name);

name = name.replace(MDX_pattern, '');
path = `${path}/${name}`.replace(new RegExp(`^${prefix}`), '');

if (node.isFile()) return isMDX && { name, path };

if (!node.isDirectory()) return;

const subs = pageListOf(path, prefix);

return subs[0] && { name, subs };
})
.filter(Boolean) as ArticleMeta[];

try {
const list = pageListOf('/article');

return { props: { list } };
} catch {
return { props: { list: [] } };
}
return { props: { tree, list } };
};

const renderTree = (list: ArticleMeta[]) => (
<ol>
{list.map(({ name, path, subs }) => (
{list.map(({ name, path, meta, subs }) => (
<li key={name}>
{path ? (
<a className="h4" href={path}>
{name}
<a
className="h4 d-flex justify-content-between align-items-center"
href={path}
>
{name}{' '}
{meta && (
<time className="fs-6" dateTime={meta.updated || meta.date}>
{meta.updated || meta.date}
</time>
)}
</a>
) : (
<details>
Expand All @@ -69,9 +41,9 @@ const renderTree = (list: ArticleMeta[]) => (
);

const ArticleIndexPage: FC<InferGetStaticPropsType<typeof getStaticProps>> =
observer(({ list }) => (
<MDXLayout className="" title={i18n.t('article')}>
{renderTree(list)}
observer(({ tree, list: { length } }) => (
<MDXLayout className="" title={`${i18n.t('article')} (${length})`}>
{renderTree(tree)}
</MDXLayout>
));

Expand Down
Loading