diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Branding/Branding.tsx | 63 | ||||
| -rw-r--r-- | src/components/Breadcrumb/Breadcrumb.tsx | 55 | ||||
| -rw-r--r-- | src/components/Comment/Comment.tsx | 42 | ||||
| -rw-r--r-- | src/components/Layouts/Layout.tsx | 24 | ||||
| -rw-r--r-- | src/components/PostPreview/PostPreview.tsx | 104 |
5 files changed, 226 insertions, 62 deletions
diff --git a/src/components/Branding/Branding.tsx b/src/components/Branding/Branding.tsx index 9421314..5e2cf6a 100644 --- a/src/components/Branding/Branding.tsx +++ b/src/components/Branding/Branding.tsx @@ -6,36 +6,57 @@ import photo from '@assets/images/armand-philippot.jpg'; import Logo from '@assets/images/armand-philippot-logo.svg'; import { config } from '@config/website'; import styles from './Branding.module.scss'; +import Head from 'next/head'; +import { Person, WithContext } from 'schema-dts'; type BrandingReturn = ({ isHome }: { isHome: boolean }) => ReactElement; const Branding: BrandingReturn = ({ isHome = false }) => { const TitleTag = isHome ? 'h1' : 'p'; + const schemaJsonLd: WithContext<Person> = { + '@context': 'https://schema.org', + '@type': 'Person', + '@id': `${config.url}/#branding`, + name: config.name, + url: config.url, + jobTitle: config.baseline, + image: photo.src, + subjectOf: { '@id': `${config.url}` }, + }; + return ( - <div className={styles.wrapper}> - <div className={styles.logo}> - <div className={styles.logo__front}> - <Image - src={photo} - alt={t({ - message: `${config.name} picture`, - comment: 'Branding logo.', - })} - layout="responsive" - /> - </div> - <div className={styles.logo__back}> - <Logo /> + <> + <Head> + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + ></script> + </Head> + <div id="branding" className={styles.wrapper}> + <div className={styles.logo}> + <div className={styles.logo__front}> + <Image + src={photo} + alt={t({ + message: `${config.name} picture`, + comment: 'Branding logo.', + })} + layout="responsive" + /> + </div> + <div className={styles.logo__back}> + <Logo /> + </div> </div> + <TitleTag className={styles.name}> + <Link href="/"> + <a className={styles.link}>{config.name}</a> + </Link> + </TitleTag> + <p className={styles.job}>{config.baseline}</p> </div> - <TitleTag className={styles.name}> - <Link href="/"> - <a className={styles.link}>{config.name}</a> - </Link> - </TitleTag> - <p className={styles.job}>{config.baseline}</p> - </div> + </> ); }; diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index 77e7c08..0b9977e 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -1,7 +1,9 @@ +import { config } from '@config/website'; import { t } from '@lingui/macro'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; +import { BreadcrumbList, WithContext } from 'schema-dts'; import styles from './Breadcrumb.module.scss'; const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => { @@ -15,9 +17,6 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => { const getItems = () => { return ( <> - <Head> - <script type="application/ld+json">{}</script> - </Head> <li className={styles.item}> <Link href="/"> <a>{t`Home`}</a> @@ -32,14 +31,62 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => { </li> </> )} + <li className="screen-reader-text">{pageTitle}</li> </> ); }; + const getElementsSchema = () => { + const items = []; + const homepage: BreadcrumbList['itemListElement'] = { + '@type': 'ListItem', + position: 1, + name: t`Home`, + item: config.url, + }; + + items.push(homepage); + + if (isArticle || isThematic || isSubject) { + const blog: BreadcrumbList['itemListElement'] = { + '@type': 'ListItem', + position: 2, + name: t`Blog`, + item: `${config.url}/blog`, + }; + + items.push(blog); + } + + const currentPage: BreadcrumbList['itemListElement'] = { + '@type': 'ListItem', + position: items.length + 1, + name: pageTitle, + item: `${config.url}${router.asPath}`, + }; + + items.push(currentPage); + + return items; + }; + + const schemaJsonLd: WithContext<BreadcrumbList> = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + '@id': `${config.url}/#breadcrumb`, + itemListElement: getElementsSchema(), + }; + return ( <> + <Head> + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + ></script> + </Head> {!isHome && ( - <nav className={styles.wrapper}> + <nav id="breadcrumb" className={styles.wrapper}> <span className="screen-reader-text">{t`You are here:`}</span> <ol className={styles.list}>{getItems()}</ol> </nav> diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx index e0a65f3..11300fc 100644 --- a/src/components/Comment/Comment.tsx +++ b/src/components/Comment/Comment.tsx @@ -1,11 +1,14 @@ import { Button } from '@components/Buttons'; import CommentForm from '@components/CommentForm/CommentForm'; +import { config } from '@config/website'; import { t } from '@lingui/macro'; import { Comment as CommentData } from '@ts/types/comments'; +import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useEffect, useRef, useState } from 'react'; +import { Comment as CommentSchema, WithContext } from 'schema-dts'; import styles from './Comment.module.scss'; const Comment = ({ @@ -117,10 +120,43 @@ const Comment = ({ return <p>{t`This comment is awaiting moderation.`}</p>; }; + const schemaJsonLd: WithContext<CommentSchema> = { + '@context': 'https://schema.org', + '@id': `${config.url}/#comment-${comment.commentId}`, + '@type': 'Comment', + parentItem: isNested + ? { '@id': `${config.url}/#comment-${comment.parentDatabaseId}` } + : undefined, + about: { '@type': 'Article', '@id': `${config.url}/#article` }, + author: { + '@type': 'Person', + name: comment.author.name, + image: comment.author.gravatarUrl, + url: comment.author.url, + }, + creator: { + '@type': 'Person', + name: comment.author.name, + image: comment.author.gravatarUrl, + url: comment.author.url, + }, + dateCreated: comment.date, + datePublished: comment.date, + text: comment.content, + }; + return ( - <li className={styles.item}> - {comment.approved ? getApprovedComment() : getCommentStatus()} - </li> + <> + <Head> + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + ></script> + </Head> + <li className={styles.item}> + {comment.approved ? getApprovedComment() : getCommentStatus()} + </li> + </> ); }; diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx index f5116f8..2e7d255 100644 --- a/src/components/Layouts/Layout.tsx +++ b/src/components/Layouts/Layout.tsx @@ -7,6 +7,7 @@ import { t } from '@lingui/macro'; import Head from 'next/head'; import { config } from '@config/website'; import { useRouter } from 'next/router'; +import { WebSite, WithContext } from 'schema-dts'; const Layout = ({ children, @@ -22,6 +23,25 @@ const Layout = ({ ref.current?.focus(); }, [asPath]); + const schemaJsonLd: WithContext<WebSite> = { + '@context': 'https://schema.org', + '@id': `${config.url}`, + '@type': 'WebSite', + name: config.name, + description: config.baseline, + url: config.url, + author: { '@id': `${config.url}/#branding` }, + copyrightYear: Number(config.copyright.startYear), + creator: { '@id': `${config.url}/#branding` }, + editor: { '@id': `${config.url}/#branding` }, + inLanguage: config.defaultLocale, + potentialAction: { + '@type': 'SearchAction', + target: `${config.url}/recherche?s={query}`, + query: 'required', + }, + }; + return ( <> <Head> @@ -43,6 +63,10 @@ const Layout = ({ type="application/feed+json" title={`${config.name}'s RSS feed`} /> + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + ></script> </Head> <span ref={ref} tabIndex={-1} /> <a href="#main" className="screen-reader-text">{t`Skip to content`}</a> diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx index 3bf7bdb..2a0bcf1 100644 --- a/src/components/PostPreview/PostPreview.tsx +++ b/src/components/PostPreview/PostPreview.tsx @@ -7,6 +7,9 @@ import Image from 'next/image'; import { ButtonLink } from '@components/Buttons'; import { ArrowIcon } from '@components/Icons'; import { TitleLevel } from '@ts/types/app'; +import { BlogPosting, WithContext } from 'schema-dts'; +import Head from 'next/head'; +import { config } from '@config/website'; const PostPreview = ({ post, @@ -24,41 +27,74 @@ const PostPreview = ({ thematics: post.thematics, }; + const publicationDate = new Date(post.dates.publication); + const updateDate = new Date(post.dates.update); + + const schemaJsonLd: WithContext<BlogPosting> = { + '@context': 'https://schema.org', + '@type': 'BlogPosting', + name: post.title, + description: post.intro, + articleBody: post.intro, + author: { '@id': `${config.url}/#branding` }, + commentCount: post.commentCount ? post.commentCount : 0, + copyrightYear: publicationDate.getFullYear(), + creator: { '@id': `${config.url}/#branding` }, + dateCreated: publicationDate.toISOString(), + dateModified: updateDate.toISOString(), + datePublished: publicationDate.toISOString(), + editor: { '@id': `${config.url}/#branding` }, + image: post.featuredImage?.sourceUrl, + inLanguage: config.defaultLocale, + isBasedOn: `${config.url}/article/${post.slug}`, + isPartOf: { '@id': `${config.url}/blog` }, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + thumbnailUrl: post.featuredImage?.sourceUrl, + }; + return ( - <article className={styles.wrapper}> - {post.featuredImage && Object.keys(post.featuredImage).length > 0 && ( - <div className={styles.cover}> - <Image - src={post.featuredImage.sourceUrl} - alt={post.featuredImage.altText} - layout="fill" - objectFit="contain" - /> - </div> - )} - <header className={styles.header}> - <TitleTag className={styles.title}> - <Link href={`/article/${post.slug}`}> - <a>{post.title}</a> - </Link> - </TitleTag> - </header> - <div - className={styles.body} - dangerouslySetInnerHTML={{ __html: post.intro }} - ></div> - <footer className={styles.footer}> - <ButtonLink target={`/article/${post.slug}`} position="left"> - {t`Read more`} - <span className="screen-reader-text"> - {' '} - {t({ message: `about ${post.title}`, comment: 'Post title' })} - </span> - <ArrowIcon /> - </ButtonLink> - </footer> - <PostMeta meta={meta} /> - </article> + <> + <Head> + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} + ></script> + </Head> + <article className={styles.wrapper}> + {post.featuredImage && Object.keys(post.featuredImage).length > 0 && ( + <div className={styles.cover}> + <Image + src={post.featuredImage.sourceUrl} + alt={post.featuredImage.altText} + layout="fill" + objectFit="contain" + /> + </div> + )} + <header className={styles.header}> + <TitleTag className={styles.title}> + <Link href={`/article/${post.slug}`}> + <a>{post.title}</a> + </Link> + </TitleTag> + </header> + <div + className={styles.body} + dangerouslySetInnerHTML={{ __html: post.intro }} + ></div> + <footer className={styles.footer}> + <ButtonLink target={`/article/${post.slug}`} position="left"> + {t`Read more`} + <span className="screen-reader-text"> + {' '} + {t({ message: `about ${post.title}`, comment: 'Post title' })} + </span> + <ArrowIcon /> + </ButtonLink> + </footer> + <PostMeta meta={meta} /> + </article> + </> ); }; |
