diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-20 12:27:46 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-20 19:32:09 +0100 |
| commit | 70b4f633a6fbedb58c8b9134ac64ede854d489de (patch) | |
| tree | c757bb12ad9a588e23b25cdb8b46710ac14dbcb1 /src/pages | |
| parent | 9a481f066e1427d53a06cf7aeec525a745abf03f (diff) | |
refactor(components): replace PageLayout template with Page
* split pages in smaller components (it is both easier to maintain and
more readable, we avoid the use of fragments in pages directory)
* extract breadcrumbs from article tag (the navigation is not related
to the page contents)
* remove useReadingTime hook
* remove layout options except `isHome`
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/404.tsx | 80 | ||||
| -rw-r--r-- | src/pages/article/[slug].tsx | 216 | ||||
| -rw-r--r-- | src/pages/blog/index.tsx | 102 | ||||
| -rw-r--r-- | src/pages/blog/page/[number].tsx | 105 | ||||
| -rw-r--r-- | src/pages/contact.tsx | 91 | ||||
| -rw-r--r-- | src/pages/cv.tsx | 160 | ||||
| -rw-r--r-- | src/pages/index.tsx | 3 | ||||
| -rw-r--r-- | src/pages/mentions-legales.tsx | 79 | ||||
| -rw-r--r-- | src/pages/projets/[slug].tsx | 112 | ||||
| -rw-r--r-- | src/pages/projets/index.tsx | 28 | ||||
| -rw-r--r-- | src/pages/recherche/index.tsx | 102 | ||||
| -rw-r--r-- | src/pages/sujet/[slug].tsx | 153 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 141 |
13 files changed, 591 insertions, 781 deletions
diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 75e2205..d6785b6 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -2,6 +2,7 @@ import type { GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; +import Script from 'next/script'; import { useCallback, type ReactNode } from 'react'; import { useIntl } from 'react-intl'; import { @@ -9,7 +10,10 @@ import { Heading, Link, LinksWidget, - PageLayout, + Page, + PageBody, + PageHeader, + PageSidebar, SearchForm, type SearchFormSubmit, } from '../components'; @@ -111,45 +115,20 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({ ); return ( - <> + <Page breadcrumbs={breadcrumbItems}> <Head> <title>{pageTitle}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} <meta name="description" content={pageDescription} /> </Head> - <PageLayout - title={title} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - widgets={[ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData( - thematicsList.map((thematic) => - getPageLinkFromRawData(thematic, 'thematic') - ) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="thematics-list" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData( - topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="topics-list" - />, - ]} - > + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader heading={title} /> + <PageBody> {body} <p> {intl.formatMessage({ @@ -159,13 +138,36 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({ })} </p> <SearchForm isLabelHidden onSubmit={searchSubmitHandler} /> - </PageLayout> - </> + </PageBody> + <PageSidebar> + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData( + thematicsList.map((thematic) => + getPageLinkFromRawData(thematic, 'thematic') + ) + )} + /> + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData( + topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) + )} + /> + </PageSidebar> + </Page> ); }; -Error404Page.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +Error404Page.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps<Error404PageProps> = async ({ locale, diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 0cba7a6..224b1c5 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -2,24 +2,23 @@ import type { ParsedUrlQuery } from 'querystring'; import type { GetStaticPaths, GetStaticProps } from 'next'; import Head from 'next/head'; -import NextImage from 'next/image'; import { useRouter } from 'next/router'; import Script from 'next/script'; -import type { HTMLAttributes } from 'react'; import { useIntl } from 'react-intl'; import type { Comment as CommentSchema, WithContext } from 'schema-dts'; import { - ButtonLink, getLayout, - Link, - PageLayout, SharingWidget, Spinner, - Time, type CommentData, Heading, - MetaList, - MetaItem, + Page, + PageHeader, + PageBody, + PageFooter, + PageComments, + PageSidebar, + TocWidget, } from '../../components'; import { getAllArticlesSlugs, @@ -41,8 +40,8 @@ import { useArticle, useBreadcrumb, useComments, + useHeadingsTree, usePrism, - useReadingTime, } from '../../utils/hooks'; type ArticlePageProps = { @@ -84,7 +83,6 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ title: article?.title ?? '', url: `${ROUTES.ARTICLE}/${slug}`, }); - const readingTime = useReadingTime(article?.meta.wordsCount ?? 0, true); const { attributes, className } = usePrism({ attributes: { 'data-toolbar-order': 'show-language,copy-to-clipboard,color-scheme', @@ -107,11 +105,21 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ description: 'ArticlePage: loading article message', id: '4iYISO', }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); if (isFallback || !article) return <Spinner>{loadingArticle}</Spinner>; const { content, id, intro, meta, title } = article; - const { author, commentsCount, cover, dates, seo, thematics, topics } = meta; + const { + author, + commentsCount, + cover, + dates, + seo, + thematics, + topics, + wordsCount, + } = meta; const webpageSchema = getWebPageSchema({ description: intro, @@ -211,9 +219,15 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ id: 'HKKkQk', description: 'SharingWidget: widget title', }); + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); + const articleComments = getComments(commentsData); return ( - <> + <Page breadcrumbs={breadcrumbItems}> <Head> <title>{seo.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -231,135 +245,63 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - allowComments={true} - bodyAttributes={attributes as HTMLAttributes<HTMLDivElement>} - bodyClassName={styles.body} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - comments={getComments(commentsData)} - footerMeta={ - topics ? ( - <MetaList> - <MetaItem - hasInlinedValues - label={intl.formatMessage({ - defaultMessage: 'Read more articles about:', - description: 'ArticlePage: footer topics list label', - id: '50xc4o', - })} - value={topics.map((topic) => { - return { - id: `topic--${topic.id}`, - value: ( - <ButtonLink - className={styles.btn} - key={topic.id} - to={topic.url} - > - {topic.logo ? <NextImage {...topic.logo} /> : null}{' '} - {topic.name} - </ButtonLink> - ), - }; - })} - /> - </MetaList> - ) : undefined - } - headerMeta={ - <MetaList> - {author ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Written by:', - description: 'ArticlePage: author label', - id: 'MJbZfX', - })} - value={author.name} - /> - ) : null} - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Reading time:', - description: 'ArticlePage: reading time label', - id: 'Gw7X3x', - })} - value={readingTime} - /> - {thematics ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Thematics:', - description: 'ArticlePage: thematics meta label', - id: 'CvOqoh', - })} - value={thematics.map((thematic) => { - return { - id: `thematic-${thematic.id}`, - value: ( - <Link key={thematic.id} href={thematic.url}> - {thematic.name} - </Link> - ), - }; - })} - /> - ) : null} - </MetaList> - } - id={id as number} + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} intro={intro} - title={title} - withToC={true} - widgets={[ - <SharingWidget - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="sharing-widget" - className={styles.widget} - data={{ excerpt: intro, title, url: pageUrl }} - heading={<Heading level={3}>{sharingWidgetTitle}</Heading>} - media={[ - 'diaspora', - 'email', - 'facebook', - 'journal-du-hacker', - 'linkedin', - 'twitter', - ]} - />, - ]} - > - {contentWithPrismClasses} - </PageLayout> - </> + meta={{ + author: author?.name, + publicationDate: dates.publication, + thematics, + updateDate: dates.update, + wordsCount, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody + {...attributes} + className={styles.body} + dangerouslySetInnerHTML={{ __html: contentWithPrismClasses }} + ref={ref} + /> + {topics ? <PageFooter readMoreAbout={topics} /> : null} + <PageSidebar> + <SharingWidget + // eslint-disable-next-line react/jsx-no-literals -- Key allowed + key="sharing-widget" + className={styles.widget} + data={{ excerpt: intro, title, url: pageUrl }} + heading={<Heading level={3}>{sharingWidgetTitle}</Heading>} + media={[ + 'diaspora', + 'email', + 'facebook', + 'journal-du-hacker', + 'linkedin', + 'twitter', + ]} + /> + </PageSidebar> + <PageComments + comments={articleComments ?? []} + depth={2} + pageId={id as number} + /> + </Page> ); }; -ArticlePage.getLayout = (page) => getLayout(page, { useGrid: true }); +ArticlePage.getLayout = (page) => getLayout(page); type PostParams = { slug: string; diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 6ed6eda..0de5523 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -10,13 +10,14 @@ import { Heading, LinksWidget, Notice, - PageLayout, PostsList, Pagination, type RenderPaginationLink, type RenderPaginationItemAriaLabel, - MetaList, - MetaItem, + Page, + PageHeader, + PageBody, + PageSidebar, } from '../../components'; import { getArticles, @@ -191,7 +192,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ }); return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -209,60 +210,14 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - title={title} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - })} - value={intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - )} - /> - </MetaList> - } - widgets={[ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData( - thematicsList.map((thematic) => - getPageLinkFromRawData(thematic, 'thematic') - ) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="thematics-list" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData( - topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="topics-list" - />, - ]} - > + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader heading={title} meta={{ total: totalArticles }} /> + <PageBody className={styles.body}> {posts ? ( <PostsList className={styles.list} @@ -297,13 +252,36 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ })} </Notice> ) : null} - </PageLayout> - </> + </PageBody> + <PageSidebar> + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData( + thematicsList.map((thematic) => + getPageLinkFromRawData(thematic, 'thematic') + ) + )} + /> + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData( + topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) + )} + /> + </PageSidebar> + </Page> ); }; -BlogPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +BlogPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps<BlogPageProps> = async ({ locale, diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 27d1816..b254603 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -10,13 +10,14 @@ import { getLayout, Heading, LinksWidget, - PageLayout, PostsList, Pagination, type RenderPaginationLink, type RenderPaginationItemAriaLabel, - MetaList, - MetaItem, + Page, + PageHeader, + PageBody, + PageSidebar, } from '../../../components'; import { getArticles, @@ -195,7 +196,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ }); return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -213,60 +214,17 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - title={pageTitleWithPageNumber} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - })} - value={intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - )} - /> - </MetaList> - } - widgets={[ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData( - thematicsList.map((thematic) => - getPageLinkFromRawData(thematic, 'thematic') - ) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="thematics-list" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData( - topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="topics-list" - />, - ]} - > + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={pageTitleWithPageNumber} + meta={{ total: totalArticles }} + /> + <PageBody> <PostsList posts={posts ?? []} sortByYear /> <Pagination aria-label={paginationAriaLabel} @@ -276,13 +234,36 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ renderLink={renderPaginationLink} total={totalArticles} /> - </PageLayout> - </> + </PageBody> + <PageSidebar> + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData( + thematicsList.map((thematic) => + getPageLinkFromRawData(thematic, 'thematic') + ) + )} + /> + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData( + topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) + )} + /> + </PageSidebar> + </Page> ); }; -BlogPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +BlogPage.getLayout = (page) => getLayout(page); type BlogPageParams = { number: string; diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx index f316143..b10d161 100644 --- a/src/pages/contact.tsx +++ b/src/pages/contact.tsx @@ -8,10 +8,13 @@ import { useIntl } from 'react-intl'; import { ContactForm, getLayout, - PageLayout, SocialMediaWidget, Heading, type ContactFormSubmit, + Page, + PageHeader, + PageBody, + PageSidebar, } from '../components'; import { meta } from '../content/pages/contact.mdx'; import { sendMail } from '../services/graphql'; @@ -78,39 +81,6 @@ const ContactPage: NextPageWithLayout = () => { description: 'ContactPage: LinkedIn profile link', id: 'Q3oEQn', }); - - const widgets = [ - <SocialMediaWidget - heading={ - <Heading isFake level={3}> - {socialMediaTitle} - </Heading> - } - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="social-media" - media={[ - { - icon: 'Github', - id: 'github', - label: githubLabel, - url: 'https://github.com/ArmandPhilippot', - }, - { - icon: 'Gitlab', - id: 'gitlab', - label: gitlabLabel, - url: 'https://gitlab.com/ArmandPhilippot', - }, - { - icon: 'LinkedIn', - id: 'linkedin', - label: linkedinLabel, - url: 'https://www.linkedin.com/in/armandphilippot', - }, - ]} - />, - ]; - const formName = intl.formatMessage({ defaultMessage: 'Contact form', description: 'Contact: form accessible name', @@ -163,7 +133,7 @@ const ContactPage: NextPageWithLayout = () => { }; return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -180,21 +150,50 @@ const ContactPage: NextPageWithLayout = () => { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - intro={intro} - title={pageTitle} - widgets={widgets} - > + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader heading={pageTitle} intro={intro} /> + <PageBody> <ContactForm aria-label={formName} onSubmit={submitMail} /> - </PageLayout> - </> + </PageBody> + <PageSidebar> + <SocialMediaWidget + heading={ + <Heading isFake level={3}> + {socialMediaTitle} + </Heading> + } + media={[ + { + icon: 'Github', + id: 'github', + label: githubLabel, + url: 'https://github.com/ArmandPhilippot', + }, + { + icon: 'Gitlab', + id: 'gitlab', + label: gitlabLabel, + url: 'https://gitlab.com/ArmandPhilippot', + }, + { + icon: 'LinkedIn', + id: 'linkedin', + label: linkedinLabel, + url: 'https://www.linkedin.com/in/armandphilippot', + }, + ]} + /> + </PageSidebar> + </Page> ); }; -ContactPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +ContactPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps = async ({ locale }) => { const translation = await loadTranslation(locale); diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index 0cda194..fd19a83 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -17,12 +17,13 @@ import { ImageWidget, Link, List, - PageLayout, SocialMediaWidget, ListItem, - Time, - MetaList, - MetaItem, + Page, + PageHeader, + PageSidebar, + TocWidget, + PageBody, } from '../components'; import CVContent, { data, meta } from '../content/pages/cv.mdx'; import type { NextPageWithLayout } from '../types'; @@ -34,7 +35,7 @@ import { getWebPageSchema, } from '../utils/helpers'; import { loadTranslation } from '../utils/helpers/server'; -import { useBreadcrumb } from '../utils/hooks'; +import { useBreadcrumb, useHeadingsTree } from '../utils/hooks'; const ExternalLink = ({ children = '', @@ -137,6 +138,7 @@ const components: MDXComponents = { */ const CVPage: NextPageWithLayout = () => { const intl = useIntl(); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); const { file, image } = data; const { dates, intro, seo, title } = meta; const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ @@ -154,6 +156,11 @@ const CVPage: NextPageWithLayout = () => { description: 'CVPage: social media widget title', id: '+Dre5J', }); + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); const cvCaption = intl.formatMessage( { @@ -186,49 +193,6 @@ const CVPage: NextPageWithLayout = () => { id: 'Sm2wCk', }); - const widgets = [ - <ImageWidget - description={cvCaption} - heading={ - <Heading isFake level={3}> - {imageWidgetTitle} - </Heading> - } - img={<NextImage {...image} />} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="image-widget" - />, - <SocialMediaWidget - heading={ - <Heading isFake level={3}> - {socialMediaTitle} - </Heading> - } - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="social-media" - media={[ - { - icon: 'Github', - id: 'github', - label: githubLabel, - url: PERSONAL_LINKS.GITHUB, - }, - { - icon: 'Gitlab', - id: 'gitlab', - label: gitlabLabel, - url: PERSONAL_LINKS.GITLAB, - }, - { - icon: 'LinkedIn', - id: 'linkedin', - label: linkedinLabel, - url: PERSONAL_LINKS.LINKEDIN, - }, - ]} - />, - ]; - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -254,38 +218,7 @@ const CVPage: NextPageWithLayout = () => { }; return ( - <PageLayout - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - </MetaList> - } - intro={intro} - title={title} - widgets={widgets} - withToC={true} - > + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -304,13 +237,72 @@ const CVPage: NextPageWithLayout = () => { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <CVContent components={components} /> - </PageLayout> + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} + intro={intro} + meta={{ + publicationDate: dates.publication, + updateDate: dates.update, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody ref={ref}> + <CVContent components={components} /> + </PageBody> + <PageSidebar> + <ImageWidget + description={cvCaption} + heading={ + <Heading isFake level={3}> + {imageWidgetTitle} + </Heading> + } + img={<NextImage {...image} />} + /> + <SocialMediaWidget + heading={ + <Heading isFake level={3}> + {socialMediaTitle} + </Heading> + } + media={[ + { + icon: 'Github', + id: 'github', + label: githubLabel, + url: PERSONAL_LINKS.GITHUB, + }, + { + icon: 'Gitlab', + id: 'gitlab', + label: gitlabLabel, + url: PERSONAL_LINKS.GITLAB, + }, + { + icon: 'LinkedIn', + id: 'linkedin', + label: linkedinLabel, + url: PERSONAL_LINKS.LINKEDIN, + }, + ]} + /> + </PageSidebar> + </Page> ); }; -CVPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +CVPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps = async ({ locale }) => { const translation = await loadTranslation(locale); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d708ac5..81883fc 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -438,8 +438,7 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { ); }; -HomePage.getLayout = (page) => - getLayout(page, { isHome: true, withExtraPadding: false }); +HomePage.getLayout = (page) => getLayout(page, { isHome: true }); export const getStaticProps: GetStaticProps<HomeProps> = async ({ locale }) => { const translation = await loadTranslation(locale); diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index e07263f..e3aabc5 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -9,11 +9,13 @@ import { useIntl } from 'react-intl'; import { getLayout, Link, - PageLayout, Figure, - Time, - MetaList, - MetaItem, + Page, + PageHeader, + PageSidebar, + TocWidget, + Heading, + PageBody, } from '../components'; import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx'; import type { NextPageWithLayout } from '../types'; @@ -25,7 +27,7 @@ import { getWebPageSchema, } from '../utils/helpers'; import { loadTranslation } from '../utils/helpers/server'; -import { useBreadcrumb } from '../utils/hooks'; +import { useBreadcrumb, useHeadingsTree } from '../utils/hooks'; const ResponsiveImage = (props: NextImageProps) => ( <Figure> @@ -49,6 +51,7 @@ const LegalNoticePage: NextPageWithLayout = () => { url: ROUTES.LEGAL_NOTICE, }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -71,39 +74,14 @@ const LegalNoticePage: NextPageWithLayout = () => { title: `${seo.title} - ${CONFIG.name}`, url: `${CONFIG.url}${asPath}`, }; + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); return ( - <PageLayout - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - </MetaList> - } - intro={intro} - title={title} - withToC={true} - > + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -120,13 +98,34 @@ const LegalNoticePage: NextPageWithLayout = () => { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <LegalNoticeContent components={components} /> - </PageLayout> + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} + intro={intro} + meta={{ + publicationDate: dates.publication, + updateDate: dates.update, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody ref={ref}> + <LegalNoticeContent components={components} /> + </PageBody> + </Page> ); }; -LegalNoticePage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +LegalNoticePage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps = async ({ locale }) => { const translation = await loadTranslation(locale); diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx index a8a4fea..82d9149 100644 --- a/src/pages/projets/[slug].tsx +++ b/src/pages/projets/[slug].tsx @@ -12,20 +12,21 @@ import { Code, getLayout, Link, - PageLayout, SharingWidget, Spinner, Heading, List, ListItem, Figure, - Time, Grid, ProjectOverview, type ProjectMeta, type Repository, - MetaList, - MetaItem, + Page, + PageHeader, + PageSidebar, + TocWidget, + PageBody, } from '../../components'; import styles from '../../styles/pages/project.module.scss'; import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types'; @@ -42,7 +43,11 @@ import { loadTranslation, type Messages, } from '../../utils/helpers/server'; -import { useBreadcrumb, useGithubApi } from '../../utils/hooks'; +import { + useBreadcrumb, + useGithubApi, + useHeadingsTree, +} from '../../utils/hooks'; const BorderedImage = (props: NextImageProps) => ( <Figure hasBorders> @@ -164,6 +169,7 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { title, url: `${ROUTES.PROJECTS}/${id}`, }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); const ProjectContent: ComponentType<MDXComponents> = dynamic( async () => import(`../../content/projects/${id}.mdx`), @@ -269,9 +275,14 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { id: 'HKKkQk', description: 'SharingWidget: widget title', }); + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -289,67 +300,54 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - title={title} + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} intro={intro} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - </MetaList> - } - withToC={true} - widgets={[ - <SharingWidget - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="sharing-widget" - data={{ excerpt: intro, title, url: page.url }} - heading={<Heading level={3}>{sharingWidgetTitle}</Heading>} - media={[ - 'diaspora', - 'email', - 'facebook', - 'journal-du-hacker', - 'linkedin', - 'twitter', - ]} - className={styles.widget} - />, - ]} - > + meta={{ + publicationDate: dates.publication, + updateDate: dates.update, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody ref={ref}> <ProjectOverview cover={cover ? <NextImage {...cover} /> : undefined} meta={overviewMeta} name={project.title} /> <ProjectContent components={components} /> - </PageLayout> - </> + </PageBody> + <PageSidebar> + <SharingWidget + data={{ excerpt: intro, title, url: page.url }} + heading={<Heading level={3}>{sharingWidgetTitle}</Heading>} + media={[ + 'diaspora', + 'email', + 'facebook', + 'journal-du-hacker', + 'linkedin', + 'twitter', + ]} + className={styles.widget} + /> + </PageSidebar> + </Page> ); }; -ProjectPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +ProjectPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps<ProjectPageProps> = async ({ locale, diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index 8feb701..0b9a91c 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -18,8 +18,10 @@ import { type GridItem, Link, MetaList, - PageLayout, MetaItem, + Page, + PageHeader, + PageBody, } from '../../components'; import PageContent, { meta } from '../../content/pages/projects.mdx'; import styles from '../../styles/pages/projects.module.scss'; @@ -139,7 +141,7 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { }; return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -157,12 +159,17 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - title={title} + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} intro={<PageContent components={components} />} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - > + /> + <PageBody className={styles.body}> <Grid className={styles.list} gap="sm" @@ -170,13 +177,12 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { items={items} sizeMax="30ch" /> - </PageLayout> - </> + </PageBody> + </Page> ); }; -ProjectsPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +ProjectsPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps<ProjectsPageProps> = async ({ locale, diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index 0fb279b..2a18aa3 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -10,13 +10,14 @@ import { Heading, LinksWidget, Notice, - PageLayout, PostsList, Spinner, SearchForm, type SearchFormSubmit, - MetaList, - MetaItem, + PageHeader, + Page, + PageSidebar, + PageBody, } from '../../components'; import { getArticles, @@ -172,7 +173,7 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ ); return ( - <> + <Page breadcrumbs={breadcrumbItems} isBodyLastChild> <Head> <title>{page.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -190,60 +191,14 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - title={title} - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - })} - value={intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - )} - /> - </MetaList> - } - widgets={[ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData( - thematicsList.map((thematic) => - getPageLinkFromRawData(thematic, 'thematic') - ) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="thematics-list" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData( - topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) - )} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="topics-list" - />, - ]} - > + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader heading={title} meta={{ total: totalArticles }} /> + <PageBody className={styles.body}> {posts ? null : <Spinner>{loadingResults}</Spinner>} {posts?.length ? ( <PostsList @@ -285,13 +240,36 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ })} </Notice> ) : null} - </PageLayout> - </> + </PageBody> + <PageSidebar> + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData( + thematicsList.map((thematic) => + getPageLinkFromRawData(thematic, 'thematic') + ) + )} + /> + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData( + topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic')) + )} + /> + </PageSidebar> + </Page> ); }; -SearchPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +SearchPage.getLayout = (page) => getLayout(page); export const getStaticProps: GetStaticProps<SearchPageProps> = async ({ locale, diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index d9734a3..30adec3 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -10,11 +10,12 @@ import { getLayout, Heading, LinksWidget, - PageLayout, PostsList, - Time, - MetaList, - MetaItem, + Page, + PageHeader, + PageSidebar, + TocWidget, + PageBody, } from '../../components'; import { getAllTopicsSlugs, @@ -35,7 +36,7 @@ import { getWebPageSchema, } from '../../utils/helpers'; import { loadTranslation, type Messages } from '../../utils/helpers/server'; -import { useBreadcrumb } from '../../utils/hooks'; +import { useBreadcrumb, useHeadingsTree } from '../../utils/hooks'; export type TopicPageProps = { currentTopic: Topic; @@ -61,6 +62,7 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ title, url: `${ROUTES.TOPICS}/${slug}`, }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ @@ -101,9 +103,14 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ </> ); const pageUrl = `${CONFIG.url}${asPath}`; + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); return ( - <> + <Page breadcrumbs={breadcrumbItems}> <Head> <title>{seo.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -121,92 +128,29 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - title={getPageHeading()} + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={getPageHeading()} intro={intro} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - {officialWebsite ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Official website:', - description: 'TopicPage: official website label', - id: 'zoifQd', - })} - value={officialWebsite} - /> - ) : null} - {articles ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'ThematicPage: total label', - id: 'lHkta9', - })} - value={intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'ThematicPage: posts count meta', - id: 'iv3Ex1', - }, - { postsCount: articles.length } - )} - /> - ) : null} - </MetaList> - } - widgets={ - thematics - ? [ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData(thematics)} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="related-thematics" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData(topics)} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="topics" - />, - ] - : [] - } - > + meta={{ + publicationDate: dates.publication, + total: articles?.length, + updateDate: dates.update, + website: officialWebsite, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody className={styles.body} ref={ref}> {/*eslint-disable-next-line react/no-danger -- Necessary for content*/} {content ? <div dangerouslySetInnerHTML={{ __html: content }} /> : null} {articles ? ( @@ -229,13 +173,32 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ /> </> ) : null} - </PageLayout> - </> + </PageBody> + <PageSidebar> + {thematics ? ( + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData(thematics)} + /> + ) : null} + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData(topics)} + /> + </PageSidebar> + </Page> ); }; -TopicPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +TopicPage.getLayout = (page) => getLayout(page); type TopicParams = { slug: string; diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index 9220ccd..b8518c5 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -9,11 +9,12 @@ import { getLayout, Heading, LinksWidget, - PageLayout, PostsList, - Time, - MetaList, - MetaItem, + Page, + PageHeader, + PageSidebar, + TocWidget, + PageBody, } from '../../components'; import { getAllThematicsSlugs, @@ -34,7 +35,7 @@ import { getWebPageSchema, } from '../../utils/helpers'; import { loadTranslation, type Messages } from '../../utils/helpers/server'; -import { useBreadcrumb } from '../../utils/hooks'; +import { useBreadcrumb, useHeadingsTree } from '../../utils/hooks'; export type ThematicPageProps = { currentThematic: Thematic; @@ -53,6 +54,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ title, url: `${ROUTES.THEMATICS.INDEX}/${slug}`, }); + const { ref, tree } = useHeadingsTree({ fromLevel: 2 }); const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ @@ -85,9 +87,14 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ id: '/42Z0z', }); const pageUrl = `${CONFIG.url}${asPath}`; + const tocTitle = intl.formatMessage({ + defaultMessage: 'Table of Contents', + description: 'PageLayout: table of contents title', + id: 'eys2uX', + }); return ( - <> + <Page breadcrumbs={breadcrumbItems}> <Head> <title>{seo.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} @@ -105,81 +112,28 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ // eslint-disable-next-line react/no-danger -- Necessary for schema dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} /> - <PageLayout - breadcrumb={breadcrumbItems} - breadcrumbSchema={breadcrumbSchema} - title={title} + <Script + dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} + // eslint-disable-next-line react/jsx-no-literals -- Id allowed + id="schema-breadcrumb" + type="application/ld+json" + /> + <PageHeader + heading={title} intro={intro} - headerMeta={ - <MetaList> - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - })} - value={<Time date={dates.publication} />} - /> - {dates.update ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - })} - value={<Time date={dates.update} />} - /> - ) : null} - {articles ? ( - <MetaItem - isInline - label={intl.formatMessage({ - defaultMessage: 'Total:', - description: 'ThematicPage: total label', - id: 'lHkta9', - })} - value={intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'ThematicPage: posts count meta', - id: 'iv3Ex1', - }, - { postsCount: articles.length } - )} - /> - ) : null} - </MetaList> - } - widgets={ - topics - ? [ - <LinksWidget - heading={ - <Heading isFake level={3}> - {thematicsListTitle} - </Heading> - } - items={getLinksItemData(thematics)} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="thematics" - />, - <LinksWidget - heading={ - <Heading isFake level={3}> - {topicsListTitle} - </Heading> - } - items={getLinksItemData(topics)} - // eslint-disable-next-line react/jsx-no-literals -- Key allowed - key="related-topics" - />, - ] - : [] - } - > + meta={{ + publicationDate: dates.publication, + total: articles?.length, + updateDate: dates.update, + }} + /> + <PageSidebar> + <TocWidget + heading={<Heading level={3}>{tocTitle}</Heading>} + tree={tree} + /> + </PageSidebar> + <PageBody className={styles.body} ref={ref}> {/*eslint-disable-next-line react/no-danger -- Necessary for content*/} <div dangerouslySetInnerHTML={{ __html: content }} /> {articles ? ( @@ -202,13 +156,32 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ /> </> ) : null} - </PageLayout> - </> + </PageBody> + <PageSidebar> + <LinksWidget + heading={ + <Heading isFake level={3}> + {thematicsListTitle} + </Heading> + } + items={getLinksItemData(thematics)} + /> + {topics ? ( + <LinksWidget + heading={ + <Heading isFake level={3}> + {topicsListTitle} + </Heading> + } + items={getLinksItemData(topics)} + /> + ) : null} + </PageSidebar> + </Page> ); }; -ThematicPage.getLayout = (page) => - getLayout(page, { useGrid: true, withExtraPadding: true }); +ThematicPage.getLayout = (page) => getLayout(page); type ThematicParams = { slug: string; |
