1
1
mirror of https://github.com/theoludwig/theoludwig.git synced 2025-05-29 22:37:44 +02:00

Compare commits

...

8 Commits

52 changed files with 9051 additions and 19161 deletions

View File

@ -6,12 +6,10 @@
}, },
"env": { "env": {
"node": true, "node": true,
"browser": true, "browser": true
"jest": true
}, },
"rules": { "rules": {
"prettier/prettier": "error", "prettier/prettier": "error",
"unicorn/prefer-node-protocol": "off", "unicorn/prefer-node-protocol": "error"
"@typescript-eslint/no-misused-promises": "off"
} }
} }

3
.gitignore vendored
View File

@ -11,8 +11,7 @@ out
# production # production
build build
dist dist
public/*.html public/curriculum-vitae
jsonresume-theme-custom/theme/index.html
# PWA # PWA
public/workbox-*.js public/workbox-*.js
public/sw.js public/sw.js

View File

@ -4,5 +4,5 @@
"http://localhost:3000/blog", "http://localhost:3000/blog",
"http://localhost:3000/blog/hello-world" "http://localhost:3000/blog/hello-world"
], ],
"files": ["./public/curriculum-vitae.html"] "files": ["./public/curriculum-vitae/index.html"]
} }

View File

@ -1,10 +1,6 @@
{ {
"*": ["editorconfig-checker"], "*": ["editorconfig-checker"],
"*.{js,jsx,ts,tsx}": [ "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
"prettier --write",
"eslint --fix",
"jest --findRelatedTests"
],
"*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"], "*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"],
"*.{md,mdx}": ["prettier --write", "markdownlint --dot --fix"] "*.{md,mdx}": ["prettier --write", "markdownlint-cli2 --fix"]
} }

11
.markdownlint-cli2.jsonc Normal file
View File

@ -0,0 +1,11 @@
{
"config": {
"default": true,
"MD013": false,
"MD024": false,
"MD033": false,
"MD041": false
},
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"]
}

View File

@ -1,7 +0,0 @@
{
"default": true,
"MD013": false,
"MD024": false,
"MD033": false,
"MD041": false
}

View File

@ -1,15 +1,15 @@
FROM node:16.15.0 AS dependencies FROM node:16.16.0 AS dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm install RUN npm install
FROM node:16.15.0 AS builder FROM node:16.16.0 AS builder
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./ ./ COPY ./ ./
COPY --from=dependencies /usr/src/app/node_modules ./node_modules COPY --from=dependencies /usr/src/app/node_modules ./node_modules
RUN npm run build RUN npm run build
FROM node:16.15.0 AS runner FROM node:16.16.0 AS runner
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=builder /usr/src/app/next.config.js ./next.config.js COPY --from=builder /usr/src/app/next.config.js ./next.config.js

View File

@ -27,6 +27,7 @@ export const Footer: React.FC<FooterProps> = (props) => {
<p className='mt-1'> <p className='mt-1'>
Version{' '} Version{' '}
<a <a
data-cy='version-link'
className='text-yellow hover:underline dark:text-yellow-dark' className='text-yellow hover:underline dark:text-yellow-dark'
href={versionLink} href={versionLink}
target='_blank' target='_blank'

View File

@ -1,7 +1,7 @@
import { useCallback, useEffect, useState, useRef } from 'react' import { useCallback, useEffect, useState, useRef } from 'react'
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import setLanguage from 'next-translate/setLanguage' import setLanguage from 'next-translate/setLanguage'
import classNames from 'classnames' import classNames from 'clsx'
import i18n from 'i18n.json' import i18n from 'i18n.json'

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import { Header } from '..'
describe('<Header />', () => {
it('should render', () => {
const { getByText } = render(<Header />)
expect(getByText('Divlo')).toBeInTheDocument()
})
})

View File

@ -1,4 +1,4 @@
import classNames from 'classnames' import classNames from 'clsx'
export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => { export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
const { children, className, ...rest } = props const { children, className, ...rest } = props

View File

@ -1,15 +0,0 @@
import { render } from '@testing-library/react'
import { ErrorPage } from '../ErrorPage'
describe('<ErrorPage />', () => {
it('should render the message and statusCode', () => {
const messageContent = 'message content'
const statusCode = 404
const { getByText } = render(
<ErrorPage statusCode={statusCode} message={messageContent} />
)
expect(getByText(messageContent)).toBeInTheDocument()
expect(getByText(statusCode)).toBeInTheDocument()
})
})

View File

@ -1,16 +0,0 @@
import { render } from '@testing-library/react'
import { Footer } from '../Footer'
describe('<Footer />', () => {
it('should render with appropriate link tag version', () => {
const version = '1.0.0'
const { getByText } = render(<Footer version={version} />)
const versionLink = getByText(version) as HTMLAnchorElement
expect(getByText('Divlo')).toBeInTheDocument()
expect(versionLink).toBeInTheDocument()
expect(versionLink.href).toEqual(
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
)
})
})

View File

@ -1,4 +1,4 @@
import classNames from 'classnames' import classNames from 'clsx'
type ShadowContainerProps = React.ComponentPropsWithRef<'div'> type ShadowContainerProps = React.ComponentPropsWithRef<'div'>

20
cypress.config.ts Normal file
View File

@ -0,0 +1,20 @@
import { defineConfig } from 'cypress'
export default defineConfig({
fixturesFolder: false,
video: false,
downloadsFolder: undefined,
screenshotOnRunFailure: false,
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: false
},
component: {
devServer: {
framework: 'next',
bundler: 'webpack'
}
}
})

View File

@ -1,8 +0,0 @@
{
"baseUrl": "http://localhost:3000",
"pluginsFile": false,
"supportFile": false,
"fixturesFolder": false,
"video": false,
"screenshotOnRunFailure": false
}

View File

@ -0,0 +1,18 @@
import { Footer } from 'components/Footer'
describe('<Footer />', () => {
it('should render with appropriate link tag version', () => {
const version = '1.0.0'
cy.mount(<Footer version={version} />)
cy.contains('Divlo')
.get('[data-cy=version-link]')
.should('have.text', version)
.should(
'have.attr',
'href',
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
)
})
})
export {}

View File

@ -0,0 +1,17 @@
import { getAge } from '../../../utils/getAge'
describe('utils/getAge', () => {
it('should calculate the right age of a person', () => {
cy.clock(new Date('2018-03-20')).then(() => {
const birthDate = new Date('1980-02-20')
expect(getAge(birthDate)).equal(38)
})
})
it('should calculate the right age of a person (taking into account the months)', () => {
cy.clock(new Date('2018-03-20')).then(() => {
const birthDate = new Date('1980-07-20')
expect(getAge(birthDate)).equal(37)
})
})
})

View File

@ -56,3 +56,5 @@ describe('Common > Header', () => {
}) })
}) })
}) })
export {}

View File

@ -5,3 +5,5 @@ describe('Page /404', () => {
cy.get('[data-cy=status-code]').contains('404') cy.get('[data-cy=status-code]').contains('404')
}) })
}) })
export {}

View File

@ -5,3 +5,5 @@ describe('Page /500', () => {
cy.get('[data-cy=status-code]').contains('500') cy.get('[data-cy=status-code]').contains('500')
}) })
}) })
export {}

View File

@ -11,3 +11,5 @@ describe('Page /blog/[slug]', () => {
cy.get('[data-cy=status-code]').contains('404') cy.get('[data-cy=status-code]').contains('404')
}) })
}) })
export {}

View File

@ -20,3 +20,5 @@ describe('Page /blog', () => {
.should('eq', '/blog/hello-world') .should('eq', '/blog/hello-world')
}) })
}) })
export {}

View File

@ -17,3 +17,5 @@ describe('Page /', () => {
} }
}) })
}) })
export {}

View File

@ -0,0 +1,3 @@
/// <reference types="cypress" />
export {}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@ -0,0 +1,13 @@
import { mount } from 'cypress/react'
import './commands'
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)

View File

@ -1,9 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["cypress"],
"isolatedModules": false
},
"include": ["../node_modules/cypress", "./**/*.ts"]
}

