From 2e5ce5286738f4867b2eba1ecc684c53f7a524bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:54:01 +0000 Subject: [PATCH 1/4] Initial plan From 0195cb0091d330391869a2508598e10c01621203 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:09:58 +0000 Subject: [PATCH 2/4] Add complete recipes section for CookLikeHOC repository Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- models/Wiki.ts | 2 + pages/recipes/[...slug].tsx | 152 ++++++++++++++++++++++++++++++++++++ pages/recipes/index.tsx | 89 +++++++++++++++++++++ translation/en-US.ts | 6 ++ translation/zh-CN.ts | 6 ++ translation/zh-TW.ts | 6 ++ 6 files changed, 261 insertions(+) create mode 100644 pages/recipes/[...slug].tsx create mode 100644 pages/recipes/index.tsx diff --git a/models/Wiki.ts b/models/Wiki.ts index 36648bb..d4448ae 100644 --- a/models/Wiki.ts +++ b/models/Wiki.ts @@ -13,6 +13,8 @@ export interface XContent extends Content { export const policyContentStore = new ContentModel('fpsig', 'open-source-policy'); +export const recipeContentStore = new ContentModel('Gar-b-age', 'CookLikeHOC'); + export class MyWikiNodeModel extends WikiNodeModel { client = lark.client; } diff --git a/pages/recipes/[...slug].tsx b/pages/recipes/[...slug].tsx new file mode 100644 index 0000000..e19761b --- /dev/null +++ b/pages/recipes/[...slug].tsx @@ -0,0 +1,152 @@ +import { marked } from 'marked'; +import { observer } from 'mobx-react'; +import { GetStaticPaths, GetStaticProps } from 'next'; +import { ParsedUrlQuery } from 'querystring'; +import { FC, useContext } from 'react'; +import { Badge, Breadcrumb, Button, Container } from 'react-bootstrap'; +import { decodeBase64 } from 'web-utility'; + +import { PageHead } from '../../components/Layout/PageHead'; +import { I18nContext } from '../../models/Translation'; +import { recipeContentStore, XContent } from '../../models/Wiki'; +import { splitFrontMatter } from '../api/core'; + +interface RecipePageParams extends ParsedUrlQuery { + slug: string[]; +} + +export const getStaticPaths: GetStaticPaths = async () => { + const nodes = await recipeContentStore.getAll(); + + const paths = nodes + .filter(({ type }) => type === 'file') + .map(({ path }) => ({ params: { slug: path.split('/') } })); + + return { paths, fallback: 'blocking' }; +}; + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const { slug } = params!; + + const node = await recipeContentStore.getOne(slug.join('/')); + + const { meta, markdown } = splitFrontMatter(decodeBase64(node.content!)); + + const markup = marked(markdown) as string; + + return { + props: JSON.parse(JSON.stringify({ ...node, content: markup, meta })), + revalidate: 300, // Revalidate every 5 minutes + }; +}; + +const RecipePage: FC = observer(({ name, path, parent_path, content, meta }) => { + const { t } = useContext(I18nContext); + + return ( + + + + + {t('recipes')} + + {parent_path?.split('/').map((segment, index, array) => { + const breadcrumbPath = array.slice(0, index + 1).join('/'); + + return ( + + {segment} + + ); + })} + {name} + + +
+
+

{name}

+ + {meta && ( +
+
    + {meta['category'] && ( +
  • + {meta['category']} +
  • + )} + {meta['difficulty'] && ( +
  • + {meta['difficulty']} +
  • + )} + {meta['time'] && ( +
  • + {meta['time']} +
  • + )} +
+
+ )} + +
+
+ {meta?.['servings'] && ( + + {t('servings')}: {meta['servings']} + + )} + {meta?.['prep_time'] && ( + + {t('prep_time')}: {meta['prep_time']} + + )} +
+ +
+ + {meta?.url && ( + + )} +
+
+
+ +
+
+ + +
+ ); +}); + +export default RecipePage; \ No newline at end of file diff --git a/pages/recipes/index.tsx b/pages/recipes/index.tsx new file mode 100644 index 0000000..ded7d82 --- /dev/null +++ b/pages/recipes/index.tsx @@ -0,0 +1,89 @@ +import { observer } from 'mobx-react'; +import { GetStaticProps } from 'next'; +import Link from 'next/link'; +import React, { FC, useContext } from 'react'; +import { Badge, Button, Card, Container } from 'react-bootstrap'; +import { treeFrom } from 'web-utility'; + +import { PageHead } from '../../components/Layout/PageHead'; +import { I18nContext } from '../../models/Translation'; +import { recipeContentStore, XContent } from '../../models/Wiki'; +import { MD_pattern, splitFrontMatter } from '../api/core'; + +export const getStaticProps: GetStaticProps<{ nodes: XContent[] }> = async () => { + const nodes = (await recipeContentStore.getAll()) + .filter(({ type, name }) => type !== 'file' || MD_pattern.test(name)) + .map(({ content, ...rest }) => { + const { meta, markdown } = content ? splitFrontMatter(content) : {}; + + return { ...rest, content: markdown, meta }; + }); + + return { + props: JSON.parse(JSON.stringify({ nodes })), + revalidate: 300, // Revalidate every 5 minutes + }; +}; + +const renderTree = (nodes: XContent[], level = 0) => ( +
    + {nodes.map(({ path, name, type, meta, children }) => ( +
  1. 0 ? 'ms-3' : ''}> + {type !== 'dir' ? ( + + {name} + + {meta?.['category'] && ( + + {meta['category']} + + )} + + ) : ( +
    + {name} + + {renderTree(children || [], level + 1)} +
    + )} +
  2. + ))} +
+); + +const RecipeIndexPage: FC<{ nodes: XContent[] }> = observer(({ nodes }) => { + const { t } = useContext(I18nContext); + + return ( + + + +
+

+ {t('recipes')} ({nodes.length}) +

+ +
+ + {nodes[0] ? ( + renderTree(treeFrom(nodes, 'path', 'parent_path', 'children')) + ) : ( + + +

{t('no_docs_available')}

+

{t('docs_auto_load_from_github')}

+
+
+ )} +
+ ); +}); + +export default RecipeIndexPage; \ No newline at end of file diff --git a/translation/en-US.ts b/translation/en-US.ts index c168b10..d73712a 100644 --- a/translation/en-US.ts +++ b/translation/en-US.ts @@ -102,6 +102,12 @@ export default { github_document_description: 'This is a document page based on a GitHub repository.', view_or_edit_on_github: 'View or edit this content on GitHub', + // Recipes + recipes: 'Recipes', + servings: 'Servings', + prep_time: 'Prep Time', + github_recipe_description: 'This is a recipe page based on a GitHub repository.', + // China NGO Map NGO: 'NGO', China_NGO_DB: 'China NGO Database', diff --git a/translation/zh-CN.ts b/translation/zh-CN.ts index 79323d9..bcf5851 100644 --- a/translation/zh-CN.ts +++ b/translation/zh-CN.ts @@ -100,6 +100,12 @@ export default { github_document_description: '这是一个基于 GitHub 仓库的文档页面。', view_or_edit_on_github: '在 GitHub 上查看或编辑此内容', + // Recipes + recipes: '菜谱', + servings: '份数', + prep_time: '准备时间', + github_recipe_description: '这是一个基于 GitHub 仓库的菜谱页面。', + // China Public Interest Map NGO: '公益', China_NGO_DB: '中国公益数据库', diff --git a/translation/zh-TW.ts b/translation/zh-TW.ts index f1a0612..9d0cfe4 100644 --- a/translation/zh-TW.ts +++ b/translation/zh-TW.ts @@ -100,6 +100,12 @@ export default { github_document_description: '這是一個基於 GitHub 存儲庫的文檔頁面。', view_or_edit_on_github: '在 GitHub 上查看或編輯此內容', + // Recipes + recipes: '菜譜', + servings: '份數', + prep_time: '準備時間', + github_recipe_description: '這是一個基於 GitHub 存儲庫的菜譜頁面。', + // China Public Interest Map NGO: '公益', China_NGO_DB: '中國公益數據庫', From 35e585b32c30657c1e323bb53133116283a062ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:27:18 +0000 Subject: [PATCH 3/4] Address code review feedback: add navigation link, acknowledgment, shared components, and BadgeBar Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- components/Layout/ContentTree.tsx | 38 ++++++++++++++++++++ components/Navigator/MainNavigator.tsx | 1 + pages/policy/[...slug].tsx | 32 +++++++---------- pages/policy/index.tsx | 36 ++++--------------- pages/recipes/[...slug].tsx | 29 ++++++--------- pages/recipes/index.tsx | 49 +++++++++++--------------- translation/en-US.ts | 1 - translation/zh-CN.ts | 1 - translation/zh-TW.ts | 1 - 9 files changed, 88 insertions(+), 100 deletions(-) create mode 100644 components/Layout/ContentTree.tsx diff --git a/components/Layout/ContentTree.tsx b/components/Layout/ContentTree.tsx new file mode 100644 index 0000000..f503248 --- /dev/null +++ b/components/Layout/ContentTree.tsx @@ -0,0 +1,38 @@ +import Link from 'next/link'; +import { FC } from 'react'; +import { Badge } from 'react-bootstrap'; + +import { XContent } from '../../models/Wiki'; + +interface ContentTreeProps { + nodes: XContent[]; + basePath: string; + level?: number; + metaKey?: string; +} + +export const ContentTree: FC = ({ nodes, basePath, level = 0, metaKey = '主题分类' }) => ( +
    + {nodes.map(({ path, name, type, meta, children }) => ( +
  1. 0 ? 'ms-3' : ''}> + {type !== 'dir' ? ( + + {name} + + {meta?.[metaKey] && ( + + {meta[metaKey]} + + )} + + ) : ( +
    + {name} + + +
    + )} +
  2. + ))} +
