summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-16 19:40:23 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-16 19:40:23 +0200
commitc77c58e18143233be042c4980a6ed08ae9beac52 (patch)
tree94f7d828571a86470ae299fff7dffd32fb38de7c /src
parent2155550fa36a3bc3c8f66e0926530123b4018cd4 (diff)
chore: adjust and complete missing styles
* add logo to topics pages and links * add Prism styles to articles * and a few other adjustements
Diffstat (limited to 'src')
-rw-r--r--src/components/atoms/forms/form.tsx3
-rw-r--r--src/components/atoms/links/link.module.scss23
-rw-r--r--src/components/atoms/lists/description-list-item.module.scss2
-rw-r--r--src/components/molecules/layout/meta.tsx59
-rw-r--r--src/components/molecules/layout/page-footer.tsx4
-rw-r--r--src/components/molecules/layout/page-header.module.scss6
-rw-r--r--src/components/molecules/layout/page-header.tsx11
-rw-r--r--src/components/organisms/forms/comment-form.tsx12
-rw-r--r--src/components/organisms/widgets/sharing.stories.tsx21
-rw-r--r--src/components/organisms/widgets/sharing.tsx7
-rw-r--r--src/components/templates/page/page-layout.module.scss6
-rw-r--r--src/components/templates/page/page-layout.stories.tsx2
-rw-r--r--src/components/templates/page/page-layout.tsx17
-rw-r--r--src/pages/article/[slug].tsx43
-rw-r--r--src/pages/blog/index.tsx12
-rw-r--r--src/pages/cv.tsx19
-rw-r--r--src/pages/recherche/index.tsx12
-rw-r--r--src/pages/sujet/[slug].tsx35
-rw-r--r--src/services/graphql/articles.query.ts11
-rw-r--r--src/services/graphql/topics.query.ts11
-rw-r--r--src/styles/base/_typography.scss24
-rw-r--r--src/styles/components/_wp-blocks.scss166
-rw-r--r--src/styles/globals.scss10
-rw-r--r--src/styles/pages/article.module.scss36
-rw-r--r--src/styles/pages/partials/_article-headings.scss57
-rw-r--r--src/styles/pages/partials/_article-links.scss104
-rw-r--r--src/styles/pages/partials/_article-lists.scss65
-rw-r--r--src/styles/pages/partials/_article-media.scss11
-rw-r--r--src/styles/pages/partials/_article-prism.scss301
-rw-r--r--src/styles/pages/partials/_article-wp-blocks.scss168
-rw-r--r--src/styles/pages/topic.module.scss6
-rw-r--r--src/ts/types/app.ts1
-rw-r--r--src/ts/types/raw-data.ts7
-rw-r--r--src/utils/helpers/pages.ts5
-rw-r--r--src/utils/hooks/use-add-prism-class-attr.tsx60
35 files changed, 1036 insertions, 301 deletions
diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx
index ef8dce4..b819aea 100644
--- a/src/components/atoms/forms/form.tsx
+++ b/src/components/atoms/forms/form.tsx
@@ -35,7 +35,6 @@ export type FormProps = {
*/
const Form: FC<FormProps> = ({
children,
- className = '',
grouped = true,
onSubmit,
...props
@@ -68,7 +67,7 @@ const Form: FC<FormProps> = ({
};
return (
- <form onSubmit={handleSubmit} className={className} {...props}>
+ <form onSubmit={handleSubmit} {...props}>
{getFormItems()}
</form>
);
diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss
index 5c97bd2..1b89727 100644
--- a/src/components/atoms/links/link.module.scss
+++ b/src/components/atoms/links/link.module.scss
@@ -2,29 +2,6 @@
@use "@styles/abstracts/variables" as var;
.link {
- background: linear-gradient(to top, var(--color-primary) 50%, transparent 50%)
- 0 0 / 100% 201% no-repeat;
- color: var(--color-primary);
- text-decoration-thickness: 0.15em;
- text-underline-offset: 20%;
- transition: all 0.3s linear 0s, text-decoration 0.18s ease-in-out 0s;
-
- &:hover {
- color: var(--color-primary-light);
- text-decoration-thickness: 0.25em;
- }
-
- &:focus {
- background-position: 0 100%;
- color: var(--color-fg-inverted);
- }
-
- &:active {
- background-position: 0 0;
- color: var(--color-primary-dark);
- text-decoration-thickness: 18%;
- }
-
&[hreflang] {
&::after {
display: inline-block;
diff --git a/src/components/atoms/lists/description-list-item.module.scss b/src/components/atoms/lists/description-list-item.module.scss
index 60cad57..aba90ce 100644
--- a/src/components/atoms/lists/description-list-item.module.scss
+++ b/src/components/atoms/lists/description-list-item.module.scss
@@ -27,6 +27,8 @@
}
&--inline-values {
+ row-gap: var(--spacing-2xs);
+
.term {
flex: 1 1 100%;
}
diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx
index 1f6219a..74bd4ff 100644
--- a/src/components/molecules/layout/meta.tsx
+++ b/src/components/molecules/layout/meta.tsx
@@ -92,13 +92,17 @@ export type MetaData = {
*/
topics?: string[] | JSX.Element[];
/**
- * A total.
+ * A total number of posts.
*/
- total?: string;
+ total?: number;
/**
* The update date.
*/
update?: MetaDate;
+ /**
+ * An url.
+ */
+ website?: string;
};
export type MetaKey = keyof MetaData;
@@ -145,80 +149,86 @@ const Meta: FC<MetaProps> = ({
case 'author':
return intl.formatMessage({
defaultMessage: 'Written by:',
- id: 'OI0N37',
description: 'Meta: author label',
+ id: 'OI0N37',
});
case 'comments':
return intl.formatMessage({
defaultMessage: 'Comments:',
- id: 'jTVIh8',
description: 'Meta: comments label',
+ id: 'jTVIh8',
});
case 'creation':
return intl.formatMessage({
defaultMessage: 'Created on:',
- id: 'b4fdYE',
description: 'Meta: creation date label',
+ id: 'b4fdYE',
});
case 'license':
return intl.formatMessage({
defaultMessage: 'License:',
- id: 'AuGklx',
description: 'Meta: license label',
+ id: 'AuGklx',
});
case 'popularity':
return intl.formatMessage({
defaultMessage: 'Popularity:',
- id: 'pWTj2W',
description: 'Meta: popularity label',
+ id: 'pWTj2W',
});
case 'publication':
return intl.formatMessage({
defaultMessage: 'Published on:',
- id: 'QGi5uD',
description: 'Meta: publication date label',
+ id: 'QGi5uD',
});
case 'readingTime':
return intl.formatMessage({
defaultMessage: 'Reading time:',
- id: 'EbFvsM',
description: 'Meta: reading time label',
+ id: 'EbFvsM',
});
case 'repositories':
return intl.formatMessage({
defaultMessage: 'Repositories:',
- id: 'DssFG1',
description: 'Meta: repositories label',
+ id: 'DssFG1',
});
case 'technologies':
return intl.formatMessage({
defaultMessage: 'Technologies:',
- id: 'ADQmDF',
description: 'Meta: technologies label',
+ id: 'ADQmDF',
});
case 'thematics':
return intl.formatMessage({
defaultMessage: 'Thematics:',
- id: 'bz53Us',
description: 'Meta: thematics label',
+ id: 'bz53Us',
});
case 'topics':
return intl.formatMessage({
defaultMessage: 'Topics:',
- id: 'gJNaBD',
description: 'Meta: topics label',
+ id: 'gJNaBD',
});
case 'total':
return intl.formatMessage({
defaultMessage: 'Total:',
- id: '92zgdp',
description: 'Meta: total label',
+ id: '92zgdp',
});
case 'update':
return intl.formatMessage({
defaultMessage: 'Updated on:',
- id: 'tLC7bh',
description: 'Meta: update date label',
+ id: 'tLC7bh',
+ });
+ case 'website':
+ return intl.formatMessage({
+ defaultMessage: 'Official website:',
+ description: 'Meta: official website label',
+ id: 'GRyyfy',
});
default:
return '';
@@ -279,8 +289,8 @@ const Meta: FC<MetaProps> = ({
{
defaultMessage:
'{commentsCount, plural, =0 {No comments} one {# comment} other {# comments}}<a11y> about {title}</a11y>',
- id: '02rgLO',
description: 'Meta: comments count',
+ id: '02rgLO',
},
{
a11y: (chunks: ReactNode) => (
@@ -316,6 +326,23 @@ const Meta: FC<MetaProps> = ({
case 'publication':
case 'update':
return getDate(value as MetaDate);
+ case 'total':
+ return intl.formatMessage(
+ {
+ defaultMessage:
+ '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
+ description: 'BlogPage: posts count meta',
+ id: 'OF5cPz',
+ },
+ { postsCount: value as number }
+ );
+ case 'website':
+ const url = value as string;
+ return (
+ <Link href={url} external={true}>
+ {url}
+ </Link>
+ );
default:
return value as string | ReactNode | ReactNode[];
}
diff --git a/src/components/molecules/layout/page-footer.tsx b/src/components/molecules/layout/page-footer.tsx
index e998b1e..97e449f 100644
--- a/src/components/molecules/layout/page-footer.tsx
+++ b/src/components/molecules/layout/page-footer.tsx
@@ -19,7 +19,9 @@ export type PageFooterProps = {
*/
const PageFooter: FC<PageFooterProps> = ({ meta, ...props }) => {
return (
- <footer {...props}>{meta && <Meta data={meta} layout="column" />}</footer>
+ <footer {...props}>
+ {meta && <Meta data={meta} withSeparator={false} />}
+ </footer>
);
};
diff --git a/src/components/molecules/layout/page-header.module.scss b/src/components/molecules/layout/page-header.module.scss
index 4c7df5f..232023a 100644
--- a/src/components/molecules/layout/page-header.module.scss
+++ b/src/components/molecules/layout/page-header.module.scss
@@ -56,3 +56,9 @@
.meta {
font-size: var(--font-size-sm);
}
+
+.intro {
+ > *:last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/src/components/molecules/layout/page-header.tsx b/src/components/molecules/layout/page-header.tsx
index 9abe9af..6759c7f 100644
--- a/src/components/molecules/layout/page-header.tsx
+++ b/src/components/molecules/layout/page-header.tsx
@@ -1,5 +1,5 @@
import Heading from '@components/atoms/headings/heading';
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
import Meta, { type MetaData } from './meta';
import styles from './page-header.module.scss';
@@ -19,7 +19,7 @@ export type PageHeaderProps = {
/**
* The page title.
*/
- title: string;
+ title: ReactNode;
};
/**
@@ -35,9 +35,12 @@ const PageHeader: FC<PageHeaderProps> = ({
}) => {
const getIntro = () => {
return typeof intro === 'string' ? (
- <div dangerouslySetInnerHTML={{ __html: intro }} />
+ <div
+ className={styles.intro}
+ dangerouslySetInnerHTML={{ __html: intro }}
+ />
) : (
- <div>{intro}</div>
+ <div className={styles.intro}>{intro}</div>
);
};
diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx
index 9e0abdf..5ff4ea4 100644
--- a/src/components/organisms/forms/comment-form.tsx
+++ b/src/components/organisms/forms/comment-form.tsx
@@ -1,5 +1,5 @@
import Button from '@components/atoms/buttons/button';
-import Form from '@components/atoms/forms/form';
+import Form, { type FormProps } from '@components/atoms/forms/form';
import Heading, { type HeadingLevel } from '@components/atoms/headings/heading';
import Spinner from '@components/atoms/loaders/spinner';
import LabelledField from '@components/molecules/forms/labelled-field';
@@ -15,11 +15,7 @@ export type CommentFormData = {
website?: string;
};
-export type CommentFormProps = {
- /**
- * Set additional classnames to the form wrapper.
- */
- className?: string;
+export type CommentFormProps = Pick<FormProps, 'className'> & {
/**
* Pass a component to print a success/error message.
*/
@@ -44,12 +40,12 @@ export type CommentFormProps = {
};
const CommentForm: FC<CommentFormProps> = ({
- className = '',
Notice,
parentId,
saveComment,
title,
titleLevel = 2,
+ ...props
}) => {
const intl = useIntl();
const [name, setName] = useState<string>('');
@@ -116,9 +112,9 @@ const CommentForm: FC<CommentFormProps> = ({
return (
<Form
onSubmit={submitHandler}
- className={className}
aria-label={formAriaLabel}
aria-labelledby={formLabelledBy}
+ {...props}
>
{title && (
<Heading id={formId} level={titleLevel}>
diff --git a/src/components/organisms/widgets/sharing.stories.tsx b/src/components/organisms/widgets/sharing.stories.tsx
index 47213b6..59b86d3 100644
--- a/src/components/organisms/widgets/sharing.stories.tsx
+++ b/src/components/organisms/widgets/sharing.stories.tsx
@@ -1,5 +1,4 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { IntlProvider } from 'react-intl';
import SharingWidget from './sharing';
/**
@@ -9,6 +8,19 @@ export default {
title: 'Organisms/Widgets',
component: SharingWidget,
argTypes: {
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the sharing links list.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
data: {
description: 'The page data.',
type: {
@@ -58,13 +70,6 @@ export default {
},
},
},
- decorators: [
- (Story) => (
- <IntlProvider locale="en">
- <Story />
- </IntlProvider>
- ),
- ],
} as ComponentMeta<typeof SharingWidget>;
const Template: ComponentStory<typeof SharingWidget> = (args) => (
diff --git a/src/components/organisms/widgets/sharing.tsx b/src/components/organisms/widgets/sharing.tsx
index 85dadb0..c63f5db 100644
--- a/src/components/organisms/widgets/sharing.tsx
+++ b/src/components/organisms/widgets/sharing.tsx
@@ -23,6 +23,10 @@ export type SharingData = {
export type SharingProps = {
/**
+ * Set additional classnames to the sharing links list.
+ */
+ className?: string;
+ /**
* The page data to share.
*/
data: SharingData;
@@ -46,6 +50,7 @@ export type SharingProps = {
* Render a list of sharing links inside a widget.
*/
const Sharing: FC<SharingProps> = ({
+ className = '',
data,
media,
expanded = true,
@@ -201,7 +206,7 @@ const Sharing: FC<SharingProps> = ({
return (
<Widget expanded={expanded} level={level} title={widgetTitle} {...props}>
- <ul className={styles.list}>{getItems()}</ul>
+ <ul className={`${styles.list} ${className}`}>{getItems()}</ul>
</Widget>
);
};
diff --git a/src/components/templates/page/page-layout.module.scss b/src/components/templates/page/page-layout.module.scss
index 7602492..83e5a80 100644
--- a/src/components/templates/page/page-layout.module.scss
+++ b/src/components/templates/page/page-layout.module.scss
@@ -72,6 +72,7 @@
.footer {
grid-column: 2;
+ margin: var(--spacing-sm) 0 var(--spacing-2xs);
}
.comments {
@@ -87,4 +88,9 @@
grid-column: 2;
margin: var(--spacing-md) 0 0;
}
+
+ &__form {
+ max-width: 40ch;
+ margin: auto;
+ }
}
diff --git a/src/components/templates/page/page-layout.stories.tsx b/src/components/templates/page/page-layout.stories.tsx
index 002b951..8af5f98 100644
--- a/src/components/templates/page/page-layout.stories.tsx
+++ b/src/components/templates/page/page-layout.stories.tsx
@@ -473,7 +473,7 @@ export const Blog = Template.bind({});
Blog.args = {
breadcrumb: postsListBreadcrumb,
title: 'Blog',
- headerMeta: { total: `${posts.length} posts` },
+ headerMeta: { total: posts.length },
children: (
<>
<PostsList posts={posts} byYear={true} total={posts.length} />
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx
index bc90f4c..f3f3ea8 100644
--- a/src/components/templates/page/page-layout.tsx
+++ b/src/components/templates/page/page-layout.tsx
@@ -20,7 +20,7 @@ import TableOfContents from '@components/organisms/widgets/table-of-contents';
import { type SendCommentVars } from '@services/graphql/api';
import { sendComment } from '@services/graphql/comments';
import useIsMounted from '@utils/hooks/use-is-mounted';
-import { FC, ReactNode, useRef, useState } from 'react';
+import { FC, HTMLAttributes, ReactNode, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import Layout, { type LayoutProps } from '../layout/layout';
import styles from './page-layout.module.scss';
@@ -33,6 +33,11 @@ export type PageLayoutProps = Pick<
* True if the page accepts new comments. Default: false.
*/
allowComments?: boolean;
+ bodyAttributes?: HTMLAttributes<HTMLDivElement>;
+ /**
+ * Set additional classnames to the body wrapper.
+ */
+ bodyClassName?: string;
/**
* The breadcrumb items.
*/
@@ -83,6 +88,8 @@ export type PageLayoutProps = Pick<
const PageLayout: FC<PageLayoutProps> = ({
children,
allowComments = false,
+ bodyAttributes,
+ bodyClassName = '',
breadcrumb,
breadcrumbSchema,
comments,
@@ -91,8 +98,8 @@ const PageLayout: FC<PageLayoutProps> = ({
id,
intro,
isHome = false,
- widgets,
title,
+ widgets,
withToC = false,
}) => {
const intl = useIntl();
@@ -202,11 +209,12 @@ const PageLayout: FC<PageLayoutProps> = ({
{typeof children === 'string' ? (
<div
ref={bodyRef}
- className={styles.body}
+ className={`${styles.body} ${bodyClassName}`}
dangerouslySetInnerHTML={{ __html: children }}
+ {...bodyAttributes}
/>
) : (
- <div ref={bodyRef} className={styles.body}>
+ <div ref={bodyRef} className={`${styles.body} ${bodyClassName}`}>
{children}
</div>
)}
@@ -245,6 +253,7 @@ const PageLayout: FC<PageLayoutProps> = ({
{allowComments && (
<section className={styles.comments__section}>
<CommentForm
+ className={styles.comments__form}
saveComment={saveComment}
title={commentFormTitle}
Notice={
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 5eeabd9..995e3a9 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -1,5 +1,6 @@
import ButtonLink from '@components/atoms/buttons/button-link';
import Link from '@components/atoms/links/link';
+import ResponsiveImage from '@components/molecules/images/responsive-image';
import Sharing from '@components/organisms/widgets/sharing';
import PageLayout, {
type PageLayoutProps,
@@ -9,15 +10,20 @@ import {
getArticleBySlug,
} from '@services/graphql/articles';
import { getPostComments } from '@services/graphql/comments';
+import styles from '@styles/pages/article.module.scss';
import { type Article, type Comment } from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
+import useAddPrismClassAttr from '@utils/hooks/use-add-prism-class-attr';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
+import usePrismPlugins, { PrismPlugin } from '@utils/hooks/use-prism-plugins';
import useSettings from '@utils/hooks/use-settings';
import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
import { ParsedUrlQuery } from 'querystring';
+import { HTMLAttributes } from 'react';
+import { useIntl } from 'react-intl';
import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts';
import useSWR from 'swr';
@@ -54,16 +60,31 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {
)),
};
+ const intl = useIntl();
+ const footerMetaLabel = intl.formatMessage({
+ defaultMessage: 'Read more articles about:',
+ description: 'ArticlePage: footer topics list label',
+ id: '50xc4o',
+ });
+
const footerMeta: PageLayoutProps['footerMeta'] = {
- topics:
- topics &&
- topics.map((topic) => {
+ custom: topics && {
+ label: footerMetaLabel,
+ value: topics.map((topic) => {
return (
- <ButtonLink key={topic.id} target={`/sujet/${topic.slug}`}>
+ <ButtonLink
+ key={topic.id}
+ target={`/sujet/${topic.slug}`}
+ className={styles.btn}
+ >
+ {topic.logo && (
+ <ResponsiveImage className={styles.btn__icon} {...topic.logo} />
+ )}{' '}
{topic.name}
</ButtonLink>
);
}),
+ },
};
const { website } = useSettings();
@@ -156,6 +177,15 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {
});
};
+ const prismPlugins: PrismPlugin[] = ['command-line', 'line-numbers'];
+ const { pluginsAttribute, pluginsClassName } = usePrismPlugins(prismPlugins);
+ useAddPrismClassAttr({
+ attributes: {
+ 'data-filter-output': '#output#"',
+ },
+ classNames: pluginsClassName,
+ });
+
return (
<>
<Head>
@@ -173,6 +203,10 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {
/>
<PageLayout
allowComments={true}
+ bodyAttributes={{
+ ...(pluginsAttribute as HTMLAttributes<HTMLDivElement>),
+ }}
+ bodyClassName={styles.body}
breadcrumb={breadcrumbItems}
breadcrumbSchema={breadcrumbSchema}
comments={data && getCommentsList(data)}
@@ -185,6 +219,7 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {
widgets={[
<Sharing
key="sharing-widget"
+ className={styles.widget}
data={{ excerpt: intro, title, url: pageUrl }}
media={[
'diaspora',
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index a5ef045..b6ce221 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -114,16 +114,6 @@ const BlogPage: NextPage<BlogPageProps> = ({
'@graph': [webpageSchema, blogSchema],
};
- const postsCount = intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- id: 'OF5cPz',
- description: 'BlogPage: posts count meta',
- },
- { postsCount: totalArticles }
- );
-
/**
* Retrieve the formatted meta.
*
@@ -231,7 +221,7 @@ const BlogPage: NextPage<BlogPageProps> = ({
title={title}
breadcrumb={breadcrumbItems}
breadcrumbSchema={breadcrumbSchema}
- headerMeta={{ total: postsCount }}
+ headerMeta={{ total: totalArticles }}
widgets={[
<LinksListWidget
key="thematics-list"
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index b3dec10..7936c84 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -1,4 +1,6 @@
+import Heading from '@components/atoms/headings/heading';
import Link from '@components/atoms/links/link';
+import List from '@components/atoms/lists/list';
import ImageWidget from '@components/organisms/widgets/image-widget';
import SocialMedia from '@components/organisms/widgets/social-media';
import PageLayout, {
@@ -9,11 +11,12 @@ import styles from '@styles/pages/cv.module.scss';
import { loadTranslation } from '@utils/helpers/i18n';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
+import { NestedMDXComponents } from 'mdx/types';
import { GetStaticProps, NextPage } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
-import { ReactNode } from 'react';
+import React, { ReactNode } from 'react';
import { useIntl } from 'react-intl';
import { AboutPage, Graph, WebPage } from 'schema-dts';
@@ -141,6 +144,18 @@ const CVPage: NextPage = () => {
'@graph': [webpageSchema, cvSchema],
};
+ const components: NestedMDXComponents = {
+ a: (props) => <Link external={true} {...props} />,
+ h1: (props) => <Heading level={1} {...props} />,
+ h2: (props) => <Heading level={2} {...props} />,
+ h3: (props) => <Heading level={3} {...props} />,
+ h4: (props) => <Heading level={4} {...props} />,
+ h5: (props) => <Heading level={5} {...props} />,
+ h6: (props) => <Heading level={6} {...props} />,
+ Link: (props) => <Link {...props} />,
+ List: (props) => <List {...props} />,
+ };
+
return (
<PageLayout
breadcrumb={breadcrumbItems}
@@ -166,7 +181,7 @@ const CVPage: NextPage = () => {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <CVContent />
+ <CVContent components={components} />
</PageLayout>
);
};
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index 0a7dc60..d88a293 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -140,16 +140,6 @@ const SearchPage: NextPage<SearchPageProps> = ({
getTotalArticles(query.s as string)
);
- const postsCount = intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- id: 'LtsVOx',
- description: 'SearchPage: posts count meta',
- },
- { postsCount: totalArticles || 0 }
- );
-
/**
* Retrieve the formatted meta.
*
@@ -244,7 +234,7 @@ const SearchPage: NextPage<SearchPageProps> = ({
title={title}
breadcrumb={breadcrumbItems}
breadcrumbSchema={breadcrumbSchema}
- headerMeta={{ total: postsCount }}
+ headerMeta={{ total: totalArticles }}
widgets={[
<LinksListWidget
key="thematics-list"
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 447d969..348fe05 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -1,4 +1,5 @@
import Heading from '@components/atoms/headings/heading';
+import ResponsiveImage from '@components/molecules/images/responsive-image';
import PostsList, { type Post } from '@components/organisms/layout/posts-list';
import LinksListWidget from '@components/organisms/widgets/links-list-widget';
import PageLayout, {
@@ -10,6 +11,7 @@ import {
getTopicsPreview,
getTotalTopics,
} from '@services/graphql/topics';
+import styles from '@styles/pages/topic.module.scss';
import { type Article, type PageLink, type Topic } from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
import {
@@ -35,7 +37,14 @@ export type TopicPageProps = {
const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {
const { content, intro, meta, slug, title } = currentTopic;
- const { articles, dates, seo, thematics } = meta;
+ const {
+ articles,
+ cover,
+ dates,
+ seo,
+ thematics,
+ website: officialWebsite,
+ } = meta;
const intl = useIntl();
const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({
title,
@@ -45,6 +54,8 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {
const headerMeta: PageLayoutProps['headerMeta'] = {
publication: { date: dates.publication },
update: dates.update ? { date: dates.update } : undefined,
+ website: officialWebsite,
+ total: articles ? articles.length : undefined,
};
const { website } = useSettings();
@@ -98,10 +109,10 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {
...remainingData
} = article;
- const { cover, ...remainingMeta } = articleMeta;
+ const { cover: articleCover, ...remainingMeta } = articleMeta;
return {
- cover,
+ cover: articleCover,
excerpt: articleIntro,
meta: getPostMeta(remainingMeta),
url: `/article/${articleSlug}`,
@@ -122,6 +133,15 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {
id: '/sRqPT',
});
+ const getPageHeading = () => {
+ return (
+ <>
+ {cover && <ResponsiveImage className={styles.logo} {...cover} />}
+ {title}
+ </>
+ );
+ };
+
return (
<>
<Head>
@@ -140,7 +160,7 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {
<PageLayout
breadcrumb={breadcrumbItems}
breadcrumbSchema={breadcrumbSchema}
- title={title}
+ title={getPageHeading()}
intro={intro}
headerMeta={headerMeta}
widgets={
@@ -206,14 +226,15 @@ export const getStaticProps: GetStaticProps<TopicPageProps> = async ({
const allTopics = allTopicsEdges.edges.map((edge) =>
getPageLinkFromRawData(edge.node)
);
+ const topicsLinks = allTopics.filter(
+ (topic) => topic.slug !== (params!.slug as TopicParams['slug'])
+ );
const translation = await loadTranslation(locale);
return {
props: {
currentTopic: JSON.parse(JSON.stringify(currentTopic)),
- topics: allTopics.filter(
- (topic) => topic.slug !== (params!.slug as TopicParams['slug'])
- ),
+ topics: JSON.parse(JSON.stringify(topicsLinks)),
translation,
},
};
diff --git a/src/services/graphql/articles.query.ts b/src/services/graphql/articles.query.ts
index d65bc9e..e02ca8e 100644
--- a/src/services/graphql/articles.query.ts
+++ b/src/services/graphql/articles.query.ts
@@ -14,6 +14,17 @@ export const articleBySlugQuery = `query PostBy($slug: ID!) {
postsInTopic {
... on Topic {
databaseId
+ featuredImage {
+ node {
+ altText
+ mediaDetails {
+ height
+ width
+ }
+ sourceUrl
+ title
+ }
+ }
slug
title
}
diff --git a/src/services/graphql/topics.query.ts b/src/services/graphql/topics.query.ts
index 19be5d7..4574256 100644
--- a/src/services/graphql/topics.query.ts
+++ b/src/services/graphql/topics.query.ts
@@ -94,6 +94,17 @@ export const topicsListQuery = `query TopicsList($after: String = "", $first: In
cursor
node {
databaseId
+ featuredImage {
+ node {
+ altText
+ mediaDetails {
+ height
+ width
+ }
+ sourceUrl
+ title
+ }
+ }
slug
title
}
diff --git a/src/styles/base/_typography.scss b/src/styles/base/_typography.scss
index 7b7a695..2c3c8cc 100644
--- a/src/styles/base/_typography.scss
+++ b/src/styles/base/_typography.scss
@@ -120,21 +120,15 @@ dl {
& & {
margin: var(--spacing-2xs) 0 0;
}
-}
-
-dt {
- flex: 0 0 max-content;
- font-weight: 600;
-}
-dd {
- flex: 0 0 auto;
- margin: 0;
+ ::marker {
+ color: var(--color-primary-dark);
+ }
}
a {
background: linear-gradient(to top, var(--color-primary) 50%, transparent 50%)
- 0 0 / 100% 200% no-repeat;
+ 0 0 / 100% 201% no-repeat;
color: var(--color-primary);
text-decoration-thickness: 0.15em;
text-underline-offset: 20%;
@@ -199,13 +193,3 @@ pre {
word-break: normal;
word-wrap: normal;
}
-
-figure {
- margin: var(--spacing-md) 0;
-}
-
-figcaption {
- margin-top: var(--spacing-xs);
- font-size: var(--font-size-sm);
- text-align: center;
-}
diff --git a/src/styles/components/_wp-blocks.scss b/src/styles/components/_wp-blocks.scss
deleted file mode 100644
index efd6db5..0000000
--- a/src/styles/components/_wp-blocks.scss
+++ /dev/null
@@ -1,166 +0,0 @@
-@use "@styles/abstracts/functions" as fun;
-@use "@styles/abstracts/mixins" as mix;
-@use "@styles/abstracts/placeholders";
-
-.wp-block-quote {
- margin: var(--spacing-sm) 0;
- padding: var(--spacing-sm);
- position: relative;
- border: fun.convert-px(1) solid var(--color-primary-lighter);
- border-left: fun.convert-px(5) solid var(--color-primary-lighter);
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-light),
- fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
- var(--color-shadow-light);
- font-style: italic;
-
- > *:last-child {
- margin: 0;
- }
-
- cite {
- font-size: var(--font-size-sm);
- font-style: normal;
- font-weight: 600;
- }
-}
-
-.wp-block-code,
-.wp-block-preformatted {
- margin: 0 auto var(--spacing-md);
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--color-bg-secondary);
- border: fun.convert-px(1) solid var(--color-border-light);
- color: var(--color-fg);
-}
-
-.wp-block-columns {
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- gap: var(--spacing-md);
- margin: var(--spacing-md) 0;
-
- @include mix.media("screen") {
- @include mix.dimensions("sm") {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
- }
-
- &.are-vertically-aligned-center {
- align-items: center;
- }
-}
-
-.wp-block-column {
- > *:first-child {
- margin-top: 0;
- }
-
- > *:last-child {
- margin-bottom: 0;
- }
-}
-
-.wp-block-gallery {
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- gap: var(--spacing-sm);
-
- .blocks-gallery-grid {
- @extend %reset-list;
-
- grid-column: 1 / -1;
- grid-row: 1 / -1;
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- gap: var(--spacing-sm);
- }
-
- .blocks-gallery-item {
- figure {
- margin: 0;
- }
-
- a {
- display: block;
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-light),
- fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
- var(--color-shadow-light);
-
- &:hover,
- &:focus {
- transform: scale(1.05);
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(3) fun.convert-px(3) fun.convert-px(2) 0
- var(--color-shadow-light),
- fun.convert-px(5) fun.convert-px(5) fun.convert-px(8) 0
- var(--color-shadow-light);
- }
-
- &:focus {
- outline: solid var(--color-primary-light);
- }
-
- &:active {
- transform: scale(0.95);
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-light),
- 0 0 0 0 var(--color-shadow-light);
- outline: none;
- }
- }
- }
-
- &.aligncenter {
- .blocks-gallery-grid {
- align-items: center;
- }
- }
-
- @for $i from 0 to 6 {
- &.columns-#{$i} {
- @include mix.media("screen") {
- @include mix.dimensions("xs") {
- grid-template-columns: repeat(2, minmax(0, 1fr));
-
- .blocks-gallery-grid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
- }
-
- @include mix.dimensions("sm") {
- grid-template-columns: repeat(#{$i}, minmax(0, 1fr));
-
- .blocks-gallery-grid {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
- }
- }
- }
- }
-}
-
-.wp-block-image {
- img {
- display: block;
- margin: auto;
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-light),
- fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
- var(--color-shadow-light);
- text-align: center;
- }
-}
-
-.wp-block-video {
- box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-light),
- fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
- var(--color-shadow-light);
-}
diff --git a/src/styles/globals.scss b/src/styles/globals.scss
index f9a1281..8ece909 100644
--- a/src/styles/globals.scss
+++ b/src/styles/globals.scss
@@ -6,7 +6,6 @@
* Import each files separately to define vendors styles order.
*/
@use "modern-normalize";
-@use "vendors/prism";
/**
* 2.0. Base
@@ -22,14 +21,7 @@
@use "base/typography";
/**
- * 3.0. Components
- *
- * Define styles for external components (like WordPress blocks).
- */
-@use "components/wp-blocks";
-
-/**
- * 4.0. Themes
+ * 3.0. Themes
*
* Define themes specific styles.
*/
diff --git a/src/styles/pages/article.module.scss b/src/styles/pages/article.module.scss
new file mode 100644
index 0000000..a42c633
--- /dev/null
+++ b/src/styles/pages/article.module.scss
@@ -0,0 +1,36 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+@use "@styles/abstracts/variables" as var;
+@use "partials/article-links";
+@use "partials/article-lists";
+@use "partials/article-media";
+@use "partials/article-prism";
+@use "partials/article-wp-blocks";
+
+.btn {
+ margin-right: var(--spacing-2xs);
+ padding: var(--spacing-2xs) var(--spacing-xs);
+
+ &__icon {
+ max-width: fun.convert-px(22);
+ margin-right: var(--spacing-2xs);
+ }
+}
+
+.body {
+ :global {
+ @include article-links.styles;
+ @include article-lists.styles;
+ @include article-media.styles;
+ @include article-prism.styles;
+ @include article-wp-blocks.styles;
+ }
+}
+
+.widget {
+ @include mix.media("screen") {
+ @include mix.dimensions("md") {
+ width: min-content;
+ }
+ }
+}
diff --git a/src/styles/pages/partials/_article-headings.scss b/src/styles/pages/partials/_article-headings.scss
new file mode 100644
index 0000000..c0c3519
--- /dev/null
+++ b/src/styles/pages/partials/_article-headings.scss
@@ -0,0 +1,57 @@
+@use "@styles/abstracts/functions" as fun;
+
+@mixin styles {
+ h1 {
+ font-size: var(--font-size-3xl);
+ font-weight: 500;
+ }
+
+ h2 {
+ padding-bottom: fun.convert-px(3);
+ background: linear-gradient(
+ to top,
+ var(--color-primary-dark) 0.3rem,
+ transparent 0.3rem
+ )
+ 0 0 / 3rem 100% no-repeat;
+ font-size: var(--font-size-2xl);
+ font-weight: 500;
+ text-shadow: fun.convert-px(1) fun.convert-px(1) 0 var(--color-shadow-light);
+ }
+
+ h3 {
+ font-size: var(--font-size-xl);
+ font-weight: 500;
+ }
+
+ h4 {
+ font-size: var(--font-size-lg);
+ font-weight: 500;
+ }
+
+ h5 {
+ font-size: var(--font-size-md);
+ font-weight: 600;
+ }
+
+ h6 {
+ font-size: var(--font-size-md);
+ font-weight: 500;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ color: var(--color-primary-dark);
+ font-family: var(--font-family-secondary);
+ letter-spacing: 0.01ex;
+ margin: 0 0 var(--spacing-sm);
+
+ & + & {
+ margin-top: var(--spacing-md);
+ }
+ }
+}
diff --git a/src/styles/pages/partials/_article-links.scss b/src/styles/pages/partials/_article-links.scss
new file mode 100644
index 0000000..df86dcf
--- /dev/null
+++ b/src/styles/pages/partials/_article-links.scss
@@ -0,0 +1,104 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/variables" as var;
+
+@mixin styles {
+ a {
+ &[hreflang] {
+ &::after {
+ display: inline-block;
+
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]";
+ font-size: var(--font-size-sm);
+ }
+ }
+ }
+
+ a.download {
+ &::after {
+ display: inline-block;
+
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
+ }
+
+ &:focus:not(:active)::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
+ }
+
+ &[hreflang] {
+ &::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
+ }
+ }
+ }
+
+ a.external {
+ &::after {
+ display: inline-block;
+
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+
+ &:focus:not(:active)::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+
+ &[hreflang] {
+ &::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+
+ &:focus:not(:active)::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+ }
+ }
+
+ a.external.download {
+ &::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+
+ &[hreflang] {
+ &::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+
+ &:focus:not(:active)::after {
+ /* Prettier is removing spacing between content parts. */
+
+ /* prettier-ignore */
+ content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+ }
+ }
+ }
+}
diff --git a/src/styles/pages/partials/_article-lists.scss b/src/styles/pages/partials/_article-lists.scss
new file mode 100644
index 0000000..c0084b0
--- /dev/null
+++ b/src/styles/pages/partials/_article-lists.scss
@@ -0,0 +1,65 @@
+@mixin styles {
+ ol {
+ padding: 0;
+ list-style-type: none;
+ counter-reset: li;
+
+ > li {
+ display: table;
+ counter-increment: li;
+
+ &::before {
+ content: counters(li, ".") ". ";
+ display: table-cell;
+ padding-right: var(--spacing-2xs);
+ color: var(--color-secondary);
+ }
+ }
+
+ li ol > li::before {
+ content: counters(li, ".") ". ";
+ }
+ }
+
+ ul,
+ ol {
+ li:not(:last-child) {
+ margin-bottom: var(--spacing-2xs);
+ }
+
+ ::marker {
+ color: var(--color-primary-dark);
+ }
+ }
+
+ ul {
+ padding-left: var(--spacing-sm);
+ }
+
+ dl {
+ display: flex;
+ flex-flow: row wrap;
+ gap: var(--spacing-2xs);
+ width: fit-content;
+ }
+
+ ul,
+ ol,
+ dl {
+ margin: var(--spacing-sm) 0;
+
+ & & {
+ margin: var(--spacing-2xs) 0 0;
+ }
+ }
+
+ dt {
+ color: var(--color-fg-light);
+ font-weight: 600;
+ }
+
+ dd {
+ margin: 0;
+ word-break: break-all;
+ }
+}
diff --git a/src/styles/pages/partials/_article-media.scss b/src/styles/pages/partials/_article-media.scss
new file mode 100644
index 0000000..8359881
--- /dev/null
+++ b/src/styles/pages/partials/_article-media.scss
@@ -0,0 +1,11 @@
+@mixin styles {
+ figure {
+ margin: var(--spacing-md) 0;
+ }
+
+ figcaption {
+ margin-top: var(--spacing-xs);
+ font-size: var(--font-size-sm);
+ text-align: center;
+ }
+}
diff --git a/src/styles/pages/partials/_article-prism.scss b/src/styles/pages/partials/_article-prism.scss
new file mode 100644
index 0000000..025e0c0
--- /dev/null
+++ b/src/styles/pages/partials/_article-prism.scss
@@ -0,0 +1,301 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+@mixin styles {
+ .code-toolbar {
+ --gutter-size: clamp(#{fun.convert-px(75)}, 20vw, #{fun.convert-px(90)});
+ --toolbar-height: #{fun.convert-px(90)};
+
+ position: relative;
+ margin-top: calc(var(--toolbar-height) + var(--spacing-md));
+
+ @include mix.media("screen") {
+ @include mix.dimensions("2xs") {
+ --toolbar-height: #{fun.convert-px(60)};
+ }
+ }
+
+ .toolbar {
+ display: grid;
+ grid-template-columns: max-content minmax(0, 1fr);
+ justify-items: end;
+ width: 100%;
+ height: var(--toolbar-height);
+ position: absolute;
+ top: calc(var(--toolbar-height) * -1);
+ left: 0;
+ right: 0;
+ background: var(--color-bg-tertiary);
+ border: fun.convert-px(1) solid var(--color-border);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("2xs") {
+ display: flex;
+ flex-flow: row wrap;
+ }
+ }
+ }
+
+ .toolbar-item {
+ display: flex;
+ align-items: center;
+ }
+
+ .toolbar-item:nth-child(1) {
+ grid-column: 1;
+ grid-row: 1 / 3;
+ margin-right: auto;
+ padding: 0 var(--spacing-sm);
+ background: var(--color-bg-code);
+ border-right: fun.convert-px(1) solid var(--color-border);
+ color: var(--color-primary-darker);
+ font-size: var(--font-size-sm);
+ font-weight: 600;
+ }
+
+ .toolbar-item:nth-child(2) {
+ grid-column: 2;
+ grid-row: 1;
+ margin: 0 var(--spacing-2xs);
+ }
+
+ .toolbar-item:nth-child(3) {
+ grid-column: 2;
+ grid-row: 2;
+ margin: 0 var(--spacing-2xs);
+ }
+ }
+
+ pre[class*="language-"] {
+ max-height: max(30vw, fun.convert-px(300));
+ margin: var(--spacing-md) 0;
+ padding: 0;
+ position: relative;
+ background: var(--color-bg-secondary);
+ color: var(--color-fg);
+ border: fun.convert-px(1) solid var(--color-border);
+
+ > code {
+ display: block;
+ padding: var(--spacing-xs) 0 var(--spacing-xs)
+ calc(var(--gutter-size) + var(--spacing-xs));
+ }
+
+ .line-numbers-rows,
+ .command-line-prompt {
+ width: var(--gutter-size);
+ min-height: 100%;
+ padding: var(--spacing-xs) var(--spacing-2xs);
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ user-select: none;
+ background: var(--color-bg);
+ border-right: fun.convert-px(1) solid var(--color-border);
+ }
+
+ .token {
+ &.comment,
+ &.doc-comment {
+ color: var(--color-fg-light);
+ }
+
+ &.punctuation {
+ color: var(--color-fg);
+ }
+
+ &.attr-name,
+ &.hexcode,
+ &.inserted,
+ &.string {
+ color: var(--color-token-green);
+ }
+
+ &.class,
+ &.coord,
+ &.id,
+ &.function {
+ color: var(--color-token-purple);
+ }
+
+ &.builtin,
+ &.builtin.class-name,
+ &.property-access,
+ &.regex,
+ &.scope {
+ color: var(--color-token-magenta);
+ }
+
+ &.class-name,
+ &.constant,
+ &.global,
+ &.interpolation,
+ &.key,
+ &.package,
+ &.this,
+ &.title,
+ &.variable {
+ color: var(--color-token-blue);
+ }
+
+ &.combinator,
+ &.keyword,
+ &.operator,
+ &.pseudo-class,
+ &.pseudo-element,
+ &.rule,
+ &.selector,
+ &.unit {
+ color: var(--color-token-orange);
+ }
+
+ &.attr-value,
+ &.boolean,
+ &.number {
+ color: var(--color-token-yellow);
+ }
+
+ &.delimiter,
+ &.doctype,
+ &.parameter,
+ &.parent,
+ &.property,
+ &.shebang,
+ &.tag {
+ color: var(--color-token-cyan);
+ }
+
+ &.deleted {
+ color: var(--color-token-red);
+ }
+
+ &.punctuation.brace-hover,
+ &.punctuation.brace-selected {
+ background: var(--color-bg);
+ outline: solid fun.convert-px(1) var(--color-primary-light);
+ }
+ }
+
+ span.inline-color-wrapper {
+ background: url(fun.encode-svg(
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="gray" d="M0 0h2v2H0z"/><path fill="white" d="M0 0h1v1H0zM1 1h1v1H1z"/></svg>'
+ ));
+
+ /* Prevent glitches where 1px from the repeating pattern could be seen. */
+ background-position: center;
+ background-size: 110%;
+
+ display: inline-block;
+ height: 1.1ch;
+ width: 1.1ch;
+ margin: 0 0.5ch 0 0;
+ border: fun.convert-px(1) solid var(--color-bg);
+ outline: fun.convert-px(1) solid var(--color-border-dark);
+ overflow: hidden;
+ }
+
+ span.inline-color {
+ display: block;
+
+ /* To prevent visual glitches again */
+ height: 120%;
+ width: 120%;
+ }
+ }
+
+ pre.line-numbers {
+ counter-reset: lineNumber;
+
+ .line-numbers-rows {
+ > span {
+ counter-increment: lineNumber;
+
+ &::before {
+ display: block;
+ padding: 0 var(--spacing-xs);
+ content: counter(lineNumber);
+ color: var(--color-primary-darker);
+ text-align: right;
+ line-height: var(--line-height);
+ }
+ }
+ }
+ }
+
+ pre.command-line {
+ --gutter-size: clamp(#{fun.convert-px(195)}, 48vw, #{fun.convert-px(235)});
+
+ ~ .toolbar {
+ --gutter-size: clamp(
+ #{fun.convert-px(195)},
+ 48vw,
+ #{fun.convert-px(235)}
+ );
+ }
+
+ .command-line-prompt {
+ > span {
+ &::before {
+ display: block;
+ content: "";
+ }
+
+ &[data-user]::before {
+ content: "[" attr(data-user) "@" attr(data-host) "] $";
+ }
+
+ &[data-user="root"]::before {
+ content: "[" attr(data-user) "@" attr(data-host) "] #";
+ }
+
+ &[data-prompt]::before {
+ content: attr(data-prompt);
+ }
+ }
+ }
+ }
+
+ .copy-to-clipboard-button,
+ .prism-color-scheme-button {
+ display: block;
+ padding: fun.convert-px(3) var(--spacing-xs);
+ background: var(--color-bg);
+ border: 0.4ex solid var(--color-primary);
+ border-radius: fun.convert-px(30);
+ box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
+ var(--color-shadow),
+ fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2)
+ var(--color-shadow),
+ fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4)
+ var(--color-shadow);
+ color: var(--color-primary);
+ font-size: var(--font-size-sm);
+ font-weight: 600;
+ transition: all 0.35s ease-in-out 0s;
+
+ &:hover,
+ &:focus {
+ transform: translateX(#{fun.convert-px(-2)})
+ translateY(#{fun.convert-px(-2)});
+ box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
+ var(--color-shadow-light),
+ fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2)
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4)
+ var(--color-shadow-light),
+ fun.convert-px(4) fun.convert-px(7) fun.convert-px(8) fun.convert-px(-3)
+ var(--color-shadow-light);
+ }
+
+ &:focus {
+ text-decoration: underline var(--color-primary) fun.convert-px(3);
+ }
+
+ &:active {
+ text-decoration: none;
+ transform: translateY(#{fun.convert-px(2)});
+ box-shadow: 0 0 0 0 var(--color-shadow);
+ }
+ }
+}
diff --git a/src/styles/pages/partials/_article-wp-blocks.scss b/src/styles/pages/partials/_article-wp-blocks.scss
new file mode 100644
index 0000000..d4fed5a
--- /dev/null
+++ b/src/styles/pages/partials/_article-wp-blocks.scss
@@ -0,0 +1,168 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+@use "@styles/abstracts/placeholders";
+
+@mixin styles {
+ .wp-block-quote {
+ margin: var(--spacing-sm) 0;
+ padding: var(--spacing-sm);
+ position: relative;
+ border: fun.convert-px(1) solid var(--color-primary-lighter);
+ border-left: fun.convert-px(5) solid var(--color-primary-lighter);
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
+ var(--color-shadow-light);
+ font-style: italic;
+
+ > *:last-child {
+ margin: 0;
+ }
+
+ cite {
+ font-size: var(--font-size-sm);
+ font-style: normal;
+ font-weight: 600;
+ }
+ }
+
+ .wp-block-code,
+ .wp-block-preformatted {
+ margin: 0 auto var(--spacing-md);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ background: var(--color-bg-secondary);
+ border: fun.convert-px(1) solid var(--color-border-light);
+ color: var(--color-fg);
+ }
+
+ .wp-block-columns {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--spacing-md);
+ margin: var(--spacing-md) 0;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ &.are-vertically-aligned-center {
+ align-items: center;
+ }
+ }
+
+ .wp-block-column {
+ > *:first-child {
+ margin-top: 0;
+ }
+
+ > *:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .wp-block-gallery {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--spacing-sm);
+
+ .blocks-gallery-grid {
+ @extend %reset-list;
+
+ grid-column: 1 / -1;
+ grid-row: 1 / -1;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--spacing-sm);
+ }
+
+ .blocks-gallery-item {
+ figure {
+ margin: 0;
+ }
+
+ a {
+ display: block;
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
+ var(--color-shadow-light);
+
+ &:hover,
+ &:focus {
+ transform: scale(1.05);
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ fun.convert-px(5) fun.convert-px(5) fun.convert-px(8) 0
+ var(--color-shadow-light);
+ }
+
+ &:focus {
+ outline: solid var(--color-primary-light);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ 0 0 0 0 var(--color-shadow-light);
+ outline: none;
+ }
+ }
+ }
+
+ &.aligncenter {
+ .blocks-gallery-grid {
+ align-items: center;
+ }
+ }
+
+ @for $i from 0 to 6 {
+ &.columns-#{$i} {
+ @include mix.media("screen") {
+ @include mix.dimensions("xs") {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+
+ .blocks-gallery-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ @include mix.dimensions("sm") {
+ grid-template-columns: repeat(#{$i}, minmax(0, 1fr));
+
+ .blocks-gallery-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .wp-block-image {
+ img {
+ display: block;
+ margin: auto;
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
+ var(--color-shadow-light);
+ text-align: center;
+ }
+ }
+
+ .wp-block-video {
+ box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow),
+ fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0
+ var(--color-shadow-light);
+ }
+}
diff --git a/src/styles/pages/topic.module.scss b/src/styles/pages/topic.module.scss
new file mode 100644
index 0000000..badb694
--- /dev/null
+++ b/src/styles/pages/topic.module.scss
@@ -0,0 +1,6 @@
+@use "@styles/abstracts/functions" as fun;
+
+.logo {
+ max-width: fun.convert-px(50);
+ margin-right: var(--spacing-xs);
+}
diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts
index f354118..a3b9889 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -81,6 +81,7 @@ export type Page<T extends PageKind> = {
export type PageLink = {
id: number;
+ logo?: Image;
name: string;
slug: string;
};
diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts
index 7e12e7f..ba6d596 100644
--- a/src/ts/types/raw-data.ts
+++ b/src/ts/types/raw-data.ts
@@ -89,14 +89,17 @@ export type RawThematic = RawPage & {
export type RawThematicPreview = Pick<
RawThematic,
- 'databaseId' | 'slug' | 'title'
+ 'databaseId' | 'featuredImage' | 'slug' | 'title'
>;
export type RawTopic = RawPage & {
acfTopics: ACFTopics;
};
-export type RawTopicPreview = Pick<RawTopic, 'databaseId' | 'slug' | 'title'>;
+export type RawTopicPreview = Pick<
+ RawTopic,
+ 'databaseId' | 'featuredImage' | 'slug' | 'title'
+>;
export type TotalItems = {
pageInfo: Pick<PageInfo, 'total'>;
diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts
index 93582f0..62337db 100644
--- a/src/utils/helpers/pages.ts
+++ b/src/utils/helpers/pages.ts
@@ -5,12 +5,14 @@ import {
type RawThematicPreview,
type RawTopicPreview,
} from '@ts/types/raw-data';
+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).
@@ -18,10 +20,11 @@ import {
export const getPageLinkFromRawData = (
data: RawThematicPreview | RawTopicPreview
): PageLink => {
- const { databaseId, slug, title } = data;
+ const { databaseId, featuredImage, slug, title } = data;
return {
id: databaseId,
+ logo: featuredImage ? getImageFromRawData(featuredImage?.node) : undefined,
name: title,
slug,
};
diff --git a/src/utils/hooks/use-add-prism-class-attr.tsx b/src/utils/hooks/use-add-prism-class-attr.tsx
new file mode 100644
index 0000000..7d33cc2
--- /dev/null
+++ b/src/utils/hooks/use-add-prism-class-attr.tsx
@@ -0,0 +1,60 @@
+import { useCallback, useEffect, useState } from 'react';
+
+export type AttributesMap = {
+ [key: string]: string;
+};
+
+export type useAddPrismClassAttrProps = {
+ attributes?: AttributesMap;
+ classNames?: string;
+};
+
+/**
+ * Add classnames and/or attributes to pre elements.
+ *
+ * @param props - An object of attributes and classnames.
+ */
+const useAddPrismClassAttr = ({
+ attributes,
+ classNames,
+}: useAddPrismClassAttrProps) => {
+ const [elements, setElements] = useState<HTMLPreElement[]>([]);
+
+ useEffect(() => {
+ const targetElements = document.querySelectorAll('pre');
+ setElements(Array.from(targetElements));
+ }, []);
+
+ const setClassNameAndAttributes = useCallback(
+ (array: HTMLElement[]) => {
+ array.forEach((el) => {
+ if (classNames) {
+ const classNamesArray = classNames.split(' ');
+ const isCommandLine = el.classList.contains('command-line');
+ const removedClassName = isCommandLine
+ ? 'line-numbers'
+ : 'command-line';
+ const filteredClassNames = classNamesArray.filter(
+ (className) => className !== removedClassName
+ );
+ filteredClassNames.forEach((className) =>
+ el.classList.add(className)
+ );
+ }
+
+ if (attributes) {
+ for (const [key, value] of Object.entries(attributes)) {
+ el.setAttribute(key, value);
+ }
+ }
+ });
+ },
+ [attributes, classNames]
+ );
+
+ useEffect(() => {
+ if (elements.length > 0) setClassNameAndAttributes(elements);
+ }, [elements, setClassNameAndAttributes]);
+};
+
+export default useAddPrismClassAttr;