1+ import { marked } from 'marked' ;
2+ import { observer } from 'mobx-react' ;
3+ import { GetStaticPaths , GetStaticProps } from 'next' ;
4+ import { ParsedUrlQuery } from 'querystring' ;
5+ import { FC , useContext } from 'react' ;
6+ import { Badge , Breadcrumb , Button , Container } from 'react-bootstrap' ;
7+ import { decodeBase64 } from 'web-utility' ;
8+
9+ import { PageHead } from '../../components/Layout/PageHead' ;
10+ import { I18nContext } from '../../models/Translation' ;
11+ import { recipeContentStore , XContent } from '../../models/Wiki' ;
12+ import { splitFrontMatter } from '../api/core' ;
13+
14+ interface RecipePageParams extends ParsedUrlQuery {
15+ slug : string [ ] ;
16+ }
17+
18+ export const getStaticPaths : GetStaticPaths < RecipePageParams > = async ( ) => {
19+ const nodes = await recipeContentStore . getAll ( ) ;
20+
21+ const paths = nodes
22+ . filter ( ( { type } ) => type === 'file' )
23+ . map ( ( { path } ) => ( { params : { slug : path . split ( '/' ) } } ) ) ;
24+
25+ return { paths, fallback : 'blocking' } ;
26+ } ;
27+
28+ export const getStaticProps : GetStaticProps < XContent , RecipePageParams > = async ( { params } ) => {
29+ const { slug } = params ! ;
30+
31+ const node = await recipeContentStore . getOne ( slug . join ( '/' ) ) ;
32+
33+ const { meta, markdown } = splitFrontMatter ( decodeBase64 ( node . content ! ) ) ;
34+
35+ const markup = marked ( markdown ) as string ;
36+
37+ return {
38+ props : JSON . parse ( JSON . stringify ( { ...node , content : markup , meta } ) ) ,
39+ revalidate : 300 , // Revalidate every 5 minutes
40+ } ;
41+ } ;
42+
43+ const RecipePage : FC < XContent > = observer ( ( { name, path, parent_path, content, meta } ) => {
44+ const { t } = useContext ( I18nContext ) ;
45+
46+ return (
47+ < Container className = "py-4" >
48+ < PageHead title = { name } />
49+
50+ < Breadcrumb className = "mb-4" >
51+ < Breadcrumb . Item href = "/recipes" > { t ( 'recipes' ) } </ Breadcrumb . Item >
52+
53+ { parent_path ?. split ( '/' ) . map ( ( segment , index , array ) => {
54+ const breadcrumbPath = array . slice ( 0 , index + 1 ) . join ( '/' ) ;
55+
56+ return (
57+ < Breadcrumb . Item key = { breadcrumbPath } href = { `/recipes/${ breadcrumbPath } ` } >
58+ { segment }
59+ </ Breadcrumb . Item >
60+ ) ;
61+ } ) }
62+ < Breadcrumb . Item active > { name } </ Breadcrumb . Item >
63+ </ Breadcrumb >
64+
65+ < article >
66+ < header className = "mb-4" >
67+ < h1 > { name } </ h1 >
68+
69+ { meta && (
70+ < div className = "d-flex flex-wrap align-items-center gap-3 mb-3" >
71+ < ul className = "mb-0" >
72+ { meta [ 'category' ] && (
73+ < li >
74+ < Badge bg = "primary" > { meta [ 'category' ] } </ Badge >
75+ </ li >
76+ ) }
77+ { meta [ 'difficulty' ] && (
78+ < li >
79+ < Badge bg = "secondary" > { meta [ 'difficulty' ] } </ Badge >
80+ </ li >
81+ ) }
82+ { meta [ 'time' ] && (
83+ < li >
84+ < Badge bg = "success" > { meta [ 'time' ] } </ Badge >
85+ </ li >
86+ ) }
87+ </ ul >
88+ </ div >
89+ ) }
90+
91+ < div className = "d-flex justify-content-between align-items-center text-muted small mb-3" >
92+ < div >
93+ { meta ?. [ 'servings' ] && (
94+ < span >
95+ { t ( 'servings' ) } : { meta [ 'servings' ] }
96+ </ span >
97+ ) }
98+ { meta ?. [ 'prep_time' ] && (
99+ < span className = "ms-3" >
100+ { t ( 'prep_time' ) } : { meta [ 'prep_time' ] }
101+ </ span >
102+ ) }
103+ </ div >
104+
105+ < div className = "d-flex gap-2" >
106+ < Button
107+ variant = "outline-primary"
108+ size = "sm"
109+ href = { `https://github.com/Gar-b-age/CookLikeHOC/blob/main/${ path } ` }
110+ target = "_blank"
111+ rel = "noopener noreferrer"
112+ >
113+ { t ( 'edit_on_github' ) }
114+ </ Button >
115+ { meta ?. url && (
116+ < Button
117+ variant = "outline-secondary"
118+ size = "sm"
119+ href = { meta . url }
120+ target = "_blank"
121+ rel = "noopener noreferrer"
122+ >
123+ { t ( 'view_original' ) }
124+ </ Button >
125+ ) }
126+ </ div >
127+ </ div >
128+ </ header >
129+
130+ < div dangerouslySetInnerHTML = { { __html : content || '' } } className = "markdown-body" />
131+ </ article >
132+
133+ < footer className = "mt-5 pt-4 border-top" >
134+ < div className = "text-center" >
135+ < p className = "text-muted" >
136+ { t ( 'github_recipe_description' ) }
137+ < a
138+ href = { `https://github.com/Gar-b-age/CookLikeHOC/blob/main/${ path } ` }
139+ target = "_blank"
140+ rel = "noopener noreferrer"
141+ className = "ms-2"
142+ >
143+ { t ( 'view_or_edit_on_github' ) }
144+ </ a >
145+ </ p >
146+ </ div >
147+ </ footer >
148+ </ Container >
149+ ) ;
150+ } ) ;
151+
152+ export default RecipePage ;
0 commit comments