View File

@ -1,14 +0,0 @@
const nextJest = require('next/jest')
const createJestConfig = nextJest()
const customJestConfig = {
moduleDirectories: ['node_modules', './'],
modulePathIgnorePatterns: ['<rootDir>/cypress'],
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
'@testing-library/react'
]
}
module.exports = createJestConfig(customJestConfig)

View File

@ -1,4 +1,22 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules node_modules
theme/index.html
dist dist
.parcel-cache dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

Before

Width:  |  Height:  |  Size: 1015 B

After

Width:  |  Height:  |  Size: 1015 B

View File

Before

Width:  |  Height:  |  Size: 986 B

After

Width:  |  Height:  |  Size: 986 B

View File

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 629 B

View File

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 912 B

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

View File

@ -5,10 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= locals.basics.name %></title> <title><%= locals.basics.name %></title>
<link rel="icon" type="image/png" href="<%= locals.basics.image %>" /> <link rel="icon" type="image/png" href="<%= locals.basics.image %>" />
<link rel="stylesheet" href="./styles/global.css" />
<style>
@import './styles/global.css';
</style>
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
@ -59,7 +56,7 @@
<div class="background-details"> <div class="background-details">
<div class="detail" id="about"> <div class="detail" id="about">
<div class="icon"> <div class="icon">
<img src="data-url:./images/user.svg" alt="user" /> <img src="./images/user.svg" alt="user" />
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">À propos</h4> <h4 class="title text-uppercase">À propos</h4>
@ -75,10 +72,7 @@
<div class="detail" id="work-experience"> <div class="detail" id="work-experience">
<div class="icon"> <div class="icon">
<img <img src="./images/building-columns.svg" alt="work" />
src="data-url:./images/building-columns.svg"
alt="work"
/>
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Expériences</h4> <h4 class="title text-uppercase">Expériences</h4>
@ -117,7 +111,7 @@
<div class="detail" id="skills"> <div class="detail" id="skills">
<div class="icon"> <div class="icon">
<img src="data-url:./images/toolbox.svg" alt="toolbox" /> <img src="./images/toolbox.svg" alt="toolbox" />
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Compétences</h4> <h4 class="title text-uppercase">Compétences</h4>
@ -144,10 +138,7 @@
<div class="detail" id="education"> <div class="detail" id="education">
<div class="icon"> <div class="icon">
<img <img src="./images/graduation-cap.svg" alt="graduation" />
src="data-url:./images/graduation-cap.svg"
alt="graduation"
/>
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Éducation</h4> <h4 class="title text-uppercase">Éducation</h4>
@ -183,7 +174,7 @@
<div class="detail" id="interests"> <div class="detail" id="interests">
<div class="icon"> <div class="icon">
<img src="data-url:./images/heart.svg" alt="heart" /> <img src="./images/heart.svg" alt="heart" />
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Intérets</h4> <h4 class="title text-uppercase">Intérets</h4>

