From c9c1c90b30e243563bb4f731da15b3fe657556d2 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 6 Nov 2023 18:08:04 +0100 Subject: refactor(components): replace Summary component with PostPreview * rename component to PostPreview because Summary is an HTML element and it could lead to confusion * replace `title` and `titleLevel` with `heading` and `headingLvl` because `title` is a native attribute * rename `intro` prop to `excerpt` * extract `cover` from `meta` prop * rewrite meta type * extract meta logic into a new component --- src/utils/helpers/index.ts | 1 + src/utils/helpers/pages.ts | 102 --------------------------- src/utils/helpers/pages.tsx | 124 +++++++++++++++++++++++++++++++++ src/utils/helpers/reading-time.test.ts | 31 +++++++++ src/utils/helpers/reading-time.ts | 46 ++++++++++++ 5 files changed, 202 insertions(+), 102 deletions(-) delete mode 100644 src/utils/helpers/pages.ts create mode 100644 src/utils/helpers/pages.tsx create mode 100644 src/utils/helpers/reading-time.test.ts create mode 100644 src/utils/helpers/reading-time.ts (limited to 'src/utils/helpers') diff --git a/src/utils/helpers/index.ts b/src/utils/helpers/index.ts index 14487e6..f340a49 100644 --- a/src/utils/helpers/index.ts +++ b/src/utils/helpers/index.ts @@ -1,6 +1,7 @@ export * from './author'; export * from './images'; export * from './pages'; +export * from './reading-time'; export * from './rss'; export * from './schema-org'; export * from './strings'; diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts deleted file mode 100644 index 84854cd..0000000 --- a/src/utils/helpers/pages.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { LinksListItems, Post } from '../../components'; -import { getArticleFromRawData } from '../../services/graphql'; -import type { - Article, - EdgesResponse, - PageLink, - RawArticle, - RawThematicPreview, - RawTopicPreview, -} from '../../types'; -import { ROUTES } from '../constants'; -import { getImageFromRawData } from './images'; - -/** - * Convert raw data to a Link object. - * - * @param data - An object. - * @param {number} data.databaseId - The data id. - * @param {number} [data.logo] - The data logo. - * @param {string} data.slug - The data slug. - * @param {string} data.title - The data name. - * @returns {PageLink} The link data (id, slug and title). - */ -export const getPageLinkFromRawData = ( - data: RawThematicPreview | RawTopicPreview, - kind: 'thematic' | 'topic' -): PageLink => { - const { databaseId, featuredImage, slug, title } = data; - const baseUrl = `${ - kind === 'thematic' ? ROUTES.THEMATICS.INDEX : ROUTES.TOPICS - }/`; - - return { - id: databaseId, - logo: featuredImage ? getImageFromRawData(featuredImage.node) : undefined, - name: title, - url: `${baseUrl}${slug}`, - }; -}; - -/** - * Method to sort PageLink objects by name. - * - * @param {PageLink} a - A PageLink object. - * @param {PageLink} b - Another PageLink object. - * @returns {1 | -1 | 0} - */ -export const sortPageLinksByName = (a: PageLink, b: PageLink) => { - const nameA = a.name.toUpperCase(); - const nameB = b.name.toUpperCase(); - - if (nameA < nameB) return -1; - if (nameA > nameB) return 1; - return 0; -}; - -/** - * Convert page link data to an array of links items. - * - * @param {PageLink[]} links - An array of page links. - * @returns {LinksListItem[]} An array of links items. - */ -export const getLinksListItems = (links: PageLink[]): LinksListItems[] => - links.map((link) => { - return { - name: link.name, - url: link.url, - }; - }); - -/** - * Retrieve the posts list with the article URL. - * - * @param {Article[]} posts - An array of articles. - * @returns {Post[]} An array of posts with full article URL. - */ -export const getPostsWithUrl = (posts: Article[]): Post[] => - posts.map((post) => { - return { - ...post, - url: `/article/${post.slug}`, - }; - }); - -/** - * Retrieve the posts list from raw data. - * - * @param {EdgesResponse[]} rawData - The raw data. - * @returns {Post[]} An array of posts. - */ -export const getPostsList = (rawData: EdgesResponse[]): Post[] => { - const articlesList: RawArticle[] = []; - rawData.forEach((articleData) => { - articleData.edges.forEach((edge) => { - articlesList.push(edge.node); - }); - }); - - return getPostsWithUrl( - articlesList.map((article) => getArticleFromRawData(article)) - ); -}; diff --git a/src/utils/helpers/pages.tsx b/src/utils/helpers/pages.tsx new file mode 100644 index 0000000..556b4fb --- /dev/null +++ b/src/utils/helpers/pages.tsx @@ -0,0 +1,124 @@ +import NextImage from 'next/image'; +import type { LinksListItems, PostData } from '../../components'; +import { getArticleFromRawData } from '../../services/graphql'; +import type { + Article, + EdgesResponse, + PageLink, + RawArticle, + RawThematicPreview, + RawTopicPreview, +} from '../../types'; +import { ROUTES } from '../constants'; +import { getImageFromRawData } from './images'; + +/** + * Convert raw data to a Link object. + * + * @param data - An object. + * @param {number} data.databaseId - The data id. + * @param {number} [data.logo] - The data logo. + * @param {string} data.slug - The data slug. + * @param {string} data.title - The data name. + * @returns {PageLink} The link data (id, slug and title). + */ +export const getPageLinkFromRawData = ( + data: RawThematicPreview | RawTopicPreview, + kind: 'thematic' | 'topic' +): PageLink => { + const { databaseId, featuredImage, slug, title } = data; + const baseUrl = `${ + kind === 'thematic' ? ROUTES.THEMATICS.INDEX : ROUTES.TOPICS + }/`; + + return { + id: databaseId, + logo: featuredImage ? getImageFromRawData(featuredImage.node) : undefined, + name: title, + url: `${baseUrl}${slug}`, + }; +}; + +/** + * Method to sort PageLink objects by name. + * + * @param {PageLink} a - A PageLink object. + * @param {PageLink} b - Another PageLink object. + * @returns {1 | -1 | 0} + */ +export const sortPageLinksByName = (a: PageLink, b: PageLink) => { + const nameA = a.name.toUpperCase(); + const nameB = b.name.toUpperCase(); + + if (nameA < nameB) return -1; + if (nameA > nameB) return 1; + return 0; +}; + +/** + * Convert page link data to an array of links items. + * + * @param {PageLink[]} links - An array of page links. + * @returns {LinksListItem[]} An array of links items. + */ +export const getLinksListItems = (links: PageLink[]): LinksListItems[] => + links.map((link) => { + return { + name: link.name, + url: link.url, + }; + }); + +/** + * Retrieve the posts list with the article URL. + * + * @param {Article[]} posts - An array of articles. + * @returns {PostData[]} An array of posts with full article URL. + */ +export const getPostsWithUrl = (posts: Article[]): PostData[] => + posts.map(({ intro, meta, slug, title, ...post }) => { + return { + ...post, + cover: meta.cover ? : undefined, + excerpt: intro, + heading: title, + meta: { + publicationDate: meta.dates.publication, + updateDate: meta.dates.update, + wordsCount: meta.wordsCount, + author: meta.author?.name, + thematics: meta.thematics, + topics: meta.topics, + comments: + meta.commentsCount === undefined + ? undefined + : { + count: meta.commentsCount, + postHeading: title, + url: `${ROUTES.ARTICLE}/${slug}#comments`, + }, + }, + url: `${ROUTES.ARTICLE}/${slug}`, + }; + }); + +/** + * Retrieve the posts list from raw data. + * + * @param {EdgesResponse[]} rawData - The raw data. + * @returns {PostData[]} An array of posts. + */ +export const getPostsList = ( + rawData: EdgesResponse[] +): PostData[] => { + const articlesList: RawArticle[] = []; + rawData.forEach((articleData) => { + articleData.edges.forEach((edge) => { + articlesList.push(edge.node); + }); + }); + + return getPostsWithUrl( + articlesList.map((article) => getArticleFromRawData(article)) + ); +}; diff --git a/src/utils/helpers/reading-time.test.ts b/src/utils/helpers/reading-time.test.ts new file mode 100644 index 0000000..24181a6 --- /dev/null +++ b/src/utils/helpers/reading-time.test.ts @@ -0,0 +1,31 @@ +import { describe, it } from '@jest/globals'; +import { getReadingTimeFrom } from './reading-time'; + +describe('reading-time', () => { + it('can transform a words count into a reading time in minutes', () => { + const wordsCount = 250; + + // With the default settings, 250 words should be rounded to one minute. + expect(getReadingTimeFrom(wordsCount).inMinutes()).toBe(1); + }); + + it('can transform a words count into a reading time in minutes and seconds', () => { + const wordsCount = 1200; + const readingTime = getReadingTimeFrom(wordsCount).inMinutesAndSeconds(); + + expect(readingTime.minutes).toBeGreaterThan(1); + expect(readingTime.seconds).toBeGreaterThan(0); + }); + + it('can use a custom words per minute setting', () => { + const wordsCount = 100; + const wordsPerMinute = 100; + const readingTime = getReadingTimeFrom( + wordsCount, + wordsPerMinute + ).inMinutesAndSeconds(); + + expect(readingTime.minutes).toBe(1); + expect(readingTime.seconds).toBe(0); + }); +}); diff --git a/src/utils/helpers/reading-time.ts b/src/utils/helpers/reading-time.ts new file mode 100644 index 0000000..6cdeba4 --- /dev/null +++ b/src/utils/helpers/reading-time.ts @@ -0,0 +1,46 @@ +export type GetReadingTimeReturn = { + /** + * The reading time rounded to minutes. + */ + inMinutes: () => number; + /** + * The reading time in minutes and seconds. + */ + inMinutesAndSeconds: () => { + minutes: number; + seconds: number; + }; +}; + +/** + * Retrieve the reading time from a words count. + * + * @param {number} wordsCount - The number of words. + * @param {number} [wordsPerMinute] - How many words can we read per minute? + * @returns {GetReadingTimeReturn} Two methods to retrieve the reading time. + */ +export const getReadingTimeFrom = ( + wordsCount: number, + wordsPerMinute = 245 +): GetReadingTimeReturn => { + const ONE_MINUTE_IN_SECONDS = 60; + const wordsPerSecond = wordsPerMinute / ONE_MINUTE_IN_SECONDS; + const estimatedTimeInSeconds = wordsCount / wordsPerSecond; + + return { + inMinutes: () => Math.round(estimatedTimeInSeconds / ONE_MINUTE_IN_SECONDS), + inMinutesAndSeconds: () => { + const estimatedTimeInMinutes = Math.floor( + estimatedTimeInSeconds / ONE_MINUTE_IN_SECONDS + ); + + return { + minutes: estimatedTimeInMinutes, + seconds: Math.round( + estimatedTimeInSeconds - + estimatedTimeInMinutes * ONE_MINUTE_IN_SECONDS + ), + }; + }, + }; +}; -- cgit v1.2.3