Skip to content

Commit d6ed36a

Browse files
committed
[add] Wiki pages with Open Source policy files
[optimize] upgrade to Koa 3 & other latest Upstream packages
1 parent ff061ac commit d6ed36a

File tree

14 files changed

+1418
-1268
lines changed

14 files changed

+1418
-1268
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
steps:
1616
- uses: actions/checkout@v4
1717
if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }}
18+
with:
19+
submodules: true
1820

1921
- name: Deploy to Vercel
2022
id: vercel-deployment

.gitmodules

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

components/Layout/MDXLayout.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { FC } from 'react';
2+
import { Container, ContainerProps } from 'react-bootstrap';
3+
4+
import { PageHead } from './PageHead';
5+
6+
export const MDXLayout: FC<ContainerProps> = ({
7+
className = 'mt-5 pt-5 pb-3',
8+
title,
9+
children,
10+
...props
11+
}) => (
12+
<Container as="article" {...props} className={className}>
13+
<PageHead title={title} />
14+
<h1 className="my-4">{title}</h1>
15+
16+
{children}
17+
</Container>
18+
);

components/Navigator/MainNavigator.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
2727
name: t('hackathon'),
2828
},
2929
{ href: '/license-filter', name: t('license_filter') },
30+
{ href: '/wiki', name: t('wiki') },
3031
];
3132