View File

@ -1,28 +0,0 @@
import fs from 'fs'
import { fileURLToPath } from 'url'
import ejs from 'ejs'
import date from 'date-and-time'
import { Parcel } from '@parcel/core'
export const render = async (resume) => {
const themeIndexURL = new URL('./theme/index.ejs', import.meta.url)
const themeBuildURL = new URL('./theme/index.html', import.meta.url)
const indexHTMLURL = new URL('./dist/index.html', import.meta.url)
const themeBuildPath = fileURLToPath(themeBuildURL)
const html = await ejs.renderFile(fileURLToPath(themeIndexURL), {
date,
locals: {
...resume
}
})
await fs.promises.writeFile(themeBuildURL, html, { encoding: 'utf-8' })
const bundler = new Parcel({
entries: themeBuildPath,
source: themeBuildPath,
mode: 'production',
defaultConfig: '@parcel/config-default'
})
await bundler.run()
return await fs.promises.readFile(indexHTMLURL, { encoding: 'utf-8' })
}

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,18 @@
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": {}, "scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": { "dependencies": {
"date-and-time": "2.3.1",
"ejs": "3.1.7",
"modern-normalize": "1.1.0" "modern-normalize": "1.1.0"
}, },
"devDependencies": { "devDependencies": {
"@parcel/config-default": "2.5.0", "@types/node": "18.7.11",
"@parcel/core": "2.5.0", "date-and-time": "2.4.1",
"@parcel/optimizer-data-url": "2.5.0", "vite": "3.0.9",
"@parcel/transformer-inline-string": "2.5.0", "vite-plugin-html": "3.2.0"
"parcel": "2.5.0"
} }
} }

