-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #168 from ChtiJS/refactor/app_router_migration
refactor(core): switch to the app router
- Loading branch information
Showing
41 changed files
with
1,201 additions
and
1,215 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { join as pathJoin } from 'path'; | ||
import ContentBlock from '../../components/contentBlock'; | ||
import Paragraph from '../../components/p'; | ||
import Anchor from '../../components/a'; | ||
import { parseMarkdown, renderMarkdown } from '../../utils/markdown'; | ||
import { readEntry, readPaths } from '../../utils/frontmatter'; | ||
import { toASCIIString } from '../../utils/ascii'; | ||
import buildMetadata from '../../utils/metadata'; | ||
import type { MarkdownRootNode } from '../../utils/markdown'; | ||
|
||
type Metadata = { | ||
date: string; | ||
title: string; | ||
description: string; | ||
author: string; | ||
illustration?: { | ||
href: string; | ||
alt: string; | ||
}; | ||
lang: string; | ||
location: string; | ||
}; | ||
type Entry = { | ||
id: string; | ||
content: MarkdownRootNode; | ||
} & Metadata; | ||
|
||
type Params = { id: string }; | ||
|
||
export async function generateMetadata({ params }: { params: Params }) { | ||
const result = await readEntry<Metadata>( | ||
pathJoin('contents', 'pages', (params?.id as string) + '.md') | ||
); | ||
const entry: Entry = { | ||
...result.attributes, | ||
id: toASCIIString(result.attributes.title), | ||
content: parseMarkdown(result.body) as MarkdownRootNode, | ||
}; | ||
|
||
return buildMetadata({ | ||
pathname: `/${entry.id}`, | ||
title: entry.title, | ||
description: entry.description, | ||
image: entry.illustration?.href, | ||
}); | ||
} | ||
|
||
export default async function Page({ params }: { params: Params }) { | ||
const result = await readEntry<Metadata>( | ||
pathJoin('contents', 'pages', (params?.id as string) + '.md') | ||
); | ||
const entry: Entry = { | ||
...result.attributes, | ||
id: toASCIIString(result.attributes.title), | ||
content: parseMarkdown(result.body) as MarkdownRootNode, | ||
}; | ||
|
||
return ( | ||
<ContentBlock> | ||
{renderMarkdown({ index: 0 }, entry.content)} | ||
<div className="clear"></div> | ||
<Paragraph> | ||
<Anchor href="/">Retour</Anchor> | ||
</Paragraph> | ||
</ContentBlock> | ||
); | ||
} | ||
|
||
export async function generateStaticParams() { | ||
const paths = (await readPaths(pathJoin('.', 'contents', 'pages'))).map( | ||
(path) => ({ | ||
id: path.replace('.md', ''), | ||
}) | ||
); | ||
|
||
return paths; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.root { | ||
:global(p.illustration) { | ||
float: left; | ||
width: var(--block); | ||
margin: 0 var(--gutter) 0 0; | ||
} | ||
img { | ||
width: 100%; | ||
margin: 0; | ||
} | ||
.clear { | ||
clear: both; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { join as pathJoin } from 'path'; | ||
import { entriesToBaseProps } from '../page'; | ||
import { readEntries } from '../../../utils/frontmatter'; | ||
import ContentBlock from '../../../components/contentBlock'; | ||
import Paragraph from '../../../components/p'; | ||
import Anchor from '../../../components/a'; | ||
import { fixText } from '../../../utils/text'; | ||
import { renderMarkdown } from '../../../utils/markdown'; | ||
import buildMetadata from '../../../utils/metadata'; | ||
import styles from './conference.module.scss'; | ||
import type { Metadata } from '../page'; | ||
import type { Entry } from '../page'; | ||
|
||
type Params = { id: string }; | ||
|
||
export async function generateMetadata({ params }: { params: Params }) { | ||
const baseProps = entriesToBaseProps( | ||
await readEntries<Metadata>(pathJoin('.', 'contents', 'conferences')) | ||
); | ||
const entry = baseProps.entries.find( | ||
({ id }) => id === (params || {}).id | ||
) as Entry; | ||
|
||
return buildMetadata({ | ||
title: fixText(entry.title), | ||
description: fixText(entry.description), | ||
image: entry.illustration?.url, | ||
pathname: `/conference/${params.id}`, | ||
}); | ||
} | ||
|
||
export async function BlogPost({ params }: { params: Params }) { | ||
const baseProps = entriesToBaseProps( | ||
await readEntries<Metadata>(pathJoin('.', 'contents', 'conferences')) | ||
); | ||
const entry = baseProps.entries.find( | ||
({ id }) => id === (params || {}).id | ||
) as Entry; | ||
|
||
return ( | ||
<ContentBlock className={styles.root}> | ||
{renderMarkdown({ index: 0 }, entry.content)} | ||
<Paragraph> | ||
Publié le{' '} | ||
{new Date(entry.date).toLocaleDateString(undefined, { | ||
weekday: 'long', | ||
year: 'numeric', | ||
month: 'long', | ||
day: 'numeric', | ||
})} | ||
. | ||
</Paragraph> | ||
<div className={styles.clear}></div> | ||
<Paragraph> | ||
<Anchor href="/conferences">Retour</Anchor> | ||
</Paragraph> | ||
</ContentBlock> | ||
); | ||
} | ||
|
||
export default BlogPost; | ||
|
||
export async function generateStaticParams() { | ||
const baseProps = entriesToBaseProps( | ||
await readEntries<Metadata>(pathJoin('.', 'contents', 'conferences')) | ||
); | ||
const paths = baseProps.entries.map((entry) => ({ | ||
id: entry.id, | ||
})); | ||
|
||
return paths; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
@import '../../variables.scss'; | ||
|
||
.root { | ||
img { | ||
width: 100%; | ||
} | ||
label { | ||
display: flex; | ||
flex-direction: row; | ||
align-items: flex-start; | ||
gap: var(--gutter); | ||
font-weight: bold; | ||
} | ||
input { | ||
height: var(--vRythm); | ||
width: var(--block); | ||
padding: 0 calc(var(--gutter) / 2); | ||
} | ||
:global(.entry_title) { | ||
margin-top: 0 !important; | ||
} | ||
:global(.entry_title a) { | ||
text-decoration: none !important; | ||
} | ||
:global(.entry_illustration) { | ||
margin: 0 !important; | ||
} | ||
:global(.entry_description) { | ||
margin: 0 !important; | ||
} | ||
} | ||
.entry_item { | ||
padding: var(--vRythm) 0; | ||
border-bottom: var(--border) solid var(--dark); | ||
} | ||
.entry_item:first-child { | ||
padding: 0 0 var(--vRythm) 0; | ||
} | ||
.entry_item:last-child { | ||
border: none; | ||
padding: var(--vRythm) 0 0 0; | ||
} | ||
.pagination { | ||
display: flex; | ||
gap: var(--gutter); | ||
align-items: center; | ||
justify-content: center; | ||
padding: var(--vRythm) 0 0 0; | ||
} | ||
@media screen and (min-width: $CSS_BREAKPOINT_START_L) { | ||
.root { | ||
img { | ||
float: left; | ||
width: var(--block); | ||
margin-right: var(--gutter); | ||
} | ||
.clear { | ||
clear: left; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
'use client'; | ||
|
||
import styles from './conferences.module.scss'; | ||
import { useEffect, useState } from 'react'; | ||
import useSWR from 'swr'; | ||
import lunr, { type Index } from 'lunr'; | ||
import ContentBlock from '../../components/contentBlock'; | ||
import Heading1 from '../../components/h1'; | ||
import Heading2 from '../../components/h2'; | ||
import Paragraph from '../../components/p'; | ||
import Anchor from '../../components/a'; | ||
import type { MarkdownRootNode } from '../../utils/markdown'; | ||
|
||
export type Metadata = { | ||
leafname?: string; | ||
title: string; | ||
description: string; | ||
date: string; | ||
draft: boolean; | ||
tags: string[]; | ||
categories: string[]; | ||
illustration?: { | ||
url: string; | ||
alt: string; | ||
}; | ||
lang: string; | ||
location: string; | ||
}; | ||
export type Entry = { | ||
id: string; | ||
content: MarkdownRootNode; | ||
} & Metadata; | ||
|
||
export type EntriesProps = { | ||
entries: Entry[]; | ||
pagesCount: number; | ||
page: number; | ||
}; | ||
|
||
const fetcher = (url: string) => fetch(url).then((res) => res.json()); | ||
|
||
export const Entries = ({ entries, page, pagesCount }: EntriesProps) => { | ||
const [search, setSearch] = useState(''); | ||
const [searchIndex, setSearchIndex] = useState<Index>(); | ||
const [searchResults, setSearchResults] = useState< | ||
{ id: string; title: string; description: string }[] | ||
>([]); | ||
const { data, error, isLoading } = useSWR( | ||
// Only load the search data when searching 🤷 | ||
search ? '/conferencesSearchIndex.json' : null, | ||
fetcher | ||
); | ||
|
||
useEffect(() => { | ||
if (data) { | ||
setSearchIndex(lunr.Index.load(data.index)); | ||
} else { | ||
setSearchIndex(undefined); | ||
} | ||
}, [data]); | ||
|
||
useEffect(() => { | ||
if (searchIndex && search) { | ||
setSearchResults( | ||
searchIndex.search(search).map((result) => ({ | ||
result, | ||
id: result.ref, | ||
...data.metadata[result.ref], | ||
})) | ||
); | ||
} else { | ||
setSearchResults([]); | ||
} | ||
}, [data, searchIndex, search]); | ||
|
||
return ( | ||
<ContentBlock className={styles.root}> | ||
<Heading1>Résumés des rencontres</Heading1> | ||
<Paragraph> | ||
Découvrez le résumé de chacune de nos rencontres ChtiJS. | ||
</Paragraph> | ||
{page === 1 ? ( | ||
<Paragraph> | ||
<label> | ||
<span>Recherche :</span> | ||
<input | ||
type="search" | ||
placeholder="Rechercher une conference" | ||
value={search} | ||
onChange={(e) => { | ||
setSearch(e.target.value); | ||
}} | ||
/> | ||
</label> | ||
</Paragraph> | ||
) : null} | ||
{search ? ( | ||
error ? ( | ||
<Paragraph>Impossible de charger l’index de recherche.</Paragraph> | ||
) : isLoading ? ( | ||
<Paragraph>Chargement de l’index de recherche...</Paragraph> | ||
) : searchResults.length ? ( | ||
<Paragraph> | ||
{searchResults.length} résultat | ||
{searchResults.length > 1 ? 's' : ''} pour la recherche “{search} | ||
”. | ||
</Paragraph> | ||
) : ( | ||
<Paragraph>Aucun résultat pour la recherche “{search}”.</Paragraph> | ||
) | ||
) : null} | ||
<div> | ||
{( | ||
(search ? searchResults : entries) as Pick< | ||
Entry, | ||
'id' | 'title' | 'description' | 'illustration' | ||
>[] | ||
).map((entry) => ( | ||
<div className={styles.entry_item} key={entry.id}> | ||
{entry.illustration ? ( | ||
<Paragraph className={styles.entry_illustration}> | ||
<Anchor href={`/conferences/${entry.id}`}> | ||
<img | ||
src={entry.illustration.url} | ||
alt={entry.illustration.alt} | ||
/> | ||
</Anchor> | ||
</Paragraph> | ||
) : null} | ||
<Heading2 className={styles.entry_title}> | ||
<Anchor href={`/conferences/${entry.id}`}>{entry.title}</Anchor> | ||
</Heading2> | ||
<Paragraph className={styles.entry_title}> | ||
{entry.description} | ||
</Paragraph> | ||
<Anchor href={`/conferences/${entry.id}`}>Lire la suite</Anchor> | ||
<div className={styles.clear}></div> | ||
</div> | ||
))} | ||
</div> | ||
{!search ? ( | ||
<nav className={styles.pagination}> | ||
{page > 1 ? ( | ||
<Anchor | ||
icon="arrow-left" | ||
href={ | ||
page > 2 ? `/conferences/pages/${page - 1}` : '/conferences' | ||
} | ||
rel="previous" | ||
> | ||
Précédent | ||
</Anchor> | ||
) : null}{' '} | ||
{page < pagesCount ? ( | ||
<Anchor | ||
icon="arrow-right" | ||
iconPosition="last" | ||
href={`/conferences/pages/${page + 1}`} | ||
rel="next" | ||
> | ||
Suivant | ||
</Anchor> | ||
) : null} | ||
</nav> | ||
) : null} | ||
</ContentBlock> | ||
); | ||
}; |
Oops, something went wrong.