Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: generate image with sharp #1091

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ excerpt: Description of the article (Visible on the list pages)
cover:
alt: Alt image
path: /imgs/articles/YYYY-MM-DD-slug/cover.jpg
position: top | right top | right | right bottom | bottom | left bottom | left | left top | center | north | northeast | east | southeast | south | southwest | west | northwest // Default value is center
categories:
- javascript | php | agile | architecture
keywords:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ excerpt: >-
categories: []
cover:
path: /imgs/articles/2024-02-19-quelques-conseils-pour-optimiser-votre-environnement-de-travail-sous-linux/cover.jpg
position: top
authors:
- nicolas
keywords:
Expand Down
1 change: 1 addition & 0 deletions _tutorials/fr/2024-03-27-chromatic/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ excerpt: >-
Nous allons découvrir comment utiliser Chromatic en CI pour faire des tests de non régression visuelle et des tests d'interaction sur un Storybook pour être confiant à chaque nouvelle feature implémentée.
cover:
path: /imgs/tutorials/2024-03-27-chromatic/cover.jpg
position: top
categories:
- javascript
keywords:
Expand Down
4 changes: 2 additions & 2 deletions src/config/i18n/i18n.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { InitOptions } from 'i18next';

import { BASE_URL, DEFAULT_LANGUAGE, IS_DEBUG, LanguageEnum, LANGUAGES_AVAILABLE } from '@/constants';
import { BASE_URL, DEFAULT_LANGUAGE, IS_DEBUG, LANGUAGES, LANGUAGES_AVAILABLE } from '@/constants';

export const i18nConfig = {
load: 'languageOnly',
preload: LANGUAGES_AVAILABLE,
whitelist: LANGUAGES_AVAILABLE,
fallbackLng: IS_DEBUG ? LanguageEnum.DT : DEFAULT_LANGUAGE,
fallbackLng: IS_DEBUG ? LANGUAGES.DT : DEFAULT_LANGUAGE,
returnEmptyString: false,
defaultNS: 'messages',
ns: 'messages',
Expand Down
8 changes: 4 additions & 4 deletions src/config/router/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Params, RouteObject } from 'react-router';
import { Outlet } from 'react-router-dom';

import { LanguageEnum, LANGUAGES_AVAILABLE_WITH_DT, PATHS } from '@/constants';
import { LANGUAGES_AVAILABLE_WITH_DT, PATHS } from '@/constants';
import { AuthorPageContainer } from '@/containers/AuthorPageContainer';
import { CategoryPageContainer } from '@/containers/CategoryPageContainer';
import { HomePageContainer } from '@/containers/HomePageContainer';
Expand All @@ -16,7 +16,7 @@ import {
loadPostListPageData,
loadPostPageData,
} from '@/helpers/loaderDataHelper';
import { LayoutTemplateData } from '@/types';
import { LanguageType, LayoutTemplateData } from '@/types';