View File

@ -1,18 +1,19 @@
import fs from 'fs' import { fileURLToPath } from 'node:url'
import fs from 'node:fs'
import { render } from '../index.js' import { build } from 'vite'
const jsonResumeURL = new URL('../../resume.json', import.meta.url) const jsonResumeThemeCustom = new URL('../', import.meta.url)
const publicResumeURL = new URL( const jsonResumeThemeCustomDist = new URL('./dist', jsonResumeThemeCustom)
'../../public/curriculum-vitae.html', const publicResumeOutputURL = new URL(
'../../public/curriculum-vitae',
import.meta.url import.meta.url
) )
const dataResumeStringJSON = await fs.promises.readFile(jsonResumeURL, { await build({
encoding: 'utf-8' root: fileURLToPath(jsonResumeThemeCustom)
}) })
const dataResumeJSON = JSON.parse(dataResumeStringJSON)
const dataResumeIndexHTML = await render(dataResumeJSON) await fs.promises.cp(jsonResumeThemeCustomDist, publicResumeOutputURL, {
await fs.promises.writeFile(publicResumeURL, dataResumeIndexHTML, { recursive: true
encoding: 'utf-8'
}) })

View File

@ -1,4 +1,4 @@
@import 'npm:modern-normalize/modern-normalize.css'; @import 'modern-normalize/modern-normalize.css';
body { body {
font-family: 'Montserrat', 'Arial', 'sans-serif'; font-family: 'Montserrat', 'Arial', 'sans-serif';

View File

@ -0,0 +1,33 @@
import fs from 'node:fs'
import { defineConfig } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'
import date from 'date-and-time'
const jsonResumeURL = new URL('../resume.json', import.meta.url)
const dataResumeStringJSON = await fs.promises.readFile(jsonResumeURL, {
encoding: 'utf-8'
})
const resume = JSON.parse(dataResumeStringJSON)
// https://vitejs.dev/config/
export default defineConfig({
build: {
assetsDir: './'
},
plugins: [
createHtmlPlugin({
inject: {
data: {
date,
locals: {
...resume
}
}
}
})
],
css: {
postcss: {}
}
})

View File

@ -6,7 +6,7 @@
"birth-date": "Birth date", "birth-date": "Birth date",
"years-old": "years old", "years-old": "years old",
"nationality": "Nationality", "nationality": "Nationality",
"description-bottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (first year)." "description-bottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (second year)."
}, },
"interests": { "interests": {
"title": "Interests", "title": "Interests",

View File

@ -6,7 +6,7 @@
"birth-date": "Date de naissance", "birth-date": "Date de naissance",
"years-old": "ans", "years-old": "ans",
"nationality": "Nationalité", "nationality": "Nationalité",
"description-bottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (premre année)." "description-bottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (deuxme année)."
}, },
"interests": { "interests": {
"title": "Intérêts", "title": "Intérêts",

View File

@ -1,41 +1,12 @@
const nextPWA = require('next-pwa') const nextPWA = require('next-pwa')({
disable: process.env.NODE_ENV !== 'production',
dest: 'public'
})
const nextTranslate = require('next-translate') const nextTranslate = require('next-translate')
const { createSecureHeaders } = require('next-secure-headers')
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
module.exports = nextTranslate( module.exports = nextTranslate(
nextPWA({ nextPWA({
reactStrictMode: true, reactStrictMode: true
pwa: {
disable: process.env.NODE_ENV !== 'production',
dest: 'public'
},
headers() {
return [
{
source: '/:path*',
headers: createSecureHeaders({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
'data:',
"'unsafe-eval'",
"'unsafe-inline'"
],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ['*', 'data:', 'blob:'],
mediaSrc: "'none'",
connectSrc: '*',
objectSrc: "'none'",
fontSrc: "'self'",
baseURI: "'none'"
}
}
})
}
]
}
}) })
) )

