- {issue.labels && issue.labels.length > 0 && (
-
+ {issue.labels?.[0] && (
+
{issue.labels.slice(0, 3).map((label, index) => (
{typeof label === 'string' ? label : label.name}
))}
{issue.labels.length > 3 && (
-
+
+{issue.labels.length - 3}
)}
-
+
)}
= ({ issue }) => {
);
-};
\ No newline at end of file
+});
diff --git a/models/Base.ts b/models/Base.ts
index 35d84e3ef..dbc9ba9b5 100644
--- a/models/Base.ts
+++ b/models/Base.ts
@@ -1,7 +1,7 @@
import 'core-js/full/array/from-async';
import { HTTPClient } from 'koajax';
-import { githubClient, RepositoryModel, Issue as GitHubIssue } from 'mobx-github';
+import { githubClient, Issue as GitHubIssue, RepositoryModel } from 'mobx-github';
import { TableCellAttachment, TableCellMedia, TableCellValue } from 'mobx-lark';
import { DataObject } from 'mobx-restful';
import { isEmpty } from 'web-utility';
@@ -30,7 +30,7 @@ githubClient.use(({ request }, next) => {
return next();
});
-export { githubClient, RepositoryModel };
+export { githubClient };
export type { GitHubIssue as Issue };
export const githubRawClient = new HTTPClient({
diff --git a/package.json b/package.json
index 4ec18213e..55148f9fc 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"copy-webpack-plugin": "^13.0.0",
"core-js": "^3.44.0",
"file-type": "^21.0.0",
- "github-markdown-css": "^5.8.1",
"idea-react": "^2.0.0-rc.13",
"koa": "^2.16.1",
"koajax": "^3.1.2",
@@ -25,7 +24,7 @@
"marked": "^16.0.0",
"mime": "^4.0.7",
"mobx": "^6.13.7",
- "mobx-github": "^0.3.11",
+ "mobx-github": "^0.5.0",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.2.0",
"mobx-react": "^9.2.0",
diff --git a/pages/weekly/[id].tsx b/pages/weekly/[id].tsx
index 9d5aee877..05977e650 100644
--- a/pages/weekly/[id].tsx
+++ b/pages/weekly/[id].tsx
@@ -1,6 +1,5 @@
-import 'github-markdown-css/github-markdown-light.css';
-
import { marked } from 'marked';
+import { IssueModel } from 'mobx-github';
import { observer } from 'mobx-react';
import { GetStaticPaths, GetStaticProps } from 'next';
import { ParsedUrlQuery } from 'querystring';
@@ -9,7 +8,6 @@ import { Badge, Breadcrumb, Button, Card, Container } from 'react-bootstrap';
import { PageHead } from '../../components/Layout/PageHead';
import type { Issue } from '../../models/Base';
-import { RepositoryModel } from '../../models/Base';
import { I18nContext } from '../../models/Translation';
import styles from '../../styles/Weekly.module.less';
@@ -22,10 +20,11 @@ interface WeeklyDetailProps {
}
export const getStaticPaths: GetStaticPaths
= async () => {
- const repository = new RepositoryModel('FreeCodeCamp-Chengdu');
- const repo = await repository.getOne('IT-Technology-weekly', ['issues']);
-
- const paths = (repo.issues || []).map(issue => ({
+ const list = await new IssueModel('FreeCodeCamp-Chengdu', 'IT-Technology-weekly').getAll({
+ state: 'all',
+ });
+
+ const paths = list.map(issue => ({
params: { id: issue.number.toString() },
}));
@@ -36,21 +35,10 @@ export const getStaticProps: GetStaticProps {
const { id } = params!;
- const repository = new RepositoryModel('FreeCodeCamp-Chengdu');
- const repo = await repository.getOne('IT-Technology-weekly', ['issues']);
-
- const issue = repo.issues?.find(issue => issue.number.toString() === id);
-
- if (!issue) {
- return {
- notFound: true,
- };
- }
+ const issue = await new IssueModel('FreeCodeCamp-Chengdu', 'IT-Technology-weekly').getOne(id);
return {
- props: {
- issue: JSON.parse(JSON.stringify(issue)),
- },
+ props: JSON.parse(JSON.stringify(issue)),
revalidate: 3600, // Revalidate every hour
};
};
@@ -63,7 +51,7 @@ const WeeklyDetailPage: FC = observer(({ issue }) => {
@@ -86,15 +74,15 @@ const WeeklyDetailPage: FC = observer(({ issue }) => {
{issue.labels?.[0] && (
{issue.labels.map((label, index) => (
- -
-
- {typeof label === 'string' ? label : label.name}
-
-
+
+ {typeof label === 'string' ? label : label.name}
+
))}
)}
@@ -143,13 +131,10 @@ const WeeklyDetailPage: FC = observer(({ issue }) => {
{htmlContent ? (
-
+
) : (
- {t('weekly_issue_no_content')}
+ {t('weekly_issue_no_content')}
)}
diff --git a/pages/weekly/index.tsx b/pages/weekly/index.tsx
index 0ffe38653..7dcaad69b 100644
--- a/pages/weekly/index.tsx
+++ b/pages/weekly/index.tsx
@@ -1,12 +1,12 @@
+import { IssueModel } from 'mobx-github';
import { observer } from 'mobx-react';
import { GetStaticProps, InferGetStaticPropsType } from 'next';
import { FC, useContext } from 'react';
import { Button, Card, Col, Container, Row } from 'react-bootstrap';
-import { IssueCard } from '../../components/IssueCard';
+import { IssueCard } from '../../components/Git/IssueCard';
import { PageHead } from '../../components/Layout/PageHead';
import type { Issue } from '../../models/Base';
-import { RepositoryModel } from '../../models/Base';
import { I18nContext } from '../../models/Translation';
import styles from '../../styles/Weekly.module.less';
@@ -15,12 +15,13 @@ interface WeeklyPageProps {
}
export const getStaticProps: GetStaticProps = async () => {
- const repository = new RepositoryModel('FreeCodeCamp-Chengdu');
- const repo = await repository.getOne('IT-Technology-weekly', ['issues']);
+ const list = await new IssueModel('FreeCodeCamp-Chengdu', 'IT-Technology-weekly').getAll({
+ state: 'all',
+ });
return {
props: {
- issues: JSON.parse(JSON.stringify(repo.issues || [])),
+ issues: JSON.parse(JSON.stringify(list)),
},
revalidate: 3600, // Revalidate every hour
};
@@ -50,11 +51,9 @@ const WeeklyIndexPage: FC> = obse
{issues.length > 0 ? (
- {issues.map((issue) => (
+ {issues.map(issue => (
-
+
))}
From 73a93054d059245aa762eec3b96ea1a72e5850d2 Mon Sep 17 00:00:00 2001
From: TechQuery
Date: Mon, 8 Sep 2025 02:43:34 +0800
Subject: [PATCH 7/9] [optimize] upgrade to Koa 3, Sentry 10 & other latest
Upstream packages [fix] many GitHub copilot bugs
---
.github/workflows/main.yml | 2 +-
components/Git/ArticleEditor.tsx | 19 +-
components/Git/IssueCard.module.less | 21 +
components/Git/IssueCard.tsx | 114 +-
components/Git/LabelBar.tsx | 22 +
models/Base.ts | 3 +-
package.json | 72 +-
pages/api/Lark/bitable/v1/[...slug].ts | 22 +-
pages/api/Lark/file/[id]/[name].ts | 20 +-
pages/api/core.ts | 51 +-
pages/index.tsx | 5 +-
pages/weekly/[id].tsx | 228 +-
pages/weekly/index.tsx | 42 +-
pnpm-lock.yaml | 3660 ++++++++++++------------
styles/Weekly.module.less | 31 -
15 files changed, 2177 insertions(+), 2135 deletions(-)
create mode 100644 components/Git/IssueCard.module.less
create mode 100644 components/Git/LabelBar.tsx
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 624c9bd2f..ff514f381 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -2,7 +2,7 @@ name: CI & CD
on:
push:
branches:
- - '*'
+ - '**'
jobs:
Build-and-Deploy:
env:
diff --git a/components/Git/ArticleEditor.tsx b/components/Git/ArticleEditor.tsx
index 1db860176..808405e77 100644
--- a/components/Git/ArticleEditor.tsx
+++ b/components/Git/ArticleEditor.tsx
@@ -3,14 +3,14 @@ import { readAs } from 'koajax';
import { debounce } from 'lodash';
import { marked } from 'marked';
import { computed, observable } from 'mobx';
-import { Content } from 'mobx-github';
+import { Content, ContentModel } from 'mobx-github';
import { observer } from 'mobx-react';
import { ObservedComponent } from 'mobx-react-helper';
import { DataObject } from 'mobx-restful';
import { SearchableInput } from 'mobx-restful-table';
import { ChangeEvent, FormEvent } from 'react';
import { Button, Col, Form } from 'react-bootstrap';
-import { blobOf, formatDate, uniqueID } from 'web-utility';
+import { blobOf, encodeBase64, formatDate, uniqueID } from 'web-utility';
import YAML from 'yaml';
import { GitFileSearchModel } from '../../models/GitFile';
@@ -181,7 +181,8 @@ export class ArticleEditor extends ObservedComponent<{}, typeof i18n> {
if (!editorContent) return;
- const root = document.querySelector('div[contenteditable]');
+ const contentStore = new ContentModel(currentRepository.owner, currentRepository.name),
+ root = document.querySelector('div[contenteditable]');
const media: HTMLMediaElement[] = [].filter.call(
root!.querySelectorAll('img[src], audio[src], video[src]'),
({ src }) => new URL(src).protocol === 'blob:',
@@ -189,22 +190,22 @@ export class ArticleEditor extends ObservedComponent<{}, typeof i18n> {
for (const file of media) {
const blob = await blobOf(file.src);
+ const [, content] = ((await readAs(blob, 'dataURL').result) as string).split(',');
const filePath = this.path.replace(/\.\w+$/, `/${uniqueID()}.${blob.type.split('/')[1]}`);
- const { download_url } = await repositoryStore.updateContent(
+
+ const { download_url } = await contentStore.updateOne(
+ { content },
filePath,
- blob,
'[Upload] from Git-Pager',
- currentRepository.name,
);
file.src = download_url!;
}
- await repositoryStore.updateContent(
+ await contentStore.updateOne(
+ { content: encodeBase64(this.getContent() || '') },
this.path,
- this.getContent() as string,
message.value.trim(),
- currentRepository.name,
);
window.alert('Submitted');
};
diff --git a/components/Git/IssueCard.module.less b/components/Git/IssueCard.module.less
new file mode 100644
index 000000000..9c7ef5c9e
--- /dev/null
+++ b/components/Git/IssueCard.module.less
@@ -0,0 +1,21 @@
+.issueCard {
+ transition:
+ transform 0.2s ease,
+ box-shadow 0.2s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+ }
+}
+.issueTitle {
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: #028dfa !important;
+ }
+}
+.authorInfo {
+ color: #586069;
+ font-size: 14px;
+}
diff --git a/components/Git/IssueCard.tsx b/components/Git/IssueCard.tsx
index 33a3d7354..a32774fc2 100644
--- a/components/Git/IssueCard.tsx
+++ b/components/Git/IssueCard.tsx
@@ -1,80 +1,54 @@
+import { Issue } from 'mobx-github';
import { observer } from 'mobx-react';
import { FC, useContext } from 'react';
import { Badge, Card, CardProps } from 'react-bootstrap';
-import type { Issue } from '../../models/Base';
import { I18nContext } from '../../models/Translation';
-import styles from '../../styles/Weekly.module.less';
+import styles from './IssueCard.module.less';
+import { LabelBar } from './LabelBar';
-export type IssueCardProps = Issue & CardProps;
+export type IssueCardProps = Omit & Omit;
-export const IssueCard: FC = observer(({ className, ...issue }) => {
- const { t } = useContext(I18nContext);
+export const IssueCard: FC = observer(
+ ({ className = '', number, title, labels, body, user, created_at, state }) => {
+ const { t } = useContext(I18nContext);
- return (
-
-
-
-
- {issue.state === 'open' ? t('open') : t('closed')}
+ return (
+
+
+
+ {state === 'open' ? t('open') : t('closed')}
- #{issue.number}
-
-
-
-
- {issue.title}
-
-
-
- {issue.body && (
-
- {issue.body.slice(0, 150)}
- {issue.body.length > 150 && '...'}
-
- )}
-
-
- {issue.labels?.[0] && (
-
- {issue.labels.slice(0, 3).map((label, index) => (
-
- {typeof label === 'string' ? label : label.name}
-
- ))}
- {issue.labels.length > 3 && (
-
- +{issue.labels.length - 3}
-
- )}
-
+
#{number}
+
+
+
+
+
+ {title}
+
+
+
+ {body && (
+
+ {body.slice(0, 150)}
+ {body.length > 150 && '...'}
+
)}
-
-
-
- {issue.user && (
- <>
- {t('weekly_author')}: {issue.user.login}
- >
- )}
-
-
-
-
-
-
- );
-});
+ {labels?.[0] && }
+
+
+
+ {user && `${t('weekly_author')}: ${user.login}`}
+
+
+
+
+ );
+ },
+);
diff --git a/components/Git/LabelBar.tsx b/components/Git/LabelBar.tsx
new file mode 100644
index 000000000..f8401c99f
--- /dev/null
+++ b/components/Git/LabelBar.tsx
@@ -0,0 +1,22 @@
+import { Issue } from 'mobx-github';
+import { FC } from 'react';
+
+export type LabelBarProps = Pick;
+
+export const LabelBar: FC = ({ labels }) => (
+
+ {labels.map((label, index) => {
+ const { name, color } = typeof label === 'object' ? label : {};
+
+ return (
+ -
+ {typeof label === 'string' ? label : name}
+
+ );
+ })}
+
+);
diff --git a/models/Base.ts b/models/Base.ts
index dbc9ba9b5..86235834f 100644
--- a/models/Base.ts
+++ b/models/Base.ts
@@ -1,7 +1,7 @@
import 'core-js/full/array/from-async';
import { HTTPClient } from 'koajax';
-import { githubClient, Issue as GitHubIssue, RepositoryModel } from 'mobx-github';
+import { githubClient, RepositoryModel } from 'mobx-github';
import { TableCellAttachment, TableCellMedia, TableCellValue } from 'mobx-lark';
import { DataObject } from 'mobx-restful';
import { isEmpty } from 'web-utility';
@@ -31,7 +31,6 @@ githubClient.use(({ request }, next) => {
});
export { githubClient };
-export type { GitHubIssue as Issue };
export const githubRawClient = new HTTPClient({
baseURI: `${ProxyBaseURL}/raw.githubusercontent.com/`,
diff --git a/package.json b/package.json
index 55148f9fc..59f35ef2c 100644
--- a/package.json
+++ b/package.json
@@ -7,78 +7,76 @@
"node": ">=22"
},
"dependencies": {
- "@koa/router": "^13.1.1",
- "@mdx-js/loader": "^3.1.0",
- "@mdx-js/react": "^3.1.0",
- "@next/mdx": "^15.4.1",
- "@sentry/nextjs": "^9.39.0",
- "copy-webpack-plugin": "^13.0.0",
- "core-js": "^3.44.0",
+ "@mdx-js/loader": "^3.1.1",
+ "@mdx-js/react": "^3.1.1",
+ "@next/mdx": "^15.5.2",
+ "@sentry/nextjs": "^10.10.0",
+ "copy-webpack-plugin": "^13.0.1",
+ "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",
- "less": "^4.3.0",
+ "less": "^4.4.1",
"less-loader": "^12.3.0",
"lodash": "^4.17.21",
- "marked": "^16.0.0",
+ "marked": "^16.2.1",
"mime": "^4.0.7",
"mobx": "^6.13.7",
- "mobx-github": "^0.5.0",
+ "mobx-github": "^0.5.1",
"mobx-i18n": "^0.7.1",
- "mobx-lark": "^2.2.0",
+ "mobx-lark": "^2.4.1",
"mobx-react": "^9.2.0",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.0",
- "mobx-restful-table": "^2.5.2",
- "next": "^15.4.1",
+ "mobx-restful-table": "^2.5.3",
+ "next": "^15.5.2",
"next-pwa": "~5.6.0",
- "next-ssr-middleware": "^1.0.1",
+ "next-ssr-middleware": "^1.0.3",
"next-with-less": "^3.0.1",
- "react": "^19.1.0",
+ "react": "^19.1.1",
"react-bootstrap": "^2.10.10",
"react-bootstrap-editor": "^2.1.1",
- "react-dom": "^19.1.0",
+ "react-dom": "^19.1.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.2.0",
- "undici": "^7.11.0",
- "web-utility": "^4.4.3",
- "webpack": "^5.100.2",
- "yaml": "^2.8.0"
+ "undici": "^7.15.0",
+ "web-utility": "^4.5.3",
+ "webpack": "^5.101.3",
+ "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/compat": "^1.3.1",
+ "@cspell/eslint-plugin": "^9.2.1",
+ "@eslint/compat": "^1.3.2",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "^9.31.0",
- "@next/eslint-plugin-next": "^15.4.1",
+ "@eslint/js": "^9.35.0",
+ "@next/eslint-plugin-next": "^15.5.2",
"@octokit/openapi-types": "^25.1.0",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
- "@stylistic/eslint-plugin": "^5.1.0",
+ "@stylistic/eslint-plugin": "^5.3.1",
"@types/eslint-config-prettier": "^6.11.3",
- "@types/koa": "^2.15.0",
- "@types/koa__router": "^12.0.4",
+ "@types/koa": "^3.0.0",
"@types/lodash": "^4.17.20",
"@types/next-pwa": "^5.6.9",
- "@types/node": "^22.16.4",
- "@types/react": "^19.1.8",
- "eslint": "^9.31.0",
- "eslint-config-next": "^15.4.1",
- "eslint-config-prettier": "^10.1.5",
+ "@types/node": "^22.18.1",
+ "@types/react": "^19.1.12",
+ "eslint": "^9.35.0",
+ "eslint-config-next": "^15.5.2",
+ "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",
- "lint-staged": "^16.1.2",
+ "jiti": "^2.5.1",
+ "lint-staged": "^16.1.6",
"prettier": "^3.6.2",
"prettier-plugin-css-order": "^2.1.2",
- "typescript": "~5.8.3",
- "typescript-eslint": "^8.37.0"
+ "typescript": "~5.9.2",
+ "typescript-eslint": "^8.42.0"
},
"resolutions": {
"next": "$next"
diff --git a/pages/api/Lark/bitable/v1/[...slug].ts b/pages/api/Lark/bitable/v1/[...slug].ts
index bf1a48133..b325e008e 100644
--- a/pages/api/Lark/bitable/v1/[...slug].ts
+++ b/pages/api/Lark/bitable/v1/[...slug].ts
@@ -1,8 +1,9 @@
+import { Context } from 'koa';
import { LarkPageData, TableRecord, TableRecordData } from 'mobx-lark';
import { DataObject } from 'mobx-restful';
-import { createKoaRouter } from 'next-ssr-middleware';
+import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware';
-import { withSafeKoaRouter } from '../../../core';
+import { safeAPI } from '../../../core';
import { proxyLark, proxyLarkAll } from '../../core';
export const config = { api: { bodyParser: false } };
@@ -10,13 +11,11 @@ export const config = { api: { bodyParser: false } };
const router = createKoaRouter(import.meta.url);
function filterData(fields: DataObject) {
- for (const key of Object.keys(fields))
- if (!/^\w+$/.test(key)) delete fields[key];
+ for (const key of Object.keys(fields)) if (!/^\w+$/.test(key)) delete fields[key];
}
-router.get('/apps/:app/tables/:table/records/:record', async context => {
- const { status, body } =
- await proxyLark>(context);
+router.get('/apps/:app/tables/:table/records/:record', safeAPI, async (context: Context) => {
+ const { status, body } = await proxyLark>(context);
const { fields } = body!.data!.record;
@@ -26,9 +25,8 @@ router.get('/apps/:app/tables/:table/records/:record', async context => {
context.body = body;
});
-router.get('/apps/:app/tables/:table/records', async context => {
- const { status, body } =
- await proxyLark>>(context);
+router.get('/apps/:app/tables/:table/records', safeAPI, async (context: Context) => {
+ const { status, body } = await proxyLark>>(context);
const list = body!.data!.items || [];
@@ -38,6 +36,6 @@ router.get('/apps/:app/tables/:table/records', async context => {
context.body = body;
});
-router.all('/(.*)', proxyLarkAll);
+router.all('/(.*)', safeAPI, proxyLarkAll);
-export default withSafeKoaRouter(router);
+export default withKoaRouter(router);
diff --git a/pages/api/Lark/file/[id]/[name].ts b/pages/api/Lark/file/[id]/[name].ts
index 393786a87..ef67c2173 100644
--- a/pages/api/Lark/file/[id]/[name].ts
+++ b/pages/api/Lark/file/[id]/[name].ts
@@ -1,15 +1,16 @@
import { fileTypeFromStream } from 'file-type';
+import { Middleware } from 'koa';
import MIME from 'mime';
-import { createKoaRouter } from 'next-ssr-middleware';
+import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware';
import { Readable } from 'stream';
import { CACHE_HOST } from '../../../../../models/configuration';
-import { withSafeKoaRouter } from '../../../core';
+import { safeAPI } from '../../../core';
import { lark } from '../../core';
const router = createKoaRouter(import.meta.url);
-router.all('/:id/:name', async context => {
+const downloader: Middleware = async context => {
const { method, url, params, query } = context;
const { id, name } = params;
@@ -21,10 +22,9 @@ router.all('/:id/:name', async context => {
const token = await lark.getAccessToken();
- const response = await fetch(
- lark.client.baseURI + `drive/v1/medias/${id}/download`,
- { headers: { Authorization: `Bearer ${token}` } },
- );
+ const response = await fetch(lark.client.baseURI + `drive/v1/medias/${id}/download`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
const { ok, status, headers, body } = response;
if (!ok) {
@@ -47,6 +47,8 @@ router.all('/:id/:name', async context => {
if (method === 'GET')
// @ts-expect-error Web type compatibility
context.body = Readable.fromWeb(stream2);
-});
+};
-export default withSafeKoaRouter(router);
+router.head('/:id/:name', safeAPI, downloader).get('/:id/:name', safeAPI, downloader);
+
+export default withKoaRouter(router);
diff --git a/pages/api/core.ts b/pages/api/core.ts
index 65bad72ce..f44eeaab7 100644
--- a/pages/api/core.ts
+++ b/pages/api/core.ts
@@ -1,10 +1,10 @@
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 { KoaOption, withKoa } from 'next-ssr-middleware';
+import { basename } from 'path';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { parse } from 'yaml';
@@ -46,11 +46,6 @@ export const safeAPI: Middleware = async (context: Context, next) => {
export const withSafeKoa = (...middlewares: Middleware[]) =>
withKoa({} as KoaOption, safeAPI, ...middlewares);
-export const withSafeKoaRouter = >(
- router: Router,
- ...middlewares: Middleware[]
-) => withKoaRouter({} as KoaOption, router, safeAPI, ...middlewares);
-
export interface ArticleMeta {
name: string;
path?: string;
@@ -58,20 +53,28 @@ export interface ArticleMeta {
subs: ArticleMeta[];
}
-const MDX_pattern = /\.mdx?$/;
+export const MD_pattern = /\.(md|markdown)$/i,
+ MDX_pattern = /\.mdx?$/i;
+
+export function splitFrontMatter(raw: string) {
+ const [, frontMatter, markdown] =
+ raw.trim().match(/^---[\r\n]([\s\S]+?[\r\n])---[\r\n]([\s\S]*)/) || [];
-export async function frontMatterOf(path: string) {
- const { readFile } = await import('fs/promises');
+ if (!frontMatter) return { markdown: raw };
- const file = await readFile(path, 'utf-8');
+ try {
+ const meta = parse(frontMatter) as DataObject;
- const [, frontMatter] = file.match(/^---[\r\n]([\s\S]+?[\r\n])---/) || [];
+ return { markdown, meta };
+ } catch (error) {
+ console.error(`Error parsing Front Matter:`, error);
- return frontMatter && parse(frontMatter);
+ return { markdown };
+ }
}
export async function* pageListOf(path: string, prefix = 'pages'): AsyncGenerator {
- const { readdir } = await import('fs/promises');
+ const { readdir, readFile } = await import('fs/promises');
const list = await readdir(prefix + path, { withFileTypes: true });
@@ -82,26 +85,27 @@ export async function* pageListOf(path: string, prefix = 'pages'): AsyncGenerato
const isMDX = MDX_pattern.test(name);
- name = name.replace(MDX_pattern, '');
+ name = basename(name);
path = `${path}/${name}`.replace(new RegExp(`^${prefix}`), '');
if (node.isFile()) {
- const article: ArticleMeta = { name: isMDX ? name : node.name, path, subs: [] };
+ const article: ArticleMeta = { name, path, subs: [] };
if (isMDX)
try {
- const meta = await frontMatterOf(`${node.path}/${node.name}`);
+ const rawFile = await readFile(`${node.path}/${node.name}`, { encoding: 'utf-8' });
+
+ const { meta } = splitFrontMatter(rawFile);
if (meta) article.meta = meta;
} catch (error) {
console.error(`Error reading front matter for ${node.path}/${node.name}:`, error);
}
-
yield article;
} else if (node.isDirectory()) {
const subs = await Array.fromAsync(pageListOf(path, prefix));
- if (subs.length) yield { name, subs };
+ if (subs[0]) yield { name, subs };
}
}
}
@@ -110,10 +114,13 @@ export type TreeNode = {
[key in K]: TreeNode[];
};
-export function* traverseTree(tree: TreeNode, key: K): Generator> {
+export function* traverseTree>(
+ tree: N,
+ key: K,
+): Generator {
for (const node of tree[key] || []) {
- yield node;
- yield* traverseTree(node, key);
+ yield node as N;
+ yield* traverseTree(node as N, key);
}
}
diff --git a/pages/index.tsx b/pages/index.tsx
index 22a3d93c9..9c0f15b4e 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -106,7 +106,10 @@ const HomePage: FC = observer(({ latestArticles, upcomingEvents,
-
+
+
diff --git a/pages/weekly/[id].tsx b/pages/weekly/[id].tsx
index 05977e650..eecc38327 100644
--- a/pages/weekly/[id].tsx
+++ b/pages/weekly/[id].tsx
@@ -1,13 +1,15 @@
+import '../../models/Base';
+
import { marked } from 'marked';
-import { IssueModel } from 'mobx-github';
+import { Issue, IssueModel } from 'mobx-github';
import { observer } from 'mobx-react';
import { GetStaticPaths, GetStaticProps } from 'next';
import { ParsedUrlQuery } from 'querystring';
import { FC, useContext } from 'react';
-import { Badge, Breadcrumb, Button, Card, Container } from 'react-bootstrap';
+import { Badge, Breadcrumb, Button, Card, Col, Container, Row } from 'react-bootstrap';
+import { LabelBar } from '../../components/Git/LabelBar';
import { PageHead } from '../../components/Layout/PageHead';
-import type { Issue } from '../../models/Base';
import { I18nContext } from '../../models/Translation';
import styles from '../../styles/Weekly.module.less';
@@ -15,25 +17,16 @@ interface WeeklyDetailParams extends ParsedUrlQuery {
id: string;
}
-interface WeeklyDetailProps {
- issue: Issue;
-}
-
export const getStaticPaths: GetStaticPaths
= async () => {
const list = await new IssueModel('FreeCodeCamp-Chengdu', 'IT-Technology-weekly').getAll({
state: 'all',
});
-
- const paths = list.map(issue => ({
- params: { id: issue.number.toString() },
- }));
+ const paths = list.map(({ number }) => ({ params: { id: number + '' } }));
return { paths, fallback: 'blocking' };
};
-export const getStaticProps: GetStaticProps = async ({
- params,
-}) => {
+export const getStaticProps: GetStaticProps = async ({ params }) => {
const { id } = params!;
const issue = await new IssueModel('FreeCodeCamp-Chengdu', 'IT-Technology-weekly').getOne(id);
@@ -43,120 +36,111 @@ export const getStaticProps: GetStaticProps = observer(({ issue }) => {
- const { t } = useContext(I18nContext);
- const htmlContent = issue.body ? (marked(issue.body) as string) : '';
-
- return (
-
-
-
-
- {t('home_page')}
- {t('weekly')}
- #{issue.number}
-
-
-
-