aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-01-25 15:33:27 +0100
committerArmand Philippot <git@armandphilippot.com>2022-01-25 15:38:50 +0100
commit97dc68e22e754d8e478beee590dbe9868171af50 (patch)
tree01876f9b236b0a4ba696d5b084bd766b69046494
parent71942c86311a9d1ddf4ae486d811f8393786e855 (diff)
chore: add reading time in posts meta
-rw-r--r--src/components/PostHeader/PostHeader.tsx4
-rw-r--r--src/components/PostMeta/PostMeta.tsx34
-rw-r--r--src/components/PostPreview/PostPreview.tsx55
-rw-r--r--src/pages/article/[slug].tsx3
-rw-r--r--src/services/graphql/queries.ts24
-rw-r--r--src/ts/types/app.ts6
-rw-r--r--src/ts/types/articles.ts18
-rw-r--r--src/ts/types/taxonomies.ts8
-rw-r--r--src/utils/helpers/format.ts8
9 files changed, 130 insertions, 30 deletions
diff --git a/src/components/PostHeader/PostHeader.tsx b/src/components/PostHeader/PostHeader.tsx
index 57d20fa..f070583 100644
--- a/src/components/PostHeader/PostHeader.tsx
+++ b/src/components/PostHeader/PostHeader.tsx
@@ -21,9 +21,11 @@ const PostHeader = ({
meta?.author ||
meta?.commentCount ||
meta?.dates ||
+ meta?.readingTime ||
meta?.results ||
meta?.thematics ||
- meta?.website
+ meta?.website ||
+ meta?.wordsCount
);
};
diff --git a/src/components/PostMeta/PostMeta.tsx b/src/components/PostMeta/PostMeta.tsx
index 9aa67c7..45f919a 100644
--- a/src/components/PostMeta/PostMeta.tsx
+++ b/src/components/PostMeta/PostMeta.tsx
@@ -13,8 +13,17 @@ const PostMeta = ({
meta: ArticleMeta;
mode?: PostMetaMode;
}) => {
- const { author, commentCount, dates, results, thematics, topics, website } =
- meta;
+ const {
+ author,
+ commentCount,
+ dates,
+ readingTime,
+ results,
+ thematics,
+ topics,
+ website,
+ wordsCount,
+ } = meta;
const { asPath, locale } = useRouter();
const isThematic = () => asPath.includes('/thematique/');
const isArticle = () => asPath.includes('/article/');
@@ -66,6 +75,16 @@ const PostMeta = ({
}
};
+ const getReadingTime = () => {
+ if (!readingTime) return;
+ if (readingTime < 0) return t`less than 1 minute`;
+ return plural(readingTime, {
+ zero: '# minutes',
+ one: '# minute',
+ other: '# minutes',
+ });
+ };
+
const wrapperClass = styles[`wrapper--${mode}`];
return (
@@ -95,6 +114,17 @@ const PostMeta = ({
</dd>
</div>
)}
+ {readingTime !== undefined && wordsCount !== undefined && (
+ <div className={styles.item}>
+ <dt className={styles.term}>{t`Reading time:`}</dt>
+ <dd
+ className={styles.description}
+ title={`Approximately ${wordsCount.toLocaleString(locale)} words`}
+ >
+ {getReadingTime()}
+ </dd>
+ </div>
+ )}
{results && (
<div className={styles.item}>
<dt className={styles.term}>{t`Total: `}</dt>
diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx
index b52d675..b084ca1 100644
--- a/src/components/PostPreview/PostPreview.tsx
+++ b/src/components/PostPreview/PostPreview.tsx
@@ -19,37 +19,50 @@ const PostPreview = ({
titleLevel: TitleLevel;
}) => {
const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements;
+ const {
+ commentCount,
+ dates,
+ featuredImage,
+ info,
+ intro,
+ slug,
+ thematics,
+ title,
+ topics,
+ } = post;
const meta: ArticleMeta = {
- commentCount: post.commentCount ? post.commentCount : 0,
- dates: post.dates,
- topics: post.topics,
- thematics: post.thematics,
+ commentCount: commentCount ? commentCount : 0,
+ dates: dates,
+ readingTime: info.readingTime,
+ thematics: thematics,
+ topics: topics,
+ wordsCount: info.wordsCount,
};
- const publicationDate = new Date(post.dates.publication);
- const updateDate = new Date(post.dates.update);
+ const publicationDate = new Date(dates.publication);
+ const updateDate = new Date(dates.update);
const schemaJsonLd: WithContext<BlogPosting> = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
- name: post.title,
- description: post.intro,
- articleBody: post.intro,
+ name: title,
+ description: intro,
+ articleBody: intro,
author: { '@id': `${config.url}/#branding` },
- commentCount: post.commentCount ? post.commentCount : 0,
+ commentCount: commentCount ? 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,
+ image: featuredImage?.sourceUrl,
inLanguage: config.locales.defaultLocale,
- isBasedOn: `${config.url}/article/${post.slug}`,
+ isBasedOn: `${config.url}/article/${slug}`,
isPartOf: { '@id': `${config.url}/blog` },
license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- thumbnailUrl: post.featuredImage?.sourceUrl,
+ thumbnailUrl: featuredImage?.sourceUrl,
};
return (
@@ -61,11 +74,11 @@ const PostPreview = ({
></script>
</Head>
<article className={styles.wrapper}>
- {post.featuredImage && Object.keys(post.featuredImage).length > 0 && (
+ {featuredImage && Object.keys(featuredImage).length > 0 && (
<div className={styles.cover}>
<Image
- src={post.featuredImage.sourceUrl}
- alt={post.featuredImage.altText}
+ src={featuredImage.sourceUrl}
+ alt={featuredImage.altText}
layout="fill"
objectFit="contain"
/>
@@ -73,21 +86,21 @@ const PostPreview = ({
)}
<header className={styles.header}>
<TitleTag className={styles.title}>
- <Link href={`/article/${post.slug}`}>
- <a>{post.title}</a>
+ <Link href={`/article/${slug}`}>
+ <a>{title}</a>
</Link>
</TitleTag>
</header>
<div
className={styles.body}
- dangerouslySetInnerHTML={{ __html: post.intro }}
+ dangerouslySetInnerHTML={{ __html: intro }}
></div>
<footer className={styles.footer}>
- <ButtonLink target={`/article/${post.slug}`} position="left">
+ <ButtonLink target={`/article/${slug}`} position="left">
{t`Read more`}
<span className="screen-reader-text">
{' '}
- {t({ message: `about ${post.title}`, comment: 'Post title' })}
+ {t({ message: `about ${title}`, comment: 'Post title' })}
</span>
<ArrowIcon />
</ButtonLink>
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index f43c9ee..c9aedb8 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -28,6 +28,7 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
databaseId,
dates,
featuredImage,
+ info,
intro,
seo,
topics,
@@ -39,7 +40,9 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
author,
commentCount: comments.length,
dates,
+ readingTime: info.readingTime,
thematics,
+ wordsCount: info.wordsCount,
};
const router = useRouter();
diff --git a/src/services/graphql/queries.ts b/src/services/graphql/queries.ts
index e7a76b1..0817cf6 100644
--- a/src/services/graphql/queries.ts
+++ b/src/services/graphql/queries.ts
@@ -87,6 +87,10 @@ export const getPublishedPosts = async ({
}
}
id
+ info {
+ readingTime
+ wordsCount
+ }
databaseId
modified
slug
@@ -201,6 +205,10 @@ export const getPostBySlug = async (slug: string): Promise<Article> => {
}
}
id
+ info {
+ readingTime
+ wordsCount
+ }
modified
seo {
title
@@ -269,6 +277,10 @@ export const getTopicBySlug = async (slug: string): Promise<Topic> => {
}
}
id
+ info {
+ readingTime
+ wordsCount
+ }
commentCount
contentParts {
beforeMore
@@ -302,6 +314,10 @@ export const getTopicBySlug = async (slug: string): Promise<Topic> => {
}
}
id
+ info {
+ readingTime
+ wordsCount
+ }
modified
seo {
metaDesc
@@ -402,6 +418,10 @@ export const getThematicBySlug = async (slug: string): Promise<Thematic> => {
}
}
id
+ info {
+ readingTime
+ wordsCount
+ }
commentCount
contentParts {
beforeMore
@@ -428,6 +448,10 @@ export const getThematicBySlug = async (slug: string): Promise<Thematic> => {
databaseId
date
id
+ info {
+ readingTime
+ wordsCount
+ }
modified
seo {
metaDesc
diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts
index a58f710..ddd64d1 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -61,6 +61,11 @@ export type ButtonKind = 'primary' | 'secondary' | 'tertiary';
export type ButtonPosition = 'left' | 'right' | 'center';
+export type ContentInfo = {
+ readingTime: number;
+ wordsCount: number;
+};
+
export type ContentParts = {
afterMore: string;
beforeMore: string;
@@ -110,6 +115,7 @@ export type Project = {
intro: string;
meta: ProjectMeta;
slug: string;
+ tagline?: string;
title: string;
seo: {
title: string;
diff --git a/src/ts/types/articles.ts b/src/ts/types/articles.ts
index 88b79dd..5281e7e 100644
--- a/src/ts/types/articles.ts
+++ b/src/ts/types/articles.ts
@@ -1,4 +1,4 @@
-import { ContentParts, Dates } from './app';
+import { ContentInfo, ContentParts, Dates } from './app';
import { Comment, CommentsNode } from './comments';
import { Cover, RawCover } from './cover';
import { SEO } from './seo';
@@ -24,10 +24,12 @@ export type ArticleMeta = {
author?: ArticleAuthor;
commentCount?: number;
dates?: Dates;
+ readingTime?: number;
results?: number;
topics?: TopicPreview[];
thematics?: ThematicPreview[];
website?: string;
+ wordsCount?: number;
};
export type Article = {
@@ -39,6 +41,7 @@ export type Article = {
dates: Dates;
featuredImage: Cover;
id: string;
+ info: ContentInfo;
intro: string;
seo: SEO;
topics: TopicPreview[] | [];
@@ -48,7 +51,7 @@ export type Article = {
export type RawArticle = Pick<
Article,
- 'commentCount' | 'databaseId' | 'id' | 'seo' | 'title'
+ 'commentCount' | 'databaseId' | 'id' | 'info' | 'seo' | 'title'
> & {
acfPosts: RawACFPosts;
author: { node: ArticleAuthor };
@@ -61,12 +64,19 @@ export type RawArticle = Pick<
export type ArticlePreview = Pick<
Article,
- 'commentCount' | 'dates' | 'id' | 'intro' | 'topics' | 'thematics' | 'title'
+ | 'commentCount'
+ | 'dates'
+ | 'id'
+ | 'info'
+ | 'intro'
+ | 'topics'
+ | 'thematics'
+ | 'title'
> & { featuredImage: Cover; slug: string };
export type RawArticlePreview = Pick<
Article,
- 'commentCount' | 'id' | 'title'
+ 'commentCount' | 'id' | 'info' | 'title'
> & {
acfPosts: ACFPosts;
contentParts: Pick<ContentParts, 'beforeMore'>;
diff --git a/src/ts/types/taxonomies.ts b/src/ts/types/taxonomies.ts
index 7d4ad3b..a62bef4 100644
--- a/src/ts/types/taxonomies.ts
+++ b/src/ts/types/taxonomies.ts
@@ -1,4 +1,4 @@
-import { ContentParts, Dates, Slug } from './app';
+import { ContentInfo, ContentParts, Dates, Slug } from './app';
import { ArticlePreview, RawArticlePreview } from './articles';
import { Cover, RawCover } from './cover';
import { SEO } from './seo';
@@ -12,13 +12,17 @@ type Taxonomy = {
databaseId: number;
dates: Dates;
id: string;
+ info: ContentInfo;
intro: string;
posts: ArticlePreview[];
seo: SEO;
title: string;
};
-type TaxonomyPreview = Pick<Taxonomy, 'databaseId' | 'id' | 'seo' | 'title'> & {
+type TaxonomyPreview = Pick<
+ Taxonomy,
+ 'databaseId' | 'id' | 'info' | 'seo' | 'title'
+> & {
slug: string;
};
diff --git a/src/utils/helpers/format.ts b/src/utils/helpers/format.ts
index 0ed1ab5..2be1844 100644
--- a/src/utils/helpers/format.ts
+++ b/src/utils/helpers/format.ts
@@ -27,6 +27,7 @@ export const getFormattedPostPreview = (rawPost: RawArticlePreview) => {
date,
featuredImage,
id,
+ info,
modified,
slug,
title,
@@ -45,6 +46,7 @@ export const getFormattedPostPreview = (rawPost: RawArticlePreview) => {
dates,
featuredImage: featuredImage ? featuredImage.node : null,
id,
+ info,
intro: contentParts.beforeMore,
slug,
topics,
@@ -83,6 +85,7 @@ export const getFormattedTopic = (rawTopic: RawTopic): Topic => {
date,
featuredImage,
id,
+ info,
modified,
seo,
title,
@@ -101,6 +104,7 @@ export const getFormattedTopic = (rawTopic: RawTopic): Topic => {
dates,
featuredImage: featuredImage ? featuredImage.node : null,
id,
+ info,
intro: contentParts.beforeMore,
officialWebsite: acfTopics.officialWebsite,
posts,
@@ -123,6 +127,7 @@ export const getFormattedThematic = (rawThematic: RawThematic): Thematic => {
databaseId,
date,
id,
+ info,
modified,
seo,
title,
@@ -140,6 +145,7 @@ export const getFormattedThematic = (rawThematic: RawThematic): Thematic => {
databaseId,
dates,
id,
+ info,
intro: contentParts.beforeMore,
posts,
seo,
@@ -225,6 +231,7 @@ export const getFormattedPost = (rawPost: RawArticle): Article => {
date,
featuredImage,
id,
+ info,
modified,
seo,
title,
@@ -250,6 +257,7 @@ export const getFormattedPost = (rawPost: RawArticle): Article => {
dates,
featuredImage: featuredImage ? featuredImage.node : null,
id,
+ info,
intro: contentParts.beforeMore,
seo,
topics,