22457
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
{ {
"name": "divlo", "name": "divlo",
"version": "2.3.1", "version": "2.4.0",
"private": true, "private": true,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/Divlo/Divlo" "url": "https://github.com/Divlo/Divlo"
}, },
"engines": { "engines": {
"node": ">=14.0.0", "node": ">=16.0.0",
"npm": ">=7.0.0" "npm": ">=8.0.0"
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@ -17,86 +17,82 @@
"export": "next export", "export": "next export",
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint \"**/*.{md,mdx}\" --dot --ignore-path \".gitignore\"", "lint:markdown": "markdownlint-cli2",
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"", "lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"",
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"", "lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"test:unit": "jest", "test:unit": "cypress run --component",
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://localhost:3000\" \"html-w3c-validator\"", "test:html-w3c-validator": "start-server-and-test \"start\" \"http://localhost:3000\" \"html-w3c-validator\"",
"test:lighthouse": "lhci autorun", "test:lighthouse": "lhci autorun",
"test:e2e": "start-server-and-test \"start\" \"http://localhost:3000\" \"cypress run\"", "test:e2e": "start-server-and-test \"start\" \"http://localhost:3000\" \"cypress run\"",
"test:e2e:dev": "start-server-and-test \"dev\" \"http://localhost:3000\" \"cypress open\"", "test:dev": "start-server-and-test \"dev\" \"http://localhost:3000\" \"cypress open\"",
"resume:build": "node ./jsonresume-theme-custom/scripts/build.js", "resume:build": "node ./jsonresume-theme-custom/scripts/build.js",
"release": "semantic-release", "release": "semantic-release",
"deploy": "vercel", "deploy": "vercel",
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@fontsource/montserrat": "4.5.10", "@fontsource/montserrat": "4.5.12",
"@fortawesome/fontawesome-svg-core": "6.1.1", "@fortawesome/fontawesome-svg-core": "6.1.2",
"@fortawesome/free-brands-svg-icons": "6.1.1", "@fortawesome/free-brands-svg-icons": "6.1.2",
"@fortawesome/free-solid-svg-icons": "6.1.1", "@fortawesome/free-solid-svg-icons": "6.1.2",
"@fortawesome/react-fontawesome": "0.1.18", "@fortawesome/react-fontawesome": "0.2.0",
"classnames": "2.3.1", "@giscus/react": "2.2.0",
"date-and-time": "2.3.1", "clsx": "1.2.1",
"date-and-time": "2.4.1",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"html-react-parser": "1.4.12", "html-react-parser": "3.0.4",
"next": "12.1.6", "next": "12.2.5",
"next-mdx-remote": "4.0.3", "next-mdx-remote": "4.1.0",
"next-pwa": "5.5.2", "next-pwa": "5.6.0",
"next-themes": "0.1.1", "next-themes": "0.2.0",
"next-translate": "1.4.0", "next-translate": "1.5.0",
"react": "18.1.0", "react": "18.2.0",
"react-dom": "18.1.0", "react-dom": "18.2.0",
"read-pkg": "7.1.0", "read-pkg": "7.1.0",
"rehype-raw": "6.1.1", "rehype-raw": "6.1.1",
"rehype-slug": "5.0.1", "rehype-slug": "5.0.1",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"sharp": "0.30.4", "sharp": "0.30.7",
"shiki": "0.10.1", "shiki": "0.11.1",
"unified": "10.1.2", "unified": "10.1.2",
"unist-util-visit": "4.1.0", "unist-util-visit": "4.1.1",
"universal-cookie": "4.0.4" "universal-cookie": "4.0.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.2.4", "@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "16.2.4", "@commitlint/config-conventional": "17.0.3",
"@lhci/cli": "0.9.0", "@lhci/cli": "0.9.0",
"@saithodev/semantic-release-backmerge": "2.1.2", "@saithodev/semantic-release-backmerge": "2.1.2",
"@semantic-release/git": "10.0.1", "@semantic-release/git": "10.0.1",
"@tailwindcss/typography": "0.5.2", "@tailwindcss/typography": "0.5.4",
"@testing-library/jest-dom": "5.16.4", "@types/node": "18.7.11",
"@testing-library/react": "13.1.1", "@types/react": "18.0.17",
"@types/jest": "27.5.0",
"@types/node": "17.0.31",
"@types/react": "18.0.8",
"@types/unist": "2.0.6", "@types/unist": "2.0.6",
"@typescript-eslint/eslint-plugin": "5.22.0", "@typescript-eslint/eslint-plugin": "5.34.0",
"autoprefixer": "10.4.7", "autoprefixer": "10.4.8",
"cypress": "9.6.0", "cypress": "10.6.0",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"eslint": "8.14.0", "eslint": "8.22.0",
"eslint-config-conventions": "2.0.0", "eslint-config-conventions": "3.0.0",
"eslint-config-next": "12.1.6", "eslint-config-next": "12.2.5",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.0.0", "eslint-plugin-promise": "6.0.0",
"eslint-plugin-unicorn": "42.0.0", "eslint-plugin-unicorn": "43.0.2",
"html-w3c-validator": "1.2.0", "html-w3c-validator": "1.2.0",
"husky": "7.0.4", "husky": "8.0.1",
"jest": "27.5.1",
"jsonresume-theme-custom": "file:./jsonresume-theme-custom", "jsonresume-theme-custom": "file:./jsonresume-theme-custom",
"lint-staged": "12.4.1", "lint-staged": "13.0.3",
"markdownlint-cli": "0.31.1", "markdownlint-cli2": "0.5.1",
"next-secure-headers": "2.2.0", "postcss": "8.4.16",
"postcss": "8.4.13", "prettier": "2.7.1",
"prettier": "2.6.2", "prettier-plugin-tailwindcss": "0.1.13",
"prettier-plugin-tailwindcss": "0.1.10", "semantic-release": "19.0.4",
"semantic-release": "19.0.2",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"tailwindcss": "3.0.24", "tailwindcss": "3.1.8",
"typescript": "4.6.4", "typescript": "4.7.4",
"vercel": "24.2.0" "vercel": "28.1.1"
} }
} }