+); \ No newline at end of file diff --git a/components/Navigator/MainNavigator.tsx b/components/Navigator/MainNavigator.tsx index 58b8d41..7aef71f 100644 --- a/components/Navigator/MainNavigator.tsx +++ b/components/Navigator/MainNavigator.tsx @@ -60,6 +60,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [ subs: [ { href: '/wiki', title: t('wiki') }, { href: '/policy', title: t('policy') }, + { href: '/recipes', title: t('recipes') }, ], }, ]; diff --git a/pages/policy/[...slug].tsx b/pages/policy/[...slug].tsx index 59528bd..3fb5ac1 100644 --- a/pages/policy/[...slug].tsx +++ b/pages/policy/[...slug].tsx @@ -1,9 +1,10 @@ import { marked } from 'marked'; import { observer } from 'mobx-react'; +import { BadgeBar } from 'mobx-restful-table'; import { GetStaticPaths, GetStaticProps } from 'next'; import { ParsedUrlQuery } from 'querystring'; import { FC, useContext } from 'react'; -import { Badge, Breadcrumb, Button, Container } from 'react-bootstrap'; +import { Breadcrumb, Button, Container } from 'react-bootstrap'; import { decodeBase64 } from 'web-utility'; import { PageHead } from '../../components/Layout/PageHead'; @@ -68,25 +69,16 @@ const WikiPage: FC = observer(({ name, path, parent_path, content, met {meta && (
-
    - {meta['主题分类'] && ( -
  • - {meta['主题分类']} -
  • - )} - {meta['发文机构'] && ( -
  • - {meta['发文机构']} -
  • - )} - {meta['有效性'] && ( -
  • - - {meta['有效性']} - -
  • - )} -
+ } + />
)} diff --git a/pages/policy/index.tsx b/pages/policy/index.tsx index 5262371..d15ef81 100644 --- a/pages/policy/index.tsx +++ b/pages/policy/index.tsx @@ -1,10 +1,10 @@ import { observer } from 'mobx-react'; import { GetStaticProps } from 'next'; -import Link from 'next/link'; import React, { FC, useContext } from 'react'; -import { Badge, Button, Card, Container } from 'react-bootstrap'; +import { Button, Card, Container } from 'react-bootstrap'; import { treeFrom } from 'web-utility'; +import { ContentTree } from '../../components/Layout/ContentTree'; import { PageHead } from '../../components/Layout/PageHead'; import { I18nContext } from '../../models/Translation'; import { policyContentStore, XContent } from '../../models/Wiki'; @@ -25,32 +25,6 @@ export const getStaticProps: GetStaticProps<{ nodes: XContent[] }> = async () => }; }; -const renderTree = (nodes: XContent[], level = 0) => ( -
    - {nodes.map(({ path, name, type, meta, children }) => ( -
  1. 0 ? 'ms-3' : ''}> - {type !== 'dir' ? ( - - {name} - - {meta?.['主题分类'] && ( - - {meta['主题分类']} - - )} - - ) : ( -
    - {name} - - {renderTree(children || [], level + 1)} -
    - )} -
  2. - ))} -
