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
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ jobs:
- uses: actions/checkout@v4
if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }}

- name: Clone latest submodules
run: git submodule update --init --recursive --remote

- name: Deploy to Vercel
id: vercel-deployment
uses: amondnet/vercel-action@v25
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "public/wiki/policy"]
path = public/wiki/policy
url = https://github.com/fpsig/open-source-policy
18 changes: 18 additions & 0 deletions components/Layout/MDXLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FC } from 'react';
import { Container, ContainerProps } from 'react-bootstrap';

import { PageHead } from './PageHead';

export const MDXLayout: FC<ContainerProps> = ({
className = 'mt-5 pt-5 pb-3',
title,
children,
...props
}) => (
<Container as="article" {...props} className={className}>
<PageHead title={title} />
<h1 className="my-4">{title}</h1>

{children}
</Container>
);
1 change: 1 addition & 0 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
name: t('hackathon'),
},
{ href: '/license-filter', name: t('license_filter') },
{ href: '/wiki', name: t('wiki') },
];

export interface MainNavigatorProps {
Expand Down
27 changes: 5 additions & 22 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import cspellPlugin from '@cspell/eslint-plugin';
import eslint from '@eslint/js';
// @ts-expect-error eslint-plugin-next doesn't come with TypeScript definitions
import nextPlugin from '@next/eslint-plugin-next';
import stylistic from '@stylistic/eslint-plugin';
import eslintConfigPrettier from 'eslint-config-prettier';
Expand Down Expand Up @@ -31,12 +30,7 @@ export default tsEslint.config(
},
{
// config with just ignores is the replacement for `.eslintignore`
ignores: [
'**/node_modules/**',
'**/public/**',
'**/.next/**',
'.github/scripts/**',
],
ignores: ['**/node_modules/**', '**/public/**', '**/.next/**', '.github/scripts/**'],
},

// extends ...
Expand All @@ -53,22 +47,15 @@ export default tsEslint.config(
warnOnUnsupportedTypeScriptVersion: false,
},
},
// @ts-expect-error https://github.com/vercel/next.js/issues/81695
rules: {
// spellchecker
'@cspell/spellchecker': [
'warn',
{
cspell: {
language: 'en',
dictionaries: [
'typescript',
'node',
'html',
'css',
'bash',
'npm',
'pnpm',
],
dictionaries: ['typescript', 'node', 'html', 'css', 'bash', 'npm', 'pnpm'],
},
},
],
Expand All @@ -91,8 +78,7 @@ export default tsEslint.config(
'error',
{
selector: "TSPropertySignature[key.name='children']",
message:
'Please use PropsWithChildren<T> instead of defining children manually',
message: 'Please use PropsWithChildren<T> instead of defining children manually',
},
],
'consistent-return': 'warn',
Expand All @@ -109,10 +95,7 @@ export default tsEslint.config(
// React
'react/no-unescaped-entities': 'off',
'react/self-closing-comp': ['error', { component: true, html: true }],
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never' },
],
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
'react/jsx-no-target-blank': 'warn',
'react/jsx-sort-props': [
'error',
Expand Down
1 change: 1 addition & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
60 changes: 31 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,70 @@
"test": "lint-staged && npm run lint"
},
"dependencies": {
"@koa/router": "^13.1.1",
"@koa/router": "^14.0.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.3.5",
"core-js": "^3.44.0",
"@next/mdx": "^15.5.0",
"core-js": "^3.45.1",
"file-type": "^21.0.0",
"idea-react": "^2.0.0-rc.13",
"koa": "^2.16.1",
"koa": "^3.0.1",
"koajax": "^3.1.2",
"license-filter": "^0.2.5",
"marked": "^16.0.0",
"marked": "^16.2.0",
"mime": "^4.0.7",
"mobx": "^6.13.7",
"mobx-github": "^0.3.11",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.2.0",
"mobx-lark": "^2.4.0",
"mobx-react": "^9.2.0",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.0",
"mobx-restful-table": "^2.5.2",
"next": "^15.3.5",
"mobx-restful-table": "^2.5.3",
"next": "^15.5.0",
"next-pwa": "^5.6.0",
"next-ssr-middleware": "^1.0.1",
"react": "^19.1.0",
"next-ssr-middleware": "^1.0.2",
"react": "^19.1.1",
"react-bootstrap": "^2.10.10",
"react-dom": "^19.1.0",
"react-dom": "^19.1.1",
"react-typed-component": "^1.0.6",
"undici": "^7.11.0",
"web-utility": "^4.4.3"
"undici": "^7.15.0",
"web-utility": "^4.5.1",
"yaml": "^2.8.1"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@cspell/eslint-plugin": "^9.1.5",
"@eslint/js": "^9.31.0",
"@cspell/eslint-plugin": "^9.2.0",
"@eslint/js": "^9.34.0",
"@next/eslint-plugin-next": "^15.5.0",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^5.1.0",
"@stylistic/eslint-plugin": "^5.2.3",
"@types/eslint-config-prettier": "^6.11.3",
"@types/koa": "^2.15.0",
"@types/koa": "^3.0.0",
"@types/koa__router": "^12.0.4",
"@types/next-pwa": "^5.6.9",
"@types/node": "^22.16.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"eslint": "^9.31.0",
"eslint-config-next": "^15.3.5",
"eslint-config-prettier": "^10.1.5",
"@types/node": "^22.17.2",
"@types/react": "^19.1.11",
"@types/react-dom": "^19.1.7",
"eslint": "^9.34.0",
"eslint-config-next": "^15.5.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^16.3.0",
"husky": "^9.1.7",
"jiti": "^2.4.2",
"less": "^4.3.0",
"jiti": "^2.5.1",
"less": "^4.4.1",
"less-loader": "^12.3.0",
"lint-staged": "^16.1.2",
"lint-staged": "^16.1.5",
"next-with-less": "^3.0.1",
"prettier": "^3.6.2",
"prettier-plugin-css-order": "^2.1.2",
"sass": "^1.89.2",
"typescript": "~5.8.3",
"typescript-eslint": "^8.36.0"
"sass": "^1.90.0",
"typescript": "~5.9.2",
"typescript-eslint": "^8.40.0"
},
"resolutions": {
"next": "$next"
Expand Down
22 changes: 6 additions & 16 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import { Image } from 'react-bootstrap';
import { MainNavigator } from '../components/Navigator/MainNavigator';
import { PageContent } from '../components/PageContent';
import { isServer } from '../models/configuration';
import {
createI18nStore,
I18nContext,
I18nProps,
loadSSRLanguage,
} from '../models/Translation';
import { createI18nStore, I18nContext, I18nProps, loadSSRLanguage } from '../models/Translation';

configure({ enforceActions: 'never' });

Expand Down Expand Up @@ -46,7 +41,8 @@ export default class CustomApp extends App<I18nProps> {
render() {
const { Component, pageProps, router } = this.props,
{ t } = this.i18nStore;
const thisFullYear = new Date().getFullYear();
const thisFullYear = new Date().getFullYear(),
{ asPath } = router;

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

<div className="mt-5 pt-2">
{router.route.startsWith('/article/') ? (
{asPath.startsWith('/article/') || asPath.startsWith('/wiki/') ? (
<PageContent>
<Component {...pageProps} />
</PageContent>
Expand All @@ -71,8 +67,7 @@ export default class CustomApp extends App<I18nProps> {
<footer className="mw-100 bg-dark text-white">
<p className="text-center my-0 py-3">
<span className="pr-3">
© 2021{thisFullYear === 2021 ? '' : `-${thisFullYear}`}{' '}
{t('open_source_bazaar')}
© 2021{thisFullYear === 2021 ? '' : `-${thisFullYear}`} {t('open_source_bazaar')}
</span>
<a
className="flex-fill d-flex justify-content-center align-items-center"
Expand All @@ -82,12 +77,7 @@ export default class CustomApp extends App<I18nProps> {
>
Powered by
<span className="mx-2">
<Image
src="/vercel.svg"
alt="Vercel Logo"
width={72}
height={16}
/>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</p>
Expand Down
80 changes: 80 additions & 0 deletions pages/api/core.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'core-js/full/array/from-async';

import Router, { RouterParamContext } from '@koa/router';
import { Context, Middleware } from 'koa';
import { HTTPError } from 'koajax';
import { DataObject } from 'mobx-restful';
import { KoaOption, withKoa, withKoaRouter } from 'next-ssr-middleware';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { parse } from 'yaml';

const { HTTP_PROXY } = process.env;

Expand Down Expand Up @@ -46,3 +50,79 @@ export const withSafeKoaRouter = <S, C extends RouterParamContext<S>>(
router: Router<S, C>,
...middlewares: Middleware<S, C>[]
) => withKoaRouter<S, C>({} as KoaOption, router, safeAPI, ...middlewares);

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

const MDX_pattern = /\.mdx?$/;

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

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

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

if (!frontMatter) return { markdown: file };

try {
const meta = parse(frontMatter) as DataObject;

return { markdown, meta };
} catch (error) {
console.error(`Error reading front matter for ${path}:`, error);

return { markdown };
}
}

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() && isMDX) {
const article: ArticleMeta = { name, path, subs: [] };

const { meta } = await splitFrontMatter(`${node.path}/${node.name}`);

if (meta) article.meta = meta;

yield article;
}
if (!node.isDirectory()) continue;

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

if (subs.length) yield { name, subs };
}
}

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

export function* traverseTree<K extends string, N extends TreeNode<K>>(
tree: N,
key: K,
): Generator<N> {
for (const node of tree[key] || []) {
yield node as N;
yield* traverseTree(node as N, key);
}
}
Loading