diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/atoms/buttons/button-link/button-link.module.scss | 2 | ||||
| -rw-r--r-- | src/components/mdx.tsx | 17 | ||||
| -rw-r--r-- | src/components/molecules/grid/grid.module.scss | 12 | ||||
| -rw-r--r-- | src/components/molecules/grid/grid.test.tsx | 40 | ||||
| -rw-r--r-- | src/components/molecules/grid/grid.tsx | 8 | ||||
| m--------- | src/content | 0 | ||||
| -rw-r--r-- | src/i18n/en.json | 52 | ||||
| -rw-r--r-- | src/i18n/fr.json | 52 | ||||
| -rw-r--r-- | src/pages/index.tsx | 347 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 5 | ||||
| -rw-r--r-- | src/services/graphql/helpers/convert-taxonomy-to-page-link.test.ts | 2 | ||||
| -rw-r--r-- | src/services/graphql/helpers/convert-taxonomy-to-page-link.ts | 2 | ||||
| -rw-r--r-- | src/services/graphql/helpers/convert-wp-thematic-to-thematic.test.ts | 2 | ||||
| -rw-r--r-- | src/services/graphql/helpers/convert-wp-thematic-to-thematic.ts | 2 | ||||
| -rw-r--r-- | src/styles/pages/home.module.scss | 34 | ||||
| -rw-r--r-- | src/utils/constants.ts | 8 | ||||
| -rw-r--r-- | src/utils/helpers/schema-org.ts | 41 | ||||
| -rw-r--r-- | src/utils/helpers/strings.ts | 13 | ||||
| -rw-r--r-- | src/utils/hooks/use-breadcrumb.ts | 3 | 
19 files changed, 212 insertions, 430 deletions
| diff --git a/src/components/atoms/buttons/button-link/button-link.module.scss b/src/components/atoms/buttons/button-link/button-link.module.scss index 0f35a24..3ddeffe 100644 --- a/src/components/atoms/buttons/button-link/button-link.module.scss +++ b/src/components/atoms/buttons/button-link/button-link.module.scss @@ -3,6 +3,8 @@  .btn {    @extend %button; +  width: fit-content; +    &--circle {      @extend %circle-button;    } diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx index 9f0a4a5..eea80a9 100644 --- a/src/components/mdx.tsx +++ b/src/components/mdx.tsx @@ -1,8 +1,17 @@  import type { MDXComponents } from 'mdx/types';  import NextImage from 'next/image';  import type { AnchorHTMLAttributes, ImgHTMLAttributes, ReactNode } from 'react'; -import { Figure, Heading, Link, List, ListItem } from './atoms'; +import { +  ButtonLink, +  Figure, +  Heading, +  Icon, +  Link, +  List, +  ListItem, +} from './atoms';  import { Code, Grid, GridItem } from './molecules'; +import { PageSection } from './templates';  const Anchor = ({    children = '', @@ -58,6 +67,7 @@ const Gallery = ({ children }: { children: ReactNode }) => (  export const mdxComponents: MDXComponents = {    a: Anchor, +  ButtonLink,    Code,    figure: ({ ref, ...props }) => <Figure {...props} />,    Figure, @@ -70,9 +80,14 @@ export const mdxComponents: MDXComponents = {    h4: ({ ref, ...props }) => <Heading {...props} level={4} />,    h5: ({ ref, ...props }) => <Heading {...props} level={5} />,    h6: ({ ref, ...props }) => <Heading {...props} level={6} />, +  Icon,    img: Img, +  Img,    li: ({ ref, ...props }) => <ListItem {...props} />,    Link, +  List, +  ListItem, +  PageSection,    ol: ({ ref, ...props }) => (      <List        // eslint-disable-next-line react/jsx-no-literals diff --git a/src/components/molecules/grid/grid.module.scss b/src/components/molecules/grid/grid.module.scss index f13af30..d5260cf 100644 --- a/src/components/molecules/grid/grid.module.scss +++ b/src/components/molecules/grid/grid.module.scss @@ -2,6 +2,18 @@    display: grid;    gap: var(--gap); +  &--align-items-center { +    align-items: center; +  } + +  &--align-items-start { +    align-items: start; +  } + +  &--align-items-end { +    align-items: end; +  } +    &--is-centered {      place-content: center;    } diff --git a/src/components/molecules/grid/grid.test.tsx b/src/components/molecules/grid/grid.test.tsx index e69610d..b4b9f77 100644 --- a/src/components/molecules/grid/grid.test.tsx +++ b/src/components/molecules/grid/grid.test.tsx @@ -109,4 +109,44 @@ describe('Grid', () => {      expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--is-centered');    }); + +  it('can render a list of centered items', () => { +    render( +      <Grid alignItems="center"> +        {items.map((item) => ( +          <GridItem key={item.id}>{item.contents}</GridItem> +        ))} +      </Grid> +    ); + +    expect(rtlScreen.getByRole('list')).toHaveClass( +      'wrapper--align-items-center' +    ); +  }); + +  it('can render a list of items with end alignment', () => { +    render( +      <Grid alignItems="end"> +        {items.map((item) => ( +          <GridItem key={item.id}>{item.contents}</GridItem> +        ))} +      </Grid> +    ); + +    expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--align-items-end'); +  }); + +  it('can render a list of items with start alignment', () => { +    render( +      <Grid alignItems="start"> +        {items.map((item) => ( +          <GridItem key={item.id}>{item.contents}</GridItem> +        ))} +      </Grid> +    ); + +    expect(rtlScreen.getByRole('list')).toHaveClass( +      'wrapper--align-items-start' +    ); +  });  }); diff --git a/src/components/molecules/grid/grid.tsx b/src/components/molecules/grid/grid.tsx index 3d0ecf1..38f6e55 100644 --- a/src/components/molecules/grid/grid.tsx +++ b/src/components/molecules/grid/grid.tsx @@ -13,6 +13,12 @@ export type GridProps<T extends boolean> = Omit<    'children' | 'hideMarker' | 'isHierarchical' | 'isInline' | 'spacing'  > & {    /** +   * How the items should be aligned? +   * +   * @default undefined // The default behavior is `stretch`. +   */ +  alignItems?: 'center' | 'end' | 'start'; +  /**     * The grid items.     */    children: ReactNode; @@ -62,6 +68,7 @@ export type GridProps<T extends boolean> = Omit<  const GridWithRef = <T extends boolean>(    { +    alignItems,      children,      className = '',      col = 'auto-fit', @@ -77,6 +84,7 @@ const GridWithRef = <T extends boolean>(  ) => {    const gridClass = [      styles.wrapper, +    styles[alignItems ? `wrapper--align-items-${alignItems}` : ''],      styles[isCentered ? 'wrapper--is-centered' : ''],      styles[size ? 'wrapper--has-fixed-size' : ''],      styles[sizeMin ? 'wrapper--has-min-size' : ''], diff --git a/src/content b/src/content -Subproject 6c0f2250ea956f9511fed8d2620466cb43d18d3 +Subproject a9aa65d7f81b6ad72ef707f40f14e955cf4fcd6 diff --git a/src/i18n/en.json b/src/i18n/en.json index aac327d..be67b38 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -99,10 +99,6 @@      "defaultMessage": "Repositories:",      "description": "ProjectOverview: repositories label"    }, -  "3f3PzH": { -    "defaultMessage": "Github", -    "description": "HomePage: Github link" -  },    "48Ww//": {      "defaultMessage": "Page not found.",      "description": "404Page: SEO - Meta description" @@ -135,10 +131,6 @@      "defaultMessage": "Github profile",      "description": "ContactPage: Github profile link"    }, -  "7AnwZ7": { -    "defaultMessage": "Gitlab", -    "description": "HomePage: Gitlab link" -  },    "7TbbIk": {      "defaultMessage": "Blog",      "description": "BlogPage: page title" @@ -315,10 +307,6 @@      "defaultMessage": "CV",      "description": "SiteNavbar: main nav - cv link"    }, -  "N44SOc": { -    "defaultMessage": "Projects", -    "description": "HomePage: link to projects" -  },    "N804XO": {      "defaultMessage": "Topics",      "description": "SearchPage: topics list widget title" @@ -363,10 +351,6 @@      "defaultMessage": "{starsCount, plural, =0 {No stars} one {# star} other {# stars}}",      "description": "ProjectOverview: stars count"    }, -  "PXp2hv": { -    "defaultMessage": "{websiteName} | Front-end developer: WordPress/React", -    "description": "HomePage: SEO - Page title" -  },    "PnrHgZ": {      "defaultMessage": "Home",      "description": "SiteNavbar: main nav - home link" @@ -407,10 +391,6 @@      "defaultMessage": "LinkedIn profile",      "description": "CVPage: LinkedIn profile link"    }, -  "T4YA64": { -    "defaultMessage": "Subscribe", -    "description": "HomePage: RSS feed subscription text" -  },    "TpyFZ6": {      "defaultMessage": "An error occurred:",      "description": "Contact: error message" @@ -551,10 +531,6 @@      "defaultMessage": "Light Theme 🌞",      "description": "usePrism: toggle light theme button text"    }, -  "i5L19t": { -    "defaultMessage": "Shaarli", -    "description": "HomePage: link to Shaarli" -  },    "iG5SHf": {      "defaultMessage": "{postTitle} cover",      "description": "PostPreview: an accessible name for the figure wrapping the cover" @@ -567,14 +543,14 @@      "defaultMessage": "Home",      "description": "Breadcrumb: home label"    }, -  "jASD7k": { -    "defaultMessage": "Linux", -    "description": "HomePage: link to Linux thematic" -  },    "jJm8wd": {      "defaultMessage": "Reading time:",      "description": "PageHeader: reading time label"    }, +  "kq+fzI": { +    "defaultMessage": "Cover of {pageTitle}", +    "description": "RecentPosts: card cover accessible name" +  },    "l50cYa": {      "defaultMessage": "Open settings",      "description": "SiteNavbar: settings button label in navbar" @@ -587,6 +563,10 @@      "defaultMessage": "Legal notice",      "description": "SiteFooter: Legal notice link label"    }, +  "mWZU4R": { +    "defaultMessage": "View {pageTitle}", +    "description": "RecentPosts: card accessible name" +  },    "nGss/j": {      "defaultMessage": "Ackee tracking (analytics)",      "description": "AckeeToggle: tooltip title" @@ -647,10 +627,6 @@      "defaultMessage": "Gitlab profile",      "description": "ProjectsPage: Gitlab profile link"    }, -  "sO/Iwj": { -    "defaultMessage": "Contact me", -    "description": "HomePage: contact button text" -  },    "sR5hah": {      "defaultMessage": "Updated on:",      "description": "PageHeader: update date label" @@ -663,10 +639,6 @@      "defaultMessage": "Partial",      "description": "AckeeToggle: partial option name"    }, -  "tMuNTy": { -    "defaultMessage": "{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.", -    "description": "HomePage: SEO - Meta description" -  },    "tsWh8x": {      "defaultMessage": "Light theme",      "description": "PrismThemeToggle: light theme label" @@ -687,10 +659,6 @@      "defaultMessage": "On",      "description": "MotionToggle: activate reduce motion label"    }, -  "vkF/RP": { -    "defaultMessage": "Web development", -    "description": "HomePage: link to web development thematic" -  },    "vtDLzG": {      "defaultMessage": "Would you like to try a new search?",      "description": "SearchPage: try a new search message" @@ -703,10 +671,6 @@      "defaultMessage": "Email:",      "description": "ContactForm: email label"    }, -  "w8GrOf": { -    "defaultMessage": "Free", -    "description": "HomePage: link to free thematic" -  },    "xaqaYQ": {      "defaultMessage": "Sending mail...",      "description": "ContactForm: spinner message on submit" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 17514a3..0226f1e 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -99,10 +99,6 @@      "defaultMessage": "Dépôts :",      "description": "ProjectOverview: repositories label"    }, -  "3f3PzH": { -    "defaultMessage": "Github", -    "description": "HomePage: Github link" -  },    "48Ww//": {      "defaultMessage": "Page non trouvée.",      "description": "404Page: SEO - Meta description" @@ -135,10 +131,6 @@      "defaultMessage": "Profil Github",      "description": "ContactPage: Github profile link"    }, -  "7AnwZ7": { -    "defaultMessage": "Gitlab", -    "description": "HomePage: Gitlab link" -  },    "7TbbIk": {      "defaultMessage": "Blog",      "description": "BlogPage: page title" @@ -315,10 +307,6 @@      "defaultMessage": "CV",      "description": "SiteNavbar: main nav - cv link"    }, -  "N44SOc": { -    "defaultMessage": "Projets", -    "description": "HomePage: link to projects" -  },    "N804XO": {      "defaultMessage": "Sujets",      "description": "SearchPage: topics list widget title" @@ -363,10 +351,6 @@      "defaultMessage": "{starsCount, plural, =0 {0 étoile} one {# étoile} other {# étoiles}}",      "description": "ProjectOverview: stars count"    }, -  "PXp2hv": { -    "defaultMessage": "{websiteName} | Intégrateur web - Développeur WordPress / React", -    "description": "HomePage: SEO - Page title" -  },    "PnrHgZ": {      "defaultMessage": "Accueil",      "description": "SiteNavbar: main nav - home link" @@ -407,10 +391,6 @@      "defaultMessage": "Profil LinkedIn",      "description": "CVPage: LinkedIn profile link"    }, -  "T4YA64": { -    "defaultMessage": "Vous abonner", -    "description": "HomePage: RSS feed subscription text" -  },    "TpyFZ6": {      "defaultMessage": "Une erreur est survenue :",      "description": "Contact: error message" @@ -551,10 +531,6 @@      "defaultMessage": "Thème clair 🌞",      "description": "usePrism: toggle light theme button text"    }, -  "i5L19t": { -    "defaultMessage": "Shaarli", -    "description": "HomePage: link to Shaarli" -  },    "iG5SHf": {      "defaultMessage": "Illustration de {postTitle}",      "description": "PostPreview: an accessible name for the figure wrapping the cover" @@ -567,14 +543,14 @@      "defaultMessage": "Accueil",      "description": "Breadcrumb: home label"    }, -  "jASD7k": { -    "defaultMessage": "Linux", -    "description": "HomePage: link to Linux thematic" -  },    "jJm8wd": {      "defaultMessage": "Temps de lecture :",      "description": "PageHeader: reading time label"    }, +  "kq+fzI": { +    "defaultMessage": "Illustration de {pageTitle}", +    "description": "RecentPosts: card cover accessible name" +  },    "l50cYa": {      "defaultMessage": "Ouvrir les réglages",      "description": "SiteNavbar: settings button label in navbar" @@ -587,6 +563,10 @@      "defaultMessage": "Mentions légales",      "description": "SiteFooter: Legal notice link label"    }, +  "mWZU4R": { +    "defaultMessage": "Consulter {pageTitle}", +    "description": "RecentPosts: card accessible name" +  },    "nGss/j": {      "defaultMessage": "Suivi Ackee (analytique)",      "description": "AckeeToggle: tooltip title" @@ -647,10 +627,6 @@      "defaultMessage": "Profil Gitlab",      "description": "ProjectsPage: Gitlab profile link"    }, -  "sO/Iwj": { -    "defaultMessage": "Me contacter", -    "description": "HomePage: contact button text" -  },    "sR5hah": {      "defaultMessage": "Mis à jour le :",      "description": "PageHeader: update date label" @@ -663,10 +639,6 @@      "defaultMessage": "Partiel",      "description": "AckeeToggle: partial option name"    }, -  "tMuNTy": { -    "defaultMessage": "{websiteName} est intégrateur web / développeur front-end en France. Il code et il écrit essentiellement à propos de développement web et du libre.", -    "description": "HomePage: SEO - Meta description" -  },    "tsWh8x": {      "defaultMessage": "Thème clair",      "description": "PrismThemeToggle: light theme label" @@ -687,10 +659,6 @@      "defaultMessage": "Marche",      "description": "MotionToggle: activate reduce motion label"    }, -  "vkF/RP": { -    "defaultMessage": "Développement web", -    "description": "HomePage: link to web development thematic" -  },    "vtDLzG": {      "defaultMessage": "Souhaitez-vous essayer une nouvelle recherche ?",      "description": "SearchPage: try a new search message" @@ -703,10 +671,6 @@      "defaultMessage": "E-mail :",      "description": "ContactForm: email label"    }, -  "w8GrOf": { -    "defaultMessage": "Libre", -    "description": "HomePage: link to free thematic" -  },    "xaqaYQ": {      "defaultMessage": "Mail en cours d’envoi…",      "description": "ContactForm: spinner message on submit" diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7bd8aec..f4d36c1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,10 +3,9 @@ import type { GetStaticProps } from 'next';  import Head from 'next/head';  import NextImage from 'next/image';  import Script from 'next/script'; -import type { FC, HTMLAttributes, ReactNode } from 'react'; +import type { FC } from 'react';  import { useIntl } from 'react-intl';  import { -  ButtonLink,    Card,    CardCover,    CardFooter, @@ -15,261 +14,77 @@ import {    CardTitle,    getLayout,    Grid, -  Icon, -  List, -  ListItem,    Time,    MetaItem, -  type PageSectionProps, -  PageSection,    Page,  } from '../components';  import { mdxComponents } from '../components/mdx'; -import HomePageContent from '../content/pages/homepage.mdx'; +import HomePageContent, { meta } from '../content/pages/homepage.mdx';  import {    convertRecentPostToRecentArticle,    fetchRecentPosts,  } from '../services/graphql'; -import styles from '../styles/pages/home.module.scss';  import type { NextPageWithLayout, RecentArticle } from '../types';  import { CONFIG } from '../utils/config'; -import { PERSONAL_LINKS, ROUTES } from '../utils/constants'; +import { ROUTES } from '../utils/constants';  import { getSchemaJson, getWebPageSchema } from '../utils/helpers';  import { loadTranslation, type Messages } from '../utils/helpers/server';  import { useBreadcrumb } from '../utils/hooks'; -/** - * Column component. - * - * Render the body as a column. - */ -const Column = ({ children, ...props }: HTMLAttributes<HTMLDivElement>) => ( -  <div {...props}>{children}</div> -); - -/** - * Retrieve a list of coding links. - * - * @returns {JSX.Element} - A list of links. - */ -const CodingLinks: FC = () => { -  const intl = useIntl(); - -  return ( -    <List className={styles.list} hideMarker isInline spacing="sm"> -      <ListItem> -        <ButtonLink to={ROUTES.THEMATICS.WEB_DEV}> -          {intl.formatMessage({ -            defaultMessage: 'Web development', -            description: 'HomePage: link to web development thematic', -            id: 'vkF/RP', -          })} -        </ButtonLink> -      </ListItem> -      <ListItem> -        <ButtonLink to={ROUTES.PROJECTS}> -          {intl.formatMessage({ -            defaultMessage: 'Projects', -            description: 'HomePage: link to projects', -            id: 'N44SOc', -          })} -        </ButtonLink> -      </ListItem> -    </List> -  ); -}; - -/** - * Retrieve a list of Coldark repositories. - * - * @returns {JSX.Element} - A list of links. - */ -const ColdarkRepos: FC = () => { -  const intl = useIntl(); -  const repo = { -    github: 'https://github.com/ArmandPhilippot/coldark', -    gitlab: 'https://gitlab.com/ArmandPhilippot/coldark', -  }; - -  return ( -    <List className={styles.list} hideMarker isInline spacing="sm"> -      <ListItem> -        <ButtonLink isExternal to={repo.github}> -          {intl.formatMessage({ -            defaultMessage: 'Github', -            description: 'HomePage: Github link', -            id: '3f3PzH', -          })} -        </ButtonLink> -      </ListItem> -      <ListItem> -        <ButtonLink isExternal to={repo.gitlab}> -          {intl.formatMessage({ -            defaultMessage: 'Gitlab', -            description: 'HomePage: Gitlab link', -            id: '7AnwZ7', -          })} -        </ButtonLink> -      </ListItem> -    </List> -  ); -}; - -/** - * Retrieve a list of links related to Free thematic. - * - * @returns {JSX.Element} - A list of links. - */ -const LibreLinks: FC = () => { -  const intl = useIntl(); - -  return ( -    <List className={styles.list} hideMarker isInline spacing="sm"> -      <ListItem> -        <ButtonLink to={ROUTES.THEMATICS.FREE}> -          {intl.formatMessage({ -            defaultMessage: 'Free', -            description: 'HomePage: link to free thematic', -            id: 'w8GrOf', -          })} -        </ButtonLink> -      </ListItem> -      <ListItem> -        <ButtonLink to={ROUTES.THEMATICS.LINUX}> -          {intl.formatMessage({ -            defaultMessage: 'Linux', -            description: 'HomePage: link to Linux thematic', -            id: 'jASD7k', -          })} -        </ButtonLink> -      </ListItem> -    </List> -  ); -}; - -/** - * Retrieve the Shaarli link. - * - * @returns {JSX.Element} - A list of links - */ -const ShaarliLink: FC = () => { -  const intl = useIntl(); - -  return ( -    <List className={styles.list} hideMarker isInline spacing="sm"> -      <ListItem> -        <ButtonLink isExternal to={PERSONAL_LINKS.SHAARLI}> -          {intl.formatMessage({ -            defaultMessage: 'Shaarli', -            description: 'HomePage: link to Shaarli', -            id: 'i5L19t', -          })} -        </ButtonLink> -      </ListItem> -    </List> -  ); -}; - -/** - * Retrieve the additional links. - * - * @returns {JSX.Element} - A list of links. - */ -const MoreLinks: FC = () => { -  const intl = useIntl(); - -  return ( -    <List className={styles.list} hideMarker isInline spacing="sm"> -      <ListItem> -        <ButtonLink to={ROUTES.CONTACT}> -          <Icon aria-hidden={true} shape="envelop" /> -          {intl.formatMessage({ -            defaultMessage: 'Contact me', -            description: 'HomePage: contact button text', -            id: 'sO/Iwj', -          })} -        </ButtonLink> -      </ListItem> -      <ListItem> -        <ButtonLink to={ROUTES.RSS}> -          <Icon aria-hidden={true} shape="feed" /> -          {intl.formatMessage({ -            defaultMessage: 'Subscribe', -            description: 'HomePage: RSS feed subscription text', -            id: 'T4YA64', -          })} -        </ButtonLink> -      </ListItem> -    </List> -  ); +type RecentPostsProps = { +  posts: RecentArticle[];  }; -const StyledGrid = ({ children }: { children: ReactNode }) => ( -  <Grid className={styles.columns} gap="sm" sizeMin="250px"> -    {children} -  </Grid> -); -  /** - * Create the page sections. + * Get a cards list of recent posts.   * - * @param {object} obj - An object containing the section body. - * @param {ReactNode[]} obj.children - The section body. - * @returns {JSX.Element} A section element. - */ -const HomePageSection: FC<PageSectionProps> = ({ -  children, -  hasBorder = true, -  variant, -}) => ( -  <PageSection -    className={styles.section} -    hasBorder={hasBorder} -    variant={variant} -  > -    {children} -  </PageSection> -); - -type HomeProps = { -  recentPosts: RecentArticle[]; -  translation?: Messages; -}; - -/** - * Home page. + * @returns {JSX.Element} - The cards list.   */ -const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { +const RecentPosts: FC<RecentPostsProps> = ({ posts }): JSX.Element => {    const intl = useIntl();    const publicationDate = intl.formatMessage({      defaultMessage: 'Published on:',      description: 'HomePage: publication date label',      id: 'pT5nHk',    }); -  const { schema: breadcrumbSchema } = useBreadcrumb({ -    title: '', -    url: `/`, -  }); - -  /** -   * Get a cards list of recent posts. -   * -   * @returns {JSX.Element} - The cards list. -   */ -  const getRecentPosts = (): JSX.Element => { -    const listClass = `${styles.list} ${styles['list--cards']}`; -    return ( -      <Grid className={listClass} gap="sm" isCentered sizeMax="25ch"> -        {recentPosts.map((post) => ( +  return ( +    <Grid +      // eslint-disable-next-line react/jsx-no-literals +      gap="sm" +      // eslint-disable-next-line react/jsx-no-literals +      sizeMax="25ch" +    > +      {posts.map((post) => { +        const postUrl = `${ROUTES.ARTICLE}/${post.slug}`; +        const cardLabel = intl.formatMessage( +          { +            defaultMessage: 'View {pageTitle}', +            description: 'RecentPosts: card accessible name', +            id: 'mWZU4R', +          }, +          { +            pageTitle: post.title, +          } +        ); +        const coverLabel = intl.formatMessage( +          { +            defaultMessage: 'Cover of {pageTitle}', +            description: 'RecentPosts: card cover accessible name', +            id: 'kq+fzI', +          }, +          { +            pageTitle: post.title, +          } +        ); + +        return (            <Card +            aria-label={cardLabel}              cover={                post.cover ? ( -                <CardCover hasBorders> -                  <NextImage -                    {...post.cover} -                    style={{ objectFit: 'scale-down' }} -                  /> +                <CardCover aria-label={coverLabel} hasBorders> +                  <NextImage {...post.cover} />                  </CardCover>                ) : undefined              } @@ -285,65 +100,57 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {                </CardMeta>              }              isCentered -            linkTo={`${ROUTES.ARTICLE}/${post.slug}`} +            linkTo={postUrl}            >              <CardHeader>                <CardTitle level={3}>{post.title}</CardTitle>              </CardHeader>              <CardFooter />            </Card> -        ))} -      </Grid> -    ); -  }; +        ); +      })} +    </Grid> +  ); +}; -  const components: MDXComponents = { +const getComponents = (recentPosts: RecentArticle[]): MDXComponents => { +  return {      ...mdxComponents, -    CodingLinks, -    ColdarkRepos, -    Column, -    Grid: StyledGrid, -    LibreLinks, -    MoreLinks, -    RecentPosts: getRecentPosts, -    Section: HomePageSection, -    ShaarliLink, +    RecentPosts: () => <RecentPosts posts={recentPosts} />,    }; +}; + +type HomeProps = { +  recentPosts: RecentArticle[]; +  translation?: Messages; +}; + +/** + * Home page. + */ +const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { +  const { schema: breadcrumbSchema } = useBreadcrumb({ +    title: '', +    url: ROUTES.HOME, +  }); -  const pageTitle = intl.formatMessage( -    { -      defaultMessage: '{websiteName} | Front-end developer: WordPress/React', -      description: 'HomePage: SEO - Page title', -      id: 'PXp2hv', -    }, -    { websiteName: CONFIG.name } -  ); -  const pageDescription = intl.formatMessage( -    { -      defaultMessage: -        '{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.', -      description: 'HomePage: SEO - Meta description', -      id: 'tMuNTy', -    }, -    { websiteName: CONFIG.name } -  );    const webpageSchema = getWebPageSchema({ -    description: pageDescription, +    description: meta.seo.description,      locale: CONFIG.locales.defaultLocale, -    slug: '', -    title: pageTitle, +    slug: ROUTES.HOME, +    title: meta.seo.title,    });    const schemaJsonLd = getSchemaJson([webpageSchema]);    return ( -    <> +    <Page hasSections>        <Head> -        <title>{pageTitle}</title> +        <title>{meta.seo.title}</title>          {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} -        <meta name="description" content={pageDescription} /> +        <meta name="description" content={meta.seo.description} />          <meta property="og:url" content={CONFIG.url} /> -        <meta property="og:title" content={pageTitle} /> -        <meta property="og:description" content={pageDescription} /> +        <meta property="og:title" content={meta.seo.title} /> +        <meta property="og:description" content={meta.seo.description} />        </Head>        <Script          // eslint-disable-next-line react/jsx-no-literals -- Id allowed @@ -357,10 +164,8 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {          type="application/ld+json"          dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}        /> -      <Page hasSections> -        <HomePageContent components={components} /> -      </Page> -    </> +      <HomePageContent components={getComponents(recentPosts)} /> +    </Page>    );  }; diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index 9ea52e1..3d1e966 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -52,7 +52,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({    const intl = useIntl();    const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({      title, -    url: `${ROUTES.THEMATICS.INDEX}/${slug}`, +    url: `${ROUTES.THEMATICS}/${slug}`,    });    const { asPath } = useRouter(); @@ -189,8 +189,7 @@ export const getStaticProps: GetStaticProps<ThematicPageProps> = async ({    );    const allThematicsLinks = allThematics.filter(      (thematic) => -      thematic.url !== -      `${ROUTES.THEMATICS.INDEX}/${(params as ThematicParams).slug}` +      thematic.url !== `${ROUTES.THEMATICS}/${(params as ThematicParams).slug}`    );    const translation = await loadTranslation(locale); diff --git a/src/services/graphql/helpers/convert-taxonomy-to-page-link.test.ts b/src/services/graphql/helpers/convert-taxonomy-to-page-link.test.ts index 54a62ad..f923850 100644 --- a/src/services/graphql/helpers/convert-taxonomy-to-page-link.test.ts +++ b/src/services/graphql/helpers/convert-taxonomy-to-page-link.test.ts @@ -18,7 +18,7 @@ describe('convert-taxonomy-to-page-link', () => {      expect(result.id).toBe(thematic.databaseId);      expect(result.logo).toBeUndefined();      expect(result.name).toBe(thematic.title); -    expect(result.url).toBe(`${ROUTES.THEMATICS.INDEX}/${thematic.slug}`); +    expect(result.url).toBe(`${ROUTES.THEMATICS}/${thematic.slug}`);    });    it('can convert a WPTopicPreview object to a Topic object', () => { diff --git a/src/services/graphql/helpers/convert-taxonomy-to-page-link.ts b/src/services/graphql/helpers/convert-taxonomy-to-page-link.ts index 9b42eea..ca86a1e 100644 --- a/src/services/graphql/helpers/convert-taxonomy-to-page-link.ts +++ b/src/services/graphql/helpers/convert-taxonomy-to-page-link.ts @@ -28,7 +28,7 @@ export const convertWPThematicPreviewToPageLink = (  ): PageLink =>    convertTaxonomyToPageLink({      ...thematic, -    slug: `${ROUTES.THEMATICS.INDEX}/${thematic.slug}`, +    slug: `${ROUTES.THEMATICS}/${thematic.slug}`,    });  export const convertWPTopicPreviewToPageLink = ( diff --git a/src/services/graphql/helpers/convert-wp-thematic-to-thematic.test.ts b/src/services/graphql/helpers/convert-wp-thematic-to-thematic.test.ts index e535a21..435489d 100644 --- a/src/services/graphql/helpers/convert-wp-thematic-to-thematic.test.ts +++ b/src/services/graphql/helpers/convert-wp-thematic-to-thematic.test.ts @@ -42,7 +42,7 @@ describe('convert-wp-thematic-to-thematic', () => {      expect(result.meta.seo.description).toBe(thematic.seo.metaDesc);      expect(result.meta.seo.title).toBe(thematic.seo.title);      expect(result.meta.relatedTopics).toBeUndefined(); -    expect(result.slug).toBe(`${ROUTES.THEMATICS.INDEX}/${thematic.slug}`); +    expect(result.slug).toBe(`${ROUTES.THEMATICS}/${thematic.slug}`);      expect(result.title).toBe(thematic.title);    });    /* eslint-enable max-statements */ diff --git a/src/services/graphql/helpers/convert-wp-thematic-to-thematic.ts b/src/services/graphql/helpers/convert-wp-thematic-to-thematic.ts index cabfa18..9aa1896 100644 --- a/src/services/graphql/helpers/convert-wp-thematic-to-thematic.ts +++ b/src/services/graphql/helpers/convert-wp-thematic-to-thematic.ts @@ -54,7 +54,7 @@ export const convertWPThematicToThematic = (thematic: WPThematic): Thematic => {          ? getRelatedTopicsFrom(thematic.acfThematics.postsInThematic)          : undefined,      }, -    slug: `${ROUTES.THEMATICS.INDEX}/${thematic.slug}`, +    slug: `${ROUTES.THEMATICS}/${thematic.slug}`,      title: thematic.title,    };  }; diff --git a/src/styles/pages/home.module.scss b/src/styles/pages/home.module.scss deleted file mode 100644 index a926ec3..0000000 --- a/src/styles/pages/home.module.scss +++ /dev/null @@ -1,34 +0,0 @@ -@use "../abstracts/functions" as fun; -@use "../abstracts/mixins" as mix; - -.section { -  --card-width: 25ch; - -  &:last-of-type { -    border-bottom: none; -  } -} - -.columns { -  margin: 0 0 var(--spacing-sm); -} - -.list { -  margin: 0 0 var(--spacing-sm); - -  &--cards { -    @include mix.media("screen") { -      @include mix.dimensions("md") { -        margin: 0 calc(var(--spacing-sm) * -1) var(--spacing-sm); -      } -    } -  } -} - -.icon { -  margin-right: var(--spacing-2xs); - -  &--feed { -    width: var(--icon-size); -  } -} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 26cbeaa..043a530 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -2,7 +2,6 @@ export const PERSONAL_LINKS = {    GITHUB: 'https://github.com/ArmandPhilippot',    GITLAB: 'https://gitlab.com/ArmandPhilippot',    LINKEDIN: 'https://www.linkedin.com/in/armandphilippot', -  SHAARLI: 'https://shaarli.armandphilippot.com/',  } as const;  /** @@ -21,12 +20,7 @@ export const ROUTES = {    PROJECTS: '/projets',    RSS: '/feed',    SEARCH: '/recherche', -  THEMATICS: { -    INDEX: '/thematique', -    FREE: '/thematique/libre', -    LINUX: '/thematique/linux', -    WEB_DEV: '/thematique/developpement-web', -  }, +  THEMATICS: '/thematique',    TOPICS: '/sujet',  } as const; diff --git a/src/utils/helpers/schema-org.ts b/src/utils/helpers/schema-org.ts index 2edc11b..f028f5a 100644 --- a/src/utils/helpers/schema-org.ts +++ b/src/utils/helpers/schema-org.ts @@ -10,6 +10,9 @@ import type {  import type { Dates } from '../../types';  import { CONFIG } from '../config';  import { ROUTES } from '../constants'; +import { trimTrailingChars } from './strings'; + +const host = trimTrailingChars(CONFIG.url, '/');  export type GetBlogSchemaProps = {    /** @@ -38,22 +41,20 @@ export const getBlogSchema = ({    slug,  }: GetBlogSchemaProps): Blog => {    return { -    '@id': `${CONFIG.url}/#blog`, +    '@id': `${host}/#blog`,      '@type': 'Blog', -    author: { '@id': `${CONFIG.url}/#branding` }, -    creator: { '@id': `${CONFIG.url}/#branding` }, -    editor: { '@id': `${CONFIG.url}/#branding` }, -    blogPost: isSinglePage ? { '@id': `${CONFIG.url}/#article` } : undefined, +    author: { '@id': `${host}/#branding` }, +    creator: { '@id': `${host}/#branding` }, +    editor: { '@id': `${host}/#branding` }, +    blogPost: isSinglePage ? { '@id': `${host}/#article` } : undefined,      inLanguage: locale,      isPartOf: isSinglePage        ? { -          '@id': `${CONFIG.url}${slug}`, +          '@id': `${host}${slug}`,          }        : undefined,      license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', -    mainEntityOfPage: isSinglePage -      ? undefined -      : { '@id': `${CONFIG.url}${slug}` }, +    mainEntityOfPage: isSinglePage ? undefined : { '@id': `${host}${slug}` },    };  }; @@ -137,19 +138,19 @@ export const getSinglePageSchema = <T extends SinglePageSchemaKind>({    };    return { -    '@id': `${CONFIG.url}/#${id}`, +    '@id': `${host}/#${id}`,      '@type': singlePageSchemaType[kind],      name: title,      description,      articleBody: content, -    author: { '@id': `${CONFIG.url}/#branding` }, +    author: { '@id': `${host}/#branding` },      commentCount: commentsCount,      copyrightYear: publicationDate.getFullYear(), -    creator: { '@id': `${CONFIG.url}/#branding` }, +    creator: { '@id': `${host}/#branding` },      dateCreated: publicationDate.toISOString(),      dateModified: updateDate?.toISOString(),      datePublished: publicationDate.toISOString(), -    editor: { '@id': `${CONFIG.url}/#branding` }, +    editor: { '@id': `${host}/#branding` },      headline: title,      image: cover,      inLanguage: locale, @@ -158,10 +159,10 @@ export const getSinglePageSchema = <T extends SinglePageSchemaKind>({      isPartOf:        kind === 'post'          ? { -            '@id': `${CONFIG.url}${ROUTES.BLOG}`, +            '@id': `${host}${ROUTES.BLOG}`,            }          : undefined, -    mainEntityOfPage: { '@id': `${CONFIG.url}${slug}` }, +    mainEntityOfPage: { '@id': `${host}${slug}` },    } as SinglePageSchemaReturn[T];  }; @@ -202,17 +203,17 @@ export const getWebPageSchema = ({    updateDate,  }: GetWebPageSchemaProps): WebPage => {    return { -    '@id': `${CONFIG.url}${slug}`, +    '@id': `${host}${slug}`,      '@type': 'WebPage', -    breadcrumb: { '@id': `${CONFIG.url}/#breadcrumb` }, +    breadcrumb: { '@id': `${host}/#breadcrumb` },      lastReviewed: updateDate,      name: title,      description,      inLanguage: locale, -    reviewedBy: { '@id': `${CONFIG.url}/#branding` }, -    url: `${CONFIG.url}${slug}`, +    reviewedBy: { '@id': `${host}/#branding` }, +    url: `${host}${slug}`,      isPartOf: { -      '@id': `${CONFIG.url}`, +      '@id': `${host}`,      },    };  }; diff --git a/src/utils/helpers/strings.ts b/src/utils/helpers/strings.ts index 8b0f923..b8af61d 100644 --- a/src/utils/helpers/strings.ts +++ b/src/utils/helpers/strings.ts @@ -45,3 +45,16 @@ export const getDataAttributeFrom = (str: string) => {    if (str.startsWith('data-')) return str;    return `data-${str}`;  }; + +/** + * Remove the given character if present at the end of the given string. + * + * @param {string} str - A string to trim. + * @param {string} char - The character to remove. + * @returns {string} The trimmed string. + */ +export const trimTrailingChars = (str: string, char: string): string => { +  const regExp = new RegExp(`${char}+$`); + +  return str.replace(regExp, ''); +}; diff --git a/src/utils/hooks/use-breadcrumb.ts b/src/utils/hooks/use-breadcrumb.ts index 1cd18d9..8b23ff2 100644 --- a/src/utils/hooks/use-breadcrumb.ts +++ b/src/utils/hooks/use-breadcrumb.ts @@ -16,8 +16,7 @@ const isProject = (url: string) => url.startsWith(`${ROUTES.PROJECTS}/`);  const isSearch = (url: string) => url.startsWith(ROUTES.SEARCH); -const isThematic = (url: string) => -  url.startsWith(`${ROUTES.THEMATICS.INDEX}/`); +const isThematic = (url: string) => url.startsWith(`${ROUTES.THEMATICS}/`);  const isTopic = (url: string) => url.startsWith(`${ROUTES.TOPICS}/`); | 
