diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-01-25 15:33:27 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-01-25 15:38:50 +0100 |
| commit | 97dc68e22e754d8e478beee590dbe9868171af50 (patch) | |
| tree | 01876f9b236b0a4ba696d5b084bd766b69046494 | |
| parent | 71942c86311a9d1ddf4ae486d811f8393786e855 (diff) | |
chore: add reading time in posts meta
| -rw-r--r-- | src/components/PostHeader/PostHeader.tsx | 4 | ||||
| -rw-r--r-- | src/components/PostMeta/PostMeta.tsx | 34 | ||||
| -rw-r--r-- | src/components/PostPreview/PostPreview.tsx | 55 | ||||
| -rw-r--r-- | src/pages/article/[slug].tsx | 3 | ||||
| -rw-r--r-- | src/services/graphql/queries.ts | 24 | ||||
| -rw-r--r-- | src/ts/types/app.ts | 6 | ||||
| -rw-r--r-- | src/ts/types/articles.ts | 18 | ||||
| -rw-r--r-- | src/ts/types/taxonomies.ts | 8 | ||||
| -rw-r--r-- | src/utils/helpers/format.ts | 8 |
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, |