3233
export interface MainNavigatorProps {

next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference path="./.next/types/routes.d.ts" />
34

45
// NOTE: This file should not be edited
56
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

package.json

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,68 +12,69 @@
1212
"test": "lint-staged && npm run lint"
1313
},
1414
"dependencies": {
15-
"@koa/router": "^13.1.1",
15+
"@koa/router": "^14.0.0",
1616
"@mdx-js/loader": "^3.1.0",
1717
"@mdx-js/react": "^3.1.0",
18-
"@next/mdx": "^15.3.5",
19-
"core-js": "^3.44.0",
18+
"@next/mdx": "^15.5.0",
19+
"core-js": "^3.45.1",
2020
"file-type": "^21.0.0",
2121
"idea-react": "^2.0.0-rc.13",
22-
"koa": "^2.16.1",
22+
"koa": "^3.0.1",
2323
"koajax": "^3.1.2",
2424
"license-filter": "^0.2.5",
25-
"marked": "^16.0.0",
25+
"marked": "^16.2.0",
2626
"mime": "^4.0.7",
2727
"mobx": "^6.13.7",
2828
"mobx-github": "^0.3.11",
2929
"mobx-i18n": "^0.7.1",
30-
"mobx-lark": "^2.2.0",
30+
"mobx-lark": "^2.4.0",
3131
"mobx-react": "^9.2.0",
3232
"mobx-react-helper": "^0.5.1",
3333
"mobx-restful": "^2.1.0",
34-
"mobx-restful-table": "^2.5.2",
35-
"next": "^15.3.5",
34+
"mobx-restful-table": "^2.5.3",
35+
"next": "^15.5.0",
3636
"next-pwa": "^5.6.0",
37-
"next-ssr-middleware": "^1.0.1",
38-
"react": "^19.1.0",
37+
"next-ssr-middleware": "^1.0.2",
38+
"react": "^19.1.1",
3939
"react-bootstrap": "^2.10.10",
40-
"react-dom": "^19.1.0",
40+
"react-dom": "^19.1.1",
4141
"react-typed-component": "^1.0.6",
42-
"undici": "^7.11.0",
43-
"web-utility": "^4.4.3"
42+
"undici": "^7.15.0",
43+
"web-utility": "^4.5.1",
44+
"yaml": "^2.8.1"
4445
},
4546
"devDependencies": {
4647
"@babel/plugin-proposal-decorators": "^7.28.0",
4748
"@babel/plugin-transform-typescript": "^7.28.0",
4849
"@babel/preset-react": "^7.27.1",
49-
"@cspell/eslint-plugin": "^9.1.5",
50-
"@eslint/js": "^9.31.0",
50+
"@cspell/eslint-plugin": "^9.2.0",
51+
"@eslint/js": "^9.34.0",
5152
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
52-
"@stylistic/eslint-plugin": "^5.1.0",
53+
"@stylistic/eslint-plugin": "^5.2.3",
5354
"@types/eslint-config-prettier": "^6.11.3",
54-
"@types/koa": "^2.15.0",
55+
"@types/koa": "^3.0.0",
5556
"@types/koa__router": "^12.0.4",
5657
"@types/next-pwa": "^5.6.9",
57-
"@types/node": "^22.16.3",
58-
"@types/react": "^19.1.8",
59-
"@types/react-dom": "^19.1.6",
60-
"eslint": "^9.31.0",
61-
"eslint-config-next": "^15.3.5",
62-
"eslint-config-prettier": "^10.1.5",
58+
"@types/node": "^22.17.2",
59+
"@types/react": "^19.1.11",
60+
"@types/react-dom": "^19.1.7",
61+
"eslint": "^9.34.0",
62+
"eslint-config-next": "^15.5.0",
63+
"eslint-config-prettier": "^10.1.8",
6364
"eslint-plugin-react": "^7.37.5",
6465
"eslint-plugin-simple-import-sort": "^12.1.1",
6566
"globals": "^16.3.0",
6667
"husky": "^9.1.7",
67-
"jiti": "^2.4.2",
68-
"less": "^4.3.0",
68+
"jiti": "^2.5.1",
69+
"less": "^4.4.1",
6970
"less-loader": "^12.3.0",
70-
"lint-staged": "^16.1.2",
71+
"lint-staged": "^16.1.5",
7172
"next-with-less": "^3.0.1",
7273
"prettier": "^3.6.2",
7374
"prettier-plugin-css-order": "^2.1.2",
74-
"sass": "^1.89.2",
75-
"typescript": "~5.8.3",
76-
"typescript-eslint": "^8.36.0"
75+
"sass": "^1.90.0",
76+
"typescript": "~5.9.2",
77+
"typescript-eslint": "^8.40.0"
7778
},
7879
"resolutions": {
7980
"next": "$next"

pages/_app.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ import { Image } from 'react-bootstrap';
1010
import { MainNavigator } from '../components/Navigator/MainNavigator';
1111
import { PageContent } from '../components/PageContent';
1212
import { isServer } from '../models/configuration';
13-
import {
14-
createI18nStore,
15-
I18nContext,
16-
I18nProps,
17-
loadSSRLanguage,
18-
} from '../models/Translation';
13+
import { createI18nStore, I18nContext, I18nProps, loadSSRLanguage } from '../models/Translation';
1914

2015
configure({ enforceActions: 'never' });
2116

@@ -46,7 +41,8 @@ export default class CustomApp extends App<I18nProps> {
4641
render() {
4742
const { Component, pageProps, router } = this.props,
4843
{ t } = this.i18nStore;
49-
const thisFullYear = new Date().getFullYear();
44+
const thisFullYear = new Date().getFullYear(),
45+
{ asPath } = router;
5046

5147
return (
5248
<I18nContext.Provider value={this.i18nStore}>
@@ -59,7 +55,7 @@ export default class CustomApp extends App<I18nProps> {
5955
<MainNavigator />
6056

6157
<div className="mt-5 pt-2">
62-
{router.route.startsWith('/article/') ? (
58+
{asPath.startsWith('/article/') || asPath.startsWith('/wiki/') ? (
6359
<PageContent>
6460
<Component {...pageProps} />
6561
</PageContent>
@@ -71,8 +67,7 @@ export default class CustomApp extends App<I18nProps> {
7167
<footer className="mw-100 bg-dark text-white">
7268
<p className="text-center my-0 py-3">
7369
<span className="pr-3">
74-
© 2021{thisFullYear === 2021 ? '' : `-${thisFullYear}`}{' '}
75-
{t('open_source_bazaar')}
70+
© 2021{thisFullYear === 2021 ? '' : `-${thisFullYear}`} {t('open_source_bazaar')}
7671
</span>
7772
<a
7873
className="flex-fill d-flex justify-content-center align-items-center"
@@ -82,12 +77,7 @@ export default class CustomApp extends App<I18nProps> {
8277
>
8378
Powered by
8479
<span className="mx-2">
85-
<Image
86-
src="/vercel.svg"
87-
alt="Vercel Logo"
88-
width={72}
89-
height={16}
90-
/>
80+
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
9181
</span>
9282
</a>
9383
</p>

pages/api/core.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Router, { RouterParamContext } from '@koa/router';
22
import { Context, Middleware } from 'koa';
33
import { HTTPError } from 'koajax';
4+
import { DataObject } from 'mobx-restful';
45
import { KoaOption, withKoa, withKoaRouter } from 'next-ssr-middleware';
56
import { ProxyAgent, setGlobalDispatcher } from 'undici';
7+
import { parse } from 'yaml';
68

79
const { HTTP_PROXY } = process.env;
810

@@ -46,3 +48,67 @@ export const withSafeKoaRouter = <S, C extends RouterParamContext<S>>(
4648
router: Router<S, C>,
4749
...middlewares: Middleware<S, C>[]
4850
) => withKoaRouter<S, C>({} as KoaOption, router, safeAPI, ...middlewares);
51+
52+
export interface ArticleMeta {
53+
name: string;
54+
path?: string;
55+
meta?: DataObject;
56+
subs: ArticleMeta[];
57+
}
58+
59+
const MDX_pattern = /\.mdx?$/;
60+
61+
export async function frontMatterOf(path: string) {
62+
const { readFile } = await import('fs/promises');
63+
64+
const file = await readFile(path, 'utf-8');
65+
66+
const [, frontMatter] = file.match(/^---[\r\n]([\s\S]+?[\r\n])---/) || [];
67+
68+
return frontMatter && parse(frontMatter);
69+
}
70+
71+
export async function* pageListOf(path: string, prefix = 'pages'): AsyncGenerator<ArticleMeta> {
72+
const { readdir } = await import('fs/promises');
73+
74+
const list = await readdir(prefix + path, { withFileTypes: true });
75+
76+
for (const node of list) {
77+
let { name, path } = node;
78+
79+
if (name.startsWith('.')) continue;
80+
81+
const isMDX = MDX_pattern.test(name);
82+
83+
name = name.replace(MDX_pattern, '');
84+
path = `${path}/${name}`.replace(new RegExp(`^${prefix}`), '');
85+
86+
if (node.isFile() && isMDX) {
87+
const article: ArticleMeta = { name, path, subs: [] };
88+
try {
89+
const meta = await frontMatterOf(`${node.path}/${node.name}`);
90+
91+
if (meta) article.meta = meta;
92+
} catch (error) {
93+
console.error(`Error reading front matter for ${node.path}/${node.name}:`, error);
94+
}
95+
yield article;
96+
}
97+
if (!node.isDirectory()) continue;
98+
99+
const subs = await Array.fromAsync(pageListOf(path, prefix));
100+
101+
if (subs.length) yield { name, subs };
102+
}
103+
}
104+
105+
export type TreeNode<K extends string> = {
106+
[key in K]: TreeNode<K>[];
107+
};
108+
109+
export function* traverseTree<K extends string>(tree: TreeNode<K>, key: K): Generator<TreeNode<K>> {
110+
for (const node of tree[key] || []) {
111+
yield node;
112+
yield* traverseTree(node, key);
113+
}
114+
}

pages/wiki/index.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { observer } from 'mobx-react';
2+
import { InferGetStaticPropsType } from 'next';
3+
import { FC } from 'react';
4+
5+
import { MDXLayout } from '../../components/Layout/MDXLayout';
6+
import { i18n } from '../../models/Translation';
7+
import { ArticleMeta, pageListOf, traverseTree } from '../api/core';
8+
9+
export const getStaticProps = async () => {
10+
const tree = await Array.fromAsync(pageListOf('/wiki'));
11+
const list = tree.map(root => [...traverseTree(root, 'subs')]).flat();
12+
13+
return { props: { tree, list } };
14+
};
15+
16+
const renderTree = (list: ArticleMeta[]) => (
17+
<ol>
18+
{list.map(({ name, path, meta, subs }) => (
19+
<li key={name}>
20+
{path ? (
21+
<a className="h4 d-flex justify-content-between align-items-center" href={path}>
22+
{name}{' '}
23+
{meta && (
24+
<time className="fs-6" dateTime={meta.updated || meta.date}>
25+
{meta.updated || meta.date}
26+
</time>
27+
)}
28+
</a>
29+
) : (
30+
<details>
31+
<summary className="h4">{name}</summary>
32+
{renderTree(subs)}
33+
</details>
34+
)}
35+
</li>
36+
))}
37+
</ol>
38+
);
39+
40+
const ArticleIndexPage: FC<InferGetStaticPropsType<typeof getStaticProps>> = observer(
41+
({ tree, list: { length } }) => (
42+
<MDXLayout className="" title={`${i18n.t('wiki')} (${length})`}>
43+
{renderTree(tree)}
44+
</MDXLayout>
45+
),
46+
);
47+
48+
export default ArticleIndexPage;

pages/wiki/policy

Submodule policy added at d35fc30

0 commit comments

Comments
 (0)