View File

@ -1,6 +1,8 @@
import { GetStaticProps, GetStaticPaths, NextPage } from 'next' import { GetStaticProps, GetStaticPaths, NextPage } from 'next'
import { MDXRemote } from 'next-mdx-remote' import { MDXRemote } from 'next-mdx-remote'
import date from 'date-and-time' import date from 'date-and-time'
import Giscus from '@giscus/react'
import { useTheme } from 'next-themes'
import { Head } from 'components/Head' import { Head } from 'components/Head'
import { Header } from 'components/Header' import { Header } from 'components/Header'
@ -14,6 +16,8 @@ interface BlogPostPageProps extends FooterProps {
const BlogPostPage: NextPage<BlogPostPageProps> = (props) => { const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
const { version, post } = props const { version, post } = props
const { theme = 'dark' } = useTheme()
return ( return (
<> <>
<Head <Head
@ -43,6 +47,20 @@ const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
} }
}} }}
/> />
<Giscus
id='comments'
repo='Divlo/Divlo'
repoId='MDEwOlJlcG9zaXRvcnkzNTg5NDg1NDQ='
category='General'
categoryId='DIC_kwDOFWUewM4CQ_WK'
mapping='pathname'
reactionsEnabled='1'
emitMetadata='0'
inputPosition='top'
theme={theme}
lang='en'
loading='lazy'
/>
</div> </div>
</main> </main>
<Footer version={version} /> <Footer version={version} />

View File

View File

@ -10,7 +10,7 @@
"removeComments": true, "removeComments": true,
"noEmit": true, "noEmit": true,
"strict": true, "strict": true,
"types": ["jest", "@testing-library/jest-dom", "@testing-library/react"], "types": ["cypress"],
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,

View File

@ -1,15 +0,0 @@
import { getAge } from '../getAge'
describe('utils/getAge', () => {
it('should calculate the right age of a person', () => {
const birthDate = new Date('1980-02-20')
jest.useFakeTimers().setSystemTime(new Date('2018-03-20'))
expect(getAge(birthDate)).toBe(38)
})
it('should calculate the right age of a person (taking into account the months)', () => {
const birthDate = new Date('1980-07-20')
jest.useFakeTimers().setSystemTime(new Date('2018-03-20'))
expect(getAge(birthDate)).toBe(37)
})
})

View File

@ -1,5 +1,5 @@
import fs from 'fs' import fs from 'node:fs'
import path from 'path' import path from 'node:path'
import type { MDXRemoteSerializeResult } from 'next-mdx-remote' import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { nodeTypes } from '@mdx-js/mdx' import { nodeTypes } from '@mdx-js/mdx'