Allow to use rich text in description (#177)

This commit is contained in:
Konrad Szwarc 2023-01-25 22:32:21 +01:00 committed by GitHub
parent 8d0cd278eb
commit c5c6872cc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 132 additions and 85 deletions

32
package-lock.json generated
View file

@ -18,6 +18,7 @@
"@astrojs/react": "1.2.2", "@astrojs/react": "1.2.2",
"@astrojs/tailwind": "2.1.3", "@astrojs/tailwind": "2.1.3",
"@percy/cli": "1.17.0", "@percy/cli": "1.17.0",
"@types/marked": "4.0.8",
"astro": "1.9.2", "astro": "1.9.2",
"astro-compress": "1.1.28", "astro-compress": "1.1.28",
"concurrently": "7.6.0", "concurrently": "7.6.0",
@ -25,6 +26,7 @@
"iconify-icon-names": "1.1.0", "iconify-icon-names": "1.1.0",
"immer": "9.0.18", "immer": "9.0.18",
"locales-ts": "1.0.0", "locales-ts": "1.0.0",
"marked": "4.2.12",
"postcss": "8.4.21", "postcss": "8.4.21",
"prettier": "2.8.3", "prettier": "2.8.3",
"prettier-plugin-astro": "0.7.2", "prettier-plugin-astro": "0.7.2",
@ -1217,6 +1219,12 @@
"integrity": "sha512-hw3bhStrg5e3FQT8qZKCJTrzt/UbEaunU1xRWJ+aNOTmeBMvE3S4Ml2HiiNnZgL8izu0LFVkHUoPFXL1s5QNpQ==", "integrity": "sha512-hw3bhStrg5e3FQT8qZKCJTrzt/UbEaunU1xRWJ+aNOTmeBMvE3S4Ml2HiiNnZgL8izu0LFVkHUoPFXL1s5QNpQ==",
"dev": true "dev": true
}, },
"node_modules/@types/marked": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz",
"integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==",
"dev": true
},
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@ -4866,6 +4874,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/marked": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/matcher": { "node_modules/matcher": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@ -10406,6 +10426,12 @@
"integrity": "sha512-hw3bhStrg5e3FQT8qZKCJTrzt/UbEaunU1xRWJ+aNOTmeBMvE3S4Ml2HiiNnZgL8izu0LFVkHUoPFXL1s5QNpQ==", "integrity": "sha512-hw3bhStrg5e3FQT8qZKCJTrzt/UbEaunU1xRWJ+aNOTmeBMvE3S4Ml2HiiNnZgL8izu0LFVkHUoPFXL1s5QNpQ==",
"dev": true "dev": true
}, },
"@types/marked": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz",
"integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==",
"dev": true
},
"@types/mdast": { "@types/mdast": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@ -13008,6 +13034,12 @@
"integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==",
"dev": true "dev": true
}, },
"marked": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
"dev": true
},
"matcher": { "matcher": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",

View file

@ -31,6 +31,7 @@
"@astrojs/react": "1.2.2", "@astrojs/react": "1.2.2",
"@astrojs/tailwind": "2.1.3", "@astrojs/tailwind": "2.1.3",
"@percy/cli": "1.17.0", "@percy/cli": "1.17.0",
"@types/marked": "4.0.8",
"astro": "1.9.2", "astro": "1.9.2",
"astro-compress": "1.1.28", "astro-compress": "1.1.28",
"concurrently": "7.6.0", "concurrently": "7.6.0",
@ -38,6 +39,7 @@
"iconify-icon-names": "1.1.0", "iconify-icon-names": "1.1.0",
"immer": "9.0.18", "immer": "9.0.18",
"locales-ts": "1.0.0", "locales-ts": "1.0.0",
"marked": "4.2.12",
"postcss": "8.4.21", "postcss": "8.4.21",
"prettier": "2.8.3", "prettier": "2.8.3",
"prettier-plugin-astro": "0.7.2", "prettier-plugin-astro": "0.7.2",

Binary file not shown.

View file

@ -0,0 +1,40 @@
---
import { marked } from 'marked';
export interface Props {
content: string;
classList: (string | undefined)[];
}
const minIndent = (str: string) => {
const match = str.match(/^[\t ]*(?=\S)/gm);
if (!match) return 0;
return match.reduce((r, a) => Math.min(r, a.length), Number.POSITIVE_INFINITY);
};
const stripIndent = (str: string) => {
const indent = minIndent(str);
if (indent === 0) return str;
const regex = new RegExp(`^[ \\t]{${indent}}`, 'gm');
return str.replace(regex, '');
};
const { content, classList } = Astro.props;
---
<div set:html={marked.parse(stripIndent(content), { breaks: true })} class:list={['description', ...classList]} />
<style is:global>
.description ul {
@apply list-disc pl-5;
}
.description a {
@apply underline opacity-90;
}
</style>

View file

@ -27,14 +27,14 @@ const experienceSectionData = {
role: 'Senior front-end developer', role: 'Senior front-end developer',
company: 'Google', company: 'Google',
dates: [new Date('2020-02'), null], dates: [new Date('2020-02'), null],
description: [ description: `
'In tristique vulputate augue vel egestas.', - In tristique vulputate augue vel egestas.
'Quisque ac imperdiet tortor, at lacinia ex.', - Quisque ac imperdiet tortor, at lacinia ex.
'Duis vel ex hendrerit, commodo odio sed, aliquam enim.', - Duis vel ex hendrerit, commodo odio sed, aliquam enim.
'Ut arcu nulla, tincidunt eget arcu eget, molestie vulputate nisi.', - Ut arcu nulla, tincidunt eget arcu eget, molestie vulputate nisi.
'Nunc malesuada leo et est iaculis facilisis.', - Nunc malesuada leo et est iaculis facilisis.
'Fusce eu urna ut magna malesuada fringilla.', - Fusce eu urna ut magna malesuada fringilla.
], `,
tagsList: { tagsList: {
title: 'Technologies', title: 'Technologies',
tags: [react(), nextJs(), typescript(), nx(), firebase()], tags: [react(), nextJs(), typescript(), nx(), firebase()],
@ -45,12 +45,12 @@ const experienceSectionData = {
role: 'React.js developer', role: 'React.js developer',
company: 'Facebook', company: 'Facebook',
dates: [new Date('2019-04'), new Date('2020-02')], dates: [new Date('2019-04'), new Date('2020-02')],
description: [ description: `
'Aenean eget ultricies felis. Pellentesque dictum massa ut tellus eleifend, sed posuere massa mattis.', - Aenean eget ultricies felis. Pellentesque dictum massa ut tellus eleifend, sed posuere massa mattis.
'Ut posuere massa lacus, eleifend molestie tortor auctor vel.', - Ut posuere massa lacus, eleifend molestie tortor auctor vel.
'Sed sed sollicitudin eros, id ultricies mi. Aliquam sodales elit vel ante tempor, non vehicula nibh facilisis.', - Sed sed sollicitudin eros, id ultricies mi. Aliquam sodales elit vel ante tempor, non vehicula nibh facilisis.
'Cras feugiat ultricies maximus. Aliquam tristique ex odio, ac semper urna accumsan a.', - Cras feugiat ultricies maximus. Aliquam tristique ex odio, ac semper urna accumsan a.
], `,
tagsList: { tagsList: {
title: 'Technologies', title: 'Technologies',
tags: [react(), reactQuery(), chakraUi(), eslint()], tags: [react(), reactQuery(), chakraUi(), eslint()],
@ -61,12 +61,14 @@ const experienceSectionData = {
role: 'Junior front-end developer', role: 'Junior front-end developer',
company: 'GitLab', company: 'GitLab',
dates: [new Date('2016-09'), new Date('2019-04')], dates: [new Date('2016-09'), new Date('2019-04')],
description: [ description: `
'Nulla volutpat justo ante, rhoncus posuere massa egestas in.', Nulla volutpat justo ante, rhoncus posuere massa egestas in:
'Quisque pellentesque, dolor nec sollicitudin iaculis, sem velit consequat ligula, eget tempus ligula leo et est.',
'Maecenas ut elit sit amet nibh maximus condimentum in nec lorem. Pellentesque tincidunt odio vel leo suscipit, in interdum mi gravida.', - Quisque pellentesque, dolor nec sollicitudin iaculis, sem velit consequat ligula, eget tempus ligula leo et est.
'Donec non vulputate augue.', - Maecenas ut elit sit amet nibh maximus condimentum in nec lorem. Pellentesque tincidunt odio vel leo suscipit, in interdum mi gravida.
],
Donec non vulputate augue 🤓
`,
tagsList: { tagsList: {
title: 'Technologies', title: 'Technologies',
tags: [vue(), tailwindCss(), pnpm()], tags: [vue(), tailwindCss(), pnpm()],

View file

@ -23,10 +23,10 @@ const mainSectionData = {
{ label: 'Email', value: 'mark.freeman.dev@gmail.com' }, { label: 'Email', value: 'mark.freeman.dev@gmail.com' },
{ label: 'LinkedIn', value: '/in/mark-freeman', url: 'https://linkedin.com' }, { label: 'LinkedIn', value: '/in/mark-freeman', url: 'https://linkedin.com' },
{ label: 'GitHub', value: '/mark-freeman', url: 'https://github.com' }, { label: 'GitHub', value: '/mark-freeman', url: 'https://github.com' },
{ label: 'Website', value: 'mark-freeman-personal-website.com', url: '#', fullRow: true }, { label: 'Website', value: 'mark-freeman-personal-website.com', url: '/', fullRow: true },
], ],
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales ac dui at vestibulum. In condimentum metus id dui tincidunt, in blandit mi vehicula. Nulla lacinia, erat sit amet elementum vulputate, lectus mauris volutpat mi, vitae accumsan metus elit ut nunc. Vestibulum lacinia enim eget eros fermentum scelerisque. Proin augue leo, posuere ut imperdiet vitae, fermentum eu ipsum. Sed sed neque sagittis, posuere urna nec, commodo leo. Pellentesque posuere justo vitae massa volutpat maximus.', 'Lorem ipsum dolor sit amet, consectetur **adipiscing elit**. In sodales ac dui at *vestibulum*. In condimentum metus id dui tincidunt, in blandit mi [vehicula](/). Nulla lacinia, erat sit amet elementum vulputate, lectus mauris volutpat mi, vitae accumsan metus elit ut nunc. Vestibulum lacinia enim eget eros fermentum scelerisque. Proin augue leo, posuere ut imperdiet vitae, fermentum eu ipsum. Sed sed neque sagittis, posuere urna nec, commodo leo. Pellentesque posuere justo vitae massa volutpat maximus.',
tags: [{ name: 'Open for freelance' }, { name: 'Available for mentoring' }, { name: 'Working on side project' }], tags: [{ name: 'Open for freelance' }, { name: 'Available for mentoring' }, { name: 'Working on side project' }],
action: { action: {
label: 'Download CV', label: 'Download CV',

View file

@ -1,23 +1,12 @@
--- ---
import type { Description } from '@/types/shared'; import Description from '@/components/description.astro';
export interface Props { export interface Props {
content: Description; content: string;
} }
const { content } = Astro.props; const { content } = Astro.props;
const baseClass = /* tw */ 'text-base font-normal text-gray-500';
--- ---
<div class="text-base font-normal text-gray-500"> <Description content={content} classList={[baseClass]} />
{
Array.isArray(content) ? (
<ul class="list-disc pl-5">
{content.map((line) => (
<li>{line}</li>
))}
</ul>
) : (
<div class="text-justify">{content}</div>
)
}
</div>

View file

@ -1,6 +1,6 @@
--- ---
import type { MainSection } from '@/types/sections/main-section.types'; import type { MainSection } from '@/types/sections/main-section.types';
import Photo from '@/pdf/components/photo.astro'; import Photo from '@/components/photo.astro';
import Description from '@/pdf/components/description.astro'; import Description from '@/pdf/components/description.astro';
import LabelledValue from '@/pdf/components/labelled-value.astro'; import LabelledValue from '@/pdf/components/labelled-value.astro';

View file

@ -21,7 +21,7 @@ const { config, projects } = Astro.props;
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<ListItemHeading title={name} dates={dates} /> <ListItemHeading title={name} dates={dates} />
<Description content={description} /> <Description content={description} />
<div class="grid grid-cols-[auto_auto] gap-x-4 gap-y-1"> <div class="flex flex-col gap-1">
{(pdfDetails ?? details).map((detail) => ( {(pdfDetails ?? details).map((detail) => (
<LabelledValue {...detail} /> <LabelledValue {...detail} />
))} ))}

View file

@ -1,4 +1,4 @@
import type { DateRange, Description, LinkButton, Section } from '../shared'; import type { DateRange, LinkButton, Section } from '../shared';
export interface Diploma { export interface Diploma {
/** /**
@ -17,9 +17,9 @@ export interface Diploma {
dates: DateRange; dates: DateRange;
/** /**
* A short overview of your studies. You can write it as a paragraph (string) or as a list of bullet points (string[]). * A short overview of your studies. You can use markdown syntax.
*/ */
description: Description; description: string;
/** /**
* [WEB] Links related to your studies (e.g. course/university website, link to realized project). * [WEB] Links related to your studies (e.g. course/university website, link to realized project).

View file

@ -1,4 +1,4 @@
import type { DateRange, Description, LinkButton, Section, TagsList } from '../shared'; import type { DateRange, LinkButton, Section, TagsList } from '../shared';
export interface Job { export interface Job {
/** /**
@ -17,9 +17,9 @@ export interface Job {
dates: DateRange; dates: DateRange;
/** /**
* A short overview of your job. You can write it as a paragraph (string) or as a list of bullet points (string[]). * A short overview of your job. You can use markdown syntax.
*/ */
description: Description; description: string;
/** /**
* Any information that you want to highlight. * Any information that you want to highlight.

View file

@ -1,4 +1,4 @@
import type { DateRange, Photo, LabelledValue, LinkButton, Section, TagsList, Description } from '../shared'; import type { DateRange, Photo, LabelledValue, LinkButton, Section, TagsList } from '../shared';
export interface Project { export interface Project {
/** /**
@ -35,9 +35,9 @@ export interface Project {
pdfDetails?: LabelledValue[]; pdfDetails?: LabelledValue[];
/** /**
* A short overview of the project. You can write it as a paragraph (string) or as a list of bullet points (string[]). * A short overview of the project. You can use markdown syntax.
*/ */
description: Description; description: string;
/** /**
* Any information that you want to highlight. * Any information that you want to highlight.

View file

@ -21,7 +21,7 @@ export interface Testimonial {
relation: string; relation: string;
/** /**
* [WEB] Content of the testimonial. * [WEB] Content of the testimonial. You can use markdown syntax.
*/ */
content: string; content: string;

View file

@ -22,12 +22,6 @@ export type Photo = Promise<{ default: ImageMetadata }> | string;
*/ */
export type DateRange = [from: Date, to: Date | null]; export type DateRange = [from: Date, to: Date | null];
/**
* If a string is provided, it will be displayed as a single justified paragraph.
* If an array of strings is provided it will be displayed as a list.
*/
export type Description = string | string[];
export interface SectionConfig { export interface SectionConfig {
/** /**
* Name displayed as a section header (except for the main section). * Name displayed as a section header (except for the main section).

View file

@ -1,29 +1,15 @@
--- ---
import type { Description } from '@/types/shared'; import Description from '@/components/description.astro';
import Typography from './typography.astro';
export interface Props { export interface Props {
content: Description; content: string;
class?: string; class?: string;
} }
const { content, ...props } = Astro.props; const { content, class: className } = Astro.props;
const baseClass =
/* tw */ 'text-sm leading-relaxed font-normal text-gray-500 sm:text-base sm:leading-relaxed dark:text-gray-300';
--- ---
<div class:list={['text-base font-normal text-gray-500', props.class]}> <Description content={content} classList={[baseClass, className]} />
{
Array.isArray(content) ? (
<ul class="list-disc pl-5">
{content.map((line) => (
<Typography component="li" variant="paragraph">
{line}
</Typography>
))}
</ul>
) : (
<Typography class="text-justify" variant="paragraph">
{content}
</Typography>
)
}
</div>

View file

@ -1,6 +1,6 @@
--- ---
import type { Book } from '@/types/sections/favorites-section.types'; import type { Book } from '@/types/sections/favorites-section.types';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
export interface Props extends Book {} export interface Props extends Book {}

View file

@ -1,6 +1,6 @@
--- ---
import type { Media } from '@/types/sections/favorites-section.types'; import type { Media } from '@/types/sections/favorites-section.types';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
export interface Props extends Media {} export interface Props extends Media {}

View file

@ -1,6 +1,6 @@
--- ---
import type { Person } from '@/types/sections/favorites-section.types'; import type { Person } from '@/types/sections/favorites-section.types';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
export interface Props extends Person {} export interface Props extends Person {}

View file

@ -1,6 +1,6 @@
--- ---
import type { Video } from '@/types/sections/favorites-section.types'; import type { Video } from '@/types/sections/favorites-section.types';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
export interface Props extends Video {} export interface Props extends Video {}

View file

@ -1,8 +1,9 @@
--- ---
import type { MainSection } from '@/types/sections/main-section.types'; import type { MainSection } from '@/types/sections/main-section.types';
import Description from '@/web/components/description.astro';
import DownloadButton from '@/web/components/download-button.astro'; import DownloadButton from '@/web/components/download-button.astro';
import LinkButton from '@/web/components/link-button.astro'; import LinkButton from '@/web/components/link-button.astro';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import SectionCard from '@/web/components/section-card.astro'; import SectionCard from '@/web/components/section-card.astro';
import TagsList from '@/web/components/tags-list.astro'; import TagsList from '@/web/components/tags-list.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
@ -54,7 +55,7 @@ const { action, config, description, details, fullName, image, links, role, tags
} }
</div> </div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<Typography variant="paragraph">{description}</Typography> <Description content={description} />
<TagsList tags={tags} /> <TagsList tags={tags} />
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@ import type { Project } from '@/types/sections/portfolio-section.types';
import Description from '@/web/components/description.astro'; import Description from '@/web/components/description.astro';
import LabelledValue from '@/web/components/labelled-value.astro'; import LabelledValue from '@/web/components/labelled-value.astro';
import LinkButton from '@/web/components/link-button.astro'; import LinkButton from '@/web/components/link-button.astro';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import TagsList from '@/web/components/tags-list.astro'; import TagsList from '@/web/components/tags-list.astro';
import Timestamp from '@/web/components/timestamp.astro'; import Timestamp from '@/web/components/timestamp.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';

View file

@ -1,8 +1,9 @@
--- ---
import type { Testimonial } from '@/types/sections/testimonials-section.types'; import type { Testimonial } from '@/types/sections/testimonials-section.types';
import LinkButton from '@/web/components/link-button.astro'; import LinkButton from '@/web/components/link-button.astro';
import Photo from '@/web/components/photo.astro'; import Photo from '@/components/photo.astro';
import Typography from '@/web/components/typography.astro'; import Typography from '@/web/components/typography.astro';
import Description from '@/web/components/description.astro';
export interface Props extends Testimonial {} export interface Props extends Testimonial {}
@ -28,5 +29,5 @@ const { author, content, image, links, relation } = Astro.props;
) )
} }
</div> </div>
<Typography variant="paragraph">{content}</Typography> <Description content={content} />
</div> </div>