-); - const WikiIndexPage: FC<{ nodes: XContent[] }> = observer(({ nodes }) => { const { t } = useContext(I18nContext); @@ -73,7 +47,11 @@ const WikiIndexPage: FC<{ nodes: XContent[] }> = observer(({ nodes }) => { {nodes[0] ? ( - renderTree(treeFrom(nodes, 'path', 'parent_path', 'children')) + ) : ( diff --git a/pages/recipes/[...slug].tsx b/pages/recipes/[...slug].tsx index e19761b..7ee2223 100644 --- a/pages/recipes/[...slug].tsx +++ b/pages/recipes/[...slug].tsx @@ -1,9 +1,10 @@ import { marked } from 'marked'; import { observer } from 'mobx-react'; +import { BadgeBar } from 'mobx-restful-table'; import { GetStaticPaths, GetStaticProps } from 'next'; import { ParsedUrlQuery } from 'querystring'; import { FC, useContext } from 'react'; -import { Badge, Breadcrumb, Button, Container } from 'react-bootstrap'; +import { Breadcrumb, Button, Container } from 'react-bootstrap'; import { decodeBase64 } from 'web-utility'; import { PageHead } from '../../components/Layout/PageHead'; @@ -68,23 +69,13 @@ const RecipePage: FC = observer(({ name, path, parent_path, content, m {meta && (
-
    - {meta['category'] && ( -
  • - {meta['category']} -
  • - )} - {meta['difficulty'] && ( -
  • - {meta['difficulty']} -
  • - )} - {meta['time'] && ( -
  • - {meta['time']} -
  • - )} -
+ } + />
)} @@ -133,7 +124,7 @@ const RecipePage: FC = observer(({ name, path, parent_path, content, m