export const routes: RouteObject[] = [
{
Expand Down Expand Up @@ -50,8 +50,8 @@ export const routes: RouteObject[] = [
{
path: '/:lang/',
loader: ({ params }): Record<string, unknown> => {
const languages = LANGUAGES_AVAILABLE_WITH_DT as LanguageEnum[];
if (!languages.includes(params.lang as LanguageEnum)) {
const languages = LANGUAGES_AVAILABLE_WITH_DT as LanguageType[];
if (!languages.includes(params.lang as LanguageType)) {
throw new Error(`The \`${params.lang}\` language doesn't exist`);
}
return {};
Expand Down
4 changes: 2 additions & 2 deletions src/config/schemaValidation/AuthorDataValidationSchema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { z } from 'zod';

import { ContentTypeEnum } from '@/constants';
import { MARKDOWN_CONTENT_TYPES } from '@/constants';

export const AuthorDataValidationSchema = z.object({
contentType: z.literal(ContentTypeEnum.AUTHOR),
contentType: z.literal(MARKDOWN_CONTENT_TYPES.AUTHOR),
username: z.string().regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/, 'Kebab case format not respected'),
name: z.string(),
twitter: z
Expand Down
14 changes: 9 additions & 5 deletions src/config/schemaValidation/PostDataSchemaValidation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { z } from 'zod';

import { CATEGORIES, ContentTypeEnum, LanguageEnum } from '@/constants';
import { CATEGORIES, IMAGE_POSITIONS, LANGUAGES, MARKDOWN_CONTENT_TYPES } from '@/constants';
import { intersection } from '@/helpers/objectHelper';

export const PostDataSchemaValidation = z.object({
contentType: z.enum([ContentTypeEnum.ARTICLE, ContentTypeEnum.TUTORIAL]),
lang: z.nativeEnum(LanguageEnum),
contentType: z.nativeEnum({
ARTICLE: MARKDOWN_CONTENT_TYPES.ARTICLE,
TUTORIAL: MARKDOWN_CONTENT_TYPES.TUTORIAL,
} as const),
lang: z.nativeEnum(LANGUAGES),
date: z.coerce.date().transform((date) => date.toISOString().slice(0, 10)),
slug: z.string().regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/, 'Kebab case format not respected'),
title: z.string(),
Expand All @@ -14,6 +17,7 @@ export const PostDataSchemaValidation = z.object({
cover: z
.object({
path: z.string(),
position: z.nativeEnum(IMAGE_POSITIONS).optional(),
alt: z.string(),
})
.optional(),
Expand Down Expand Up @@ -48,13 +52,13 @@ export const PostDataSchemaValidation = z.object({

export const ArticleDataSchemaValidation = PostDataSchemaValidation.merge(
z.object({
contentType: z.literal(ContentTypeEnum.ARTICLE),
contentType: z.literal(MARKDOWN_CONTENT_TYPES.ARTICLE),
})
);

export const TutorialDataSchemaValidation = PostDataSchemaValidation.merge(
z.object({
contentType: z.literal(ContentTypeEnum.TUTORIAL),
contentType: z.literal(MARKDOWN_CONTENT_TYPES.TUTORIAL),
steps: z.array(z.string()),
})
);
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { z } from 'zod';

import { ContentTypeEnum } from '@/constants';
import { MARKDOWN_CONTENT_TYPES } from '@/constants';

export const TutorialStepDataValidationSchema = z.object({
contentType: z.literal(ContentTypeEnum.TUTORIAL_STEP),
contentType: z.literal(MARKDOWN_CONTENT_TYPES.TUTORIAL_STEP),
tutorial: z.string(),
slug: z.string(),
title: z.string(),
Expand Down
132 changes: 81 additions & 51 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getEnv } from '@/helpers/getEnvHelper';
import { DeviceType, ImageExtensionType, ImageFormatType } from '@/types';

export const IS_SSR = import.meta.env?.SSR ?? false;
export const IS_PRERENDER = import.meta.env?.MODE === 'prerender';
Expand All @@ -7,26 +8,25 @@ export const BASE_URL = import.meta.env?.BASE_URL || '/';

export const IS_DEBUG = getEnv<string>('VITE_IS_DEBUG') === 'true';

export enum LanguageEnum {
FR = 'fr',
EN = 'en',
DT = 'dt',
}
export const LANGUAGES = {
FR: 'fr',
EN: 'en',
DT: 'dt',
} as const;

export const LANGUAGES_AVAILABLE = [LanguageEnum.FR, LanguageEnum.EN] as const;
export const LANGUAGES_AVAILABLE_WITH_DT = IS_DEBUG ? [...LANGUAGES_AVAILABLE, LanguageEnum.DT] : LANGUAGES_AVAILABLE;
export const LANGUAGES_AVAILABLE = [LANGUAGES.FR, LANGUAGES.EN] as const;
export const LANGUAGES_AVAILABLE_WITH_DT = IS_DEBUG ? [...LANGUAGES_AVAILABLE, LANGUAGES.DT] : LANGUAGES_AVAILABLE;

export enum ContentTypeEnum {
ARTICLE = 'article',
TUTORIAL = 'tutorial',
TUTORIAL_STEP = 'tutorial-step',
AUTHOR = 'author',
}
export const MARKDOWN_CONTENT_TYPES = {
ARTICLE: 'article',
TUTORIAL: 'tutorial',
TUTORIAL_STEP: 'tutorial-step',
AUTHOR: 'author',
} as const;

export const CATEGORIES = ['javascript', 'php', 'agile', 'architecture'] as const;
export type CategoryEnum = (typeof CATEGORIES)[number];

export const DEFAULT_LANGUAGE = LanguageEnum.FR;
export const DEFAULT_LANGUAGE = LANGUAGES.FR;
export const NUMBER_OF_ITEMS_FOR_SEARCH = 6;
export const NUMBER_OF_ITEMS_PER_PAGE = 12;

Expand All @@ -51,53 +51,83 @@ export const GTM_ID = getEnv<string>('VITE_GTM_ID');

export const GOOGLE_SITE_VERIFICATION = getEnv<string>('VITE_GOOGLE_SITE_VERIFICATION');

export enum ImageFormatEnum {
HIGHLIGHTED_ARTICLE_POST_CARD_COVER = 'highlighted-article-post-card-cover',
HIGHLIGHTED_TUTORIAL_POST_CARD_COVER = 'highlighted-tutorial-post-card-cover',
POST_CARD_COVER = 'post-card-cover',
POST_COVER = 'post-cover',
}

export enum DeviceEnum {
MOBILE = 'mobile',
DESKTOP = 'desktop',
}

export const IMAGE_FORMATS: Record<DeviceEnum, Record<ImageFormatEnum, { width: number; height: number }>> = {
mobile: {
[ImageFormatEnum.HIGHLIGHTED_ARTICLE_POST_CARD_COVER]: {
width: 67,
height: 67,
},
[ImageFormatEnum.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER]: {
width: 328,
height: 130,
},
[ImageFormatEnum.POST_CARD_COVER]: {
width: 67,
height: 67,
},
[ImageFormatEnum.POST_COVER]: {
width: 330,
height: 160,
},
},
desktop: {
[ImageFormatEnum.HIGHLIGHTED_ARTICLE_POST_CARD_COVER]: {
export const IMAGE_FORMATS = {
HIGHLIGHTED_ARTICLE_POST_CARD_COVER: 'highlighted-article-post-card-cover',
HIGHLIGHTED_TUTORIAL_POST_CARD_COVER: 'highlighted-tutorial-post-card-cover',
POST_CARD_COVER: 'post-card-cover',
POST_COVER: 'post-cover',
} as const;

export const DEVICES = {
DESKTOP: 'desktop',
MOBILE: 'mobile',
} as const;

export const IMAGE_CONTENT_TYPES = {
jpeg: 'image/jpeg',
gif: 'image/gif',
webp: 'image/webp',
png: 'image/png',
avif: 'image/avif',
} as const;

export const IMAGE_POSITIONS = {
TOP: 'top',
RIGHT_AND_TOP: 'right top',
RIGHT: 'right',
RIGHT_BOTTOM: 'right bottom',
BOTTOM: 'bottom',
LEFT_AND_BOTTOM: 'left bottom',
LEFT: 'left',
LEFT_TOP: 'left top',
CENTER: 'center',
NORTH: 'north',
NORTHEAST: 'northeast',
EAST: 'east',
SOUTHEAST: 'southeast',
SOUTH: 'south',
SOUTHWEST: 'southwest',
WEST: 'west',
NORTHWEST: 'northwest',
} as const;

export const DEFAULT_EXTENSION_FOR_IMAGES: ImageExtensionType = 'avif';

export const SIZES_BY_IMAGE_FORMAT: Record<DeviceType, Record<ImageFormatType, { width: number; height: number }>> = {
[DEVICES.DESKTOP]: {
[IMAGE_FORMATS.HIGHLIGHTED_ARTICLE_POST_CARD_COVER]: {
width: 385,
height: 175,
},
[ImageFormatEnum.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER]: {
[IMAGE_FORMATS.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER]: {
width: 400,
height: 245,
},
[ImageFormatEnum.POST_CARD_COVER]: {
[IMAGE_FORMATS.POST_CARD_COVER]: {
width: 190,
height: 190,
},
[ImageFormatEnum.POST_COVER]: {
[IMAGE_FORMATS.POST_COVER]: {
width: 1200,
height: 330,
},
},
[DEVICES.MOBILE]: {
[IMAGE_FORMATS.HIGHLIGHTED_ARTICLE_POST_CARD_COVER]: {
width: 67,
height: 67,
},
[IMAGE_FORMATS.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER]: {
width: 328,
height: 130,
},
[IMAGE_FORMATS.POST_CARD_COVER]: {
width: 67,
height: 67,
},
[IMAGE_FORMATS.POST_COVER]: {
width: 330,
height: 160,
},
},
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Box, PostPageProps } from '@eleven-labs/design-system';
import React from 'react';
import { useTranslation } from 'react-i18next';

import { ContentTypeEnum } from '@/constants';
import { MARKDOWN_CONTENT_TYPES } from '@/constants';
import { slugify } from '@/helpers/stringHelper';
import { usePostPage } from '@/hooks/usePostPage';
import { ArticlePageData } from '@/types';
Expand All @@ -12,7 +12,7 @@ export const useArticlePageContainer = (article: ArticlePageData): PostPageProps
const postPage = usePostPage(article);

return {
variant: ContentTypeEnum.ARTICLE,
variant: MARKDOWN_CONTENT_TYPES.ARTICLE,
...postPage,
summary: {
title: t('pages.article.summary_card.title'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useLoaderData, useParams } from 'react-router-dom';

import { blogUrl } from '@/config/website';
import { ContentTypeEnum, DEFAULT_LANGUAGE, PATHS } from '@/constants';
import { DEFAULT_LANGUAGE, MARKDOWN_CONTENT_TYPES, PATHS } from '@/constants';
import { PostCardListContainer, PostCardListContainerProps } from '@/containers/PostCardListContainer';
import { TransWithHtml } from '@/containers/TransWithHtml';
import { generatePath } from '@/helpers/routerHelper';
Expand Down Expand Up @@ -40,7 +40,7 @@ export const useCategoryPageContainer = (): CategoryPageProps => {
title: <TransWithHtml i18nKey={`pages.category.${categoryName}.title`} onlyLineBreak />,
description: <TransWithHtml i18nKey={`pages.category.${categoryName}.description`} />,
},
categoryEndingBlock: !['all', ContentTypeEnum.TUTORIAL].includes(categoryName as string)
categoryEndingBlock: !['all', MARKDOWN_CONTENT_TYPES.TUTORIAL].includes(categoryName as string)
? {
title: <TransWithHtml i18nKey={`pages.category.${categoryName}.expertise.title`} onlyLineBreak />,
description: <TransWithHtml i18nKey={`pages.category.${categoryName}.expertise.description`} />,
Expand Down
16 changes: 8 additions & 8 deletions src/containers/HomePageContainer/useHomePageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useLoaderData } from 'react-router-dom';

import { blogUrl, websiteUrl } from '@/config/website';
import { ContentTypeEnum, DEFAULT_LANGUAGE, ImageFormatEnum, LanguageEnum, PATHS } from '@/constants';
import { DEFAULT_LANGUAGE, IMAGE_FORMATS, LANGUAGES, MARKDOWN_CONTENT_TYPES, PATHS } from '@/constants';
import { TransWithHtml } from '@/containers/TransWithHtml';
import { generatePath } from '@/helpers/routerHelper';
import { useNewsletterCard } from '@/hooks/useNewsletterCard';
Expand All @@ -22,21 +22,21 @@ export const useHomePageContainer = (): HomePageProps => {
posts: postListPageData.posts
.filter(
(post) =>
post.contentType === ContentTypeEnum.ARTICLE &&
(i18n.language === LanguageEnum.DT || post.lang === i18n.language)
post.contentType === MARKDOWN_CONTENT_TYPES.ARTICLE &&
(i18n.language === LANGUAGES.DT || post.lang === i18n.language)
)
.slice(0, 3),
imageFormatEnum: ImageFormatEnum.HIGHLIGHTED_ARTICLE_POST_CARD_COVER,
imageFormat: IMAGE_FORMATS.HIGHLIGHTED_ARTICLE_POST_CARD_COVER,
});
const lastTutorialsForCardList = usePostsForCardList({
posts: postListPageData.posts
.filter(
(post) =>
post.contentType === ContentTypeEnum.TUTORIAL &&
(i18n.language === LanguageEnum.DT || post.lang === i18n.language)
post.contentType === MARKDOWN_CONTENT_TYPES.TUTORIAL &&
(i18n.language === LANGUAGES.DT || post.lang === i18n.language)
)
.slice(0, 2),
imageFormatEnum: ImageFormatEnum.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER,
imageFormat: IMAGE_FORMATS.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER,
});

useTitle(t('pages.home.seo.title'));
Expand Down Expand Up @@ -73,7 +73,7 @@ export const useHomePageContainer = (): HomePageProps => {
posts: lastTutorialsForCardList,
linkSeeMore: {
label: t('pages.home.last-tutorials-block.link-see-more'),
href: generatePath(PATHS.CATEGORY, { categoryName: ContentTypeEnum.TUTORIAL, lang: i18n.language }),
href: generatePath(PATHS.CATEGORY, { categoryName: MARKDOWN_CONTENT_TYPES.TUTORIAL, lang: i18n.language }),
},
}
: undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PostCardListProps } from '@eleven-labs/design-system';

import { ImageFormatEnum, NUMBER_OF_ITEMS_PER_PAGE } from '@/constants';
import { IMAGE_FORMATS, NUMBER_OF_ITEMS_PER_PAGE } from '@/constants';
import { usePostsForCardList } from '@/hooks/usePostsForCardList';

import { PostCardListContainerProps } from './PostCardListContainer';
Expand All @@ -20,7 +20,7 @@ export const usePostCardListContainer = ({
isLoading,
numberOfItems: NUMBER_OF_ITEMS_PER_PAGE,
posts,
imageFormatEnum: ImageFormatEnum.POST_CARD_COVER,
imageFormat: IMAGE_FORMATS.POST_CARD_COVER,
});

return {
Expand Down