mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
Compare commits
17 Commits
v3.2.4
...
dd09092842
Author | SHA1 | Date | |
---|---|---|---|
dd09092842
|
|||
f64acb68c7
|
|||
3074945c54
|
|||
fc0dfdda5f
|
|||
f62964c62a
|
|||
8ec113c9cb
|
|||
8a59e9034f
|
|||
d7121ea833
|
|||
c10f690622
|
|||
6915072ab9
|
|||
dd803bcc51
|
|||
efa33f26ec
|
|||
5f3dfad988
|
|||
b231381cb3
|
|||
bbb2e56512
|
|||
66cf6d7438
|
|||
2a635bf3ba
|
@ -1 +0,0 @@
|
||||
FROM mcr.microsoft.com/devcontainers/javascript-node:20
|
@ -1,9 +0,0 @@
|
||||
services:
|
||||
workspace:
|
||||
build:
|
||||
context: "./"
|
||||
dockerfile: "./Dockerfile"
|
||||
volumes:
|
||||
- "..:/workspace:cached"
|
||||
command: "sleep infinity"
|
||||
network_mode: "host"
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "theoludwig",
|
||||
"dockerComposeFile": "./compose.yaml",
|
||||
"service": "workspace",
|
||||
"workspaceFolder": "/workspace",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"remote.autoForwardPorts": false,
|
||||
"remote.localPortHost": "allInterfaces"
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"editorconfig.editorconfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"mikestead.dotenv",
|
||||
"ms-azuretools.vscode-docker"
|
||||
]
|
||||
},
|
||||
"remoteUser": "node"
|
||||
}
|
@ -3,13 +3,9 @@
|
||||
"extends": [
|
||||
"conventions",
|
||||
"next/core-web-vitals",
|
||||
"plugin:tailwindcss/recommended",
|
||||
"prettier"
|
||||
"plugin:tailwindcss/recommended"
|
||||
],
|
||||
"plugins": ["prettier"],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["import", "promise", "unicorn"],
|
||||
"settings": {
|
||||
"tailwindcss": {
|
||||
"callees": ["classNames"]
|
||||
@ -19,7 +15,6 @@
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"react/self-closing-comp": [
|
||||
"error",
|
||||
{
|
||||
@ -33,7 +28,11 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser"
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
- uses: "actions/checkout@v4.1.6"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.2"
|
||||
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
- uses: "actions/checkout@v4.1.6"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.2"
|
||||
@ -37,6 +37,6 @@ jobs:
|
||||
run: "npm run lint:prettier"
|
||||
|
||||
- name: "lint:dotenv"
|
||||
uses: "dotenv-linter/action-dotenv-linter@v2.18.0"
|
||||
uses: "dotenv-linter/action-dotenv-linter@v2.21.0"
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
release:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
- uses: "actions/checkout@v4.1.6"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
test-unit:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
- uses: "actions/checkout@v4.1.6"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.2"
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
test-e2e:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
- uses: "actions/checkout@v4.1.6"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.2"
|
||||
|
10
.gitpod.yml
10
.gitpod.yml
@ -1,10 +0,0 @@
|
||||
image: "gitpod/workspace-full"
|
||||
|
||||
tasks:
|
||||
- before: "cp .env.example .env"
|
||||
init: "npm clean-install"
|
||||
command: "npm run dev"
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: "open-preview"
|
@ -29,8 +29,6 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv
|
||||
|
||||
## Getting Started
|
||||
|
||||
[](https://gitpod.io/#https://github.com/theoludwig/theoludwig)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) >= 20.0.0
|
||||
@ -66,6 +64,6 @@ npm run dev
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
### Services started
|
||||
### Service started
|
||||
|
||||
- `website`: <http://127.0.0.1:3000>
|
||||
`website`: <http://127.0.0.1:3000>
|
||||
|
@ -62,15 +62,17 @@ code {
|
||||
code .line::before {
|
||||
content: counter(step);
|
||||
counter-increment: step;
|
||||
width: 1rem;
|
||||
margin-right: 1.5rem;
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
text-align: right;
|
||||
color: rgba(133, 133, 133, 0.8);
|
||||
word-wrap: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code .line:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.katex .base {
|
||||
display: inline !important;
|
||||
white-space: normal !important;
|
||||
|
@ -57,10 +57,13 @@ const RootLayout = (props: RootLayoutProps): JSX.Element => {
|
||||
return (
|
||||
<html
|
||||
lang={i18n.locale}
|
||||
className={classNames({
|
||||
dark: theme === "dark",
|
||||
light: theme === "light",
|
||||
})}
|
||||
className={classNames(
|
||||
{
|
||||
dark: theme === "dark",
|
||||
light: theme === "light",
|
||||
},
|
||||
"scroll-smooth",
|
||||
)}
|
||||
style={{
|
||||
colorScheme: theme,
|
||||
}}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { faLink } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { nodeTypes } from "@mdx-js/mdx"
|
||||
import rehypeShikiFromHighlighter from "@shikijs/rehype/core"
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import { cookies } from "next/headers"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { cookies } from "next/headers"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faLink } from "@fortawesome/free-solid-svg-icons"
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import { nodeTypes } from "@mdx-js/mdx"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import rehypeSlug from "rehype-slug"
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeKatex from "rehype-katex"
|
||||
import { getHighlighter } from "shiki"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import rehypeSlug from "rehype-slug"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkMath from "remark-math"
|
||||
import { getHighlighterCore } from "shiki/core"
|
||||
|
||||
import "katex/dist/katex.min.css"
|
||||
|
||||
import { getTheme } from "@/theme/theme.server"
|
||||
import { remarkSyntaxHighlightingPlugin } from "@/blog/remarkSyntaxHighlightingPlugin"
|
||||
import { BlogPostComments } from "@/blog/BlogPostComments"
|
||||
import { getTheme } from "@/theme/theme.server"
|
||||
|
||||
const Heading = (
|
||||
props: React.DetailedHTMLProps<
|
||||
@ -26,14 +26,14 @@ const Heading = (
|
||||
): JSX.Element => {
|
||||
const { children, id = "" } = props
|
||||
return (
|
||||
<h2 {...props} className="group">
|
||||
<Link
|
||||
href={`#${id}`}
|
||||
className="invisible !text-black group-hover:visible dark:!text-white"
|
||||
>
|
||||
<FontAwesomeIcon className="mr-2 inline size-4" icon={faLink} />
|
||||
<h2 {...props}>
|
||||
<Link href={`#${id}`} className="group relative hover:no-underline">
|
||||
<FontAwesomeIcon
|
||||
className="absolute bottom-2 left-[-26px] mr-2 hidden size-4 !text-black group-hover:inline dark:!text-white"
|
||||
icon={faLink}
|
||||
/>
|
||||
{children}
|
||||
</Link>
|
||||
{children}
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
@ -50,8 +50,19 @@ export const BlogPostContent = async (
|
||||
const cookiesStore = cookies()
|
||||
const theme = getTheme()
|
||||
|
||||
const highlighter = await getHighlighter({
|
||||
theme: `${theme}-plus`,
|
||||
const highlighter = await getHighlighterCore({
|
||||
themes: [
|
||||
import("shiki/themes/light-plus.mjs"),
|
||||
import("shiki/themes/dark-plus.mjs"),
|
||||
],
|
||||
langs: [
|
||||
import("shiki/langs/markdown.mjs"),
|
||||
import("shiki/langs/shell.mjs"),
|
||||
import("shiki/langs/javascript.mjs"),
|
||||
import("shiki/langs/typescript.mjs"),
|
||||
import("shiki/langs/python.mjs"),
|
||||
],
|
||||
loadWasm: import("shiki/wasm"),
|
||||
})
|
||||
|
||||
return (
|
||||
@ -61,15 +72,18 @@ export const BlogPostContent = async (
|
||||
source={content}
|
||||
options={{
|
||||
mdxOptions: {
|
||||
remarkPlugins: [
|
||||
remarkGfm,
|
||||
[remarkSyntaxHighlightingPlugin, { highlighter }],
|
||||
remarkMath,
|
||||
],
|
||||
remarkPlugins: [remarkGfm, remarkMath],
|
||||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
[rehypeRaw, { passThrough: nodeTypes }],
|
||||
rehypeKatex,
|
||||
[
|
||||
rehypeShikiFromHighlighter,
|
||||
highlighter,
|
||||
{
|
||||
theme: `${theme}-plus`,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}}
|
||||
|
@ -9,37 +9,34 @@ export const BlogPosts = async (): Promise<JSX.Element> => {
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center p-8">
|
||||
<div className="w-[1600px]" data-cy="blog-posts">
|
||||
{posts.map((post, index) => {
|
||||
<ul className="w-[1600px]" data-cy="blog-posts">
|
||||
{posts.map((post) => {
|
||||
const postPublishedOn = date.format(
|
||||
new Date(post.frontmatter.publishedOn),
|
||||
"DD/MM/YYYY",
|
||||
)
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${post.slug}`}
|
||||
key={index}
|
||||
locale="en"
|
||||
data-cy={post.slug}
|
||||
>
|
||||
<ShadowContainer className="cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2">
|
||||
<h2
|
||||
data-cy="blog-post-title"
|
||||
className="text-xl font-semibold text-primary dark:text-primary-dark"
|
||||
>
|
||||
{post.frontmatter.title}
|
||||
</h2>
|
||||
<p data-cy="blog-post-date" className="mt-2">
|
||||
{postPublishedOn}
|
||||
</p>
|
||||
<p data-cy="blog-post-description" className="mt-3">
|
||||
{post.frontmatter.description}
|
||||
</p>
|
||||
</ShadowContainer>
|
||||
</Link>
|
||||
<li key={post.slug}>
|
||||
<Link href={`/blog/${post.slug}`} locale="en" data-cy={post.slug}>
|
||||
<ShadowContainer className="cursor-pointer p-6 transition-all duration-300 ease-in-out hover:scale-[1.02]">
|
||||
<h2
|
||||
data-cy="blog-post-title"
|
||||
className="text-xl font-semibold text-primary dark:text-primary-dark"
|
||||
>
|
||||
{post.frontmatter.title}
|
||||
</h2>
|
||||
<p data-cy="blog-post-date" className="mt-2">
|
||||
{postPublishedOn}
|
||||
</p>
|
||||
<p data-cy="blog-post-description" className="mt-3">
|
||||
{post.frontmatter.description}
|
||||
</p>
|
||||
</ShadowContainer>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -154,6 +154,17 @@ git reset --soft <branch>
|
||||
# (by first being on the branch where you want to apply the commit)
|
||||
git cherry-pick <commit>
|
||||
|
||||
# To avoid creating duplicated commits with cherry-pick, we can use rebase after cherry-pick.
|
||||
# <target-branch> being the commit where you want to apply the commit to cherry-pick.
|
||||
# <from-branch> being the branch where the commit to cherry-pick is.
|
||||
git rebase <target-branch> <from-branch>
|
||||
|
||||
# If, by mistake, you have started a branch from the wrong base branch, you can rebase the branch on the correct base branch.
|
||||
# For example, if you have started a branch `feature-2` from `feature` instead of `develop`, you can rebase the branch on `develop`.
|
||||
git rebase --onto <new-base-branch> <old-base-branch> <branch>
|
||||
# For example:
|
||||
git rebase --onto develop feature feature-2
|
||||
|
||||
# To list all commits that differ between two branches
|
||||
git log <branch1>..<branch2> # commits in branch2 that are not in branch1 (branch2 ahead of branch1, branch2 behind branch1)
|
||||
git log <branch2>..<branch1> # commits in branch1 that are not in branch2 (branch1 ahead of branch2, branch1 behind branch2)
|
||||
@ -245,6 +256,32 @@ There are many ways to organize the work, but the most popular ones are:
|
||||
|
||||
They are called **Git workflows**, or **Git branching strategies**.
|
||||
|
||||
## Tips and tricks
|
||||
|
||||
### `diff-commits` alias
|
||||
|
||||
The `git diff` command allows you to compare the changes between two commits, branches, etc.
|
||||
|
||||
Sometimes, you want to compare what commits have been made between two branches, without looking at the changes in the files, to do so, we can create an `alias` in `.gitconfig`:
|
||||
|
||||
```sh
|
||||
[alias]
|
||||
diff-commits = !sh -c 'echo -n "Commits in $2 not in $1 \\(" && printf "%d" $(git cherry -v $1 $2 | wc -l) && echo "\\)" && git cherry -v $1 $2 && echo "" && echo -n "Commits in $1 not in $2 \\(" && printf "%d" $(git cherry -v $2 $1 | wc -l) && echo "\\)" && git cherry -v $2 $1' -
|
||||
```
|
||||
|
||||
With this alias, we can compare the commits between `main` and `develop` branches for example:
|
||||
|
||||
```sh
|
||||
$ git diff-commits main develop
|
||||
|
||||
Commits in develop not in main (2)
|
||||
+ 9b80e0724df8454b43bc3935a1bffb67615572d7 feat: new feature
|
||||
+ 50721f8ecb60ff023bdccc1873ec1e20ee0b21a0 feat: new feature 2
|
||||
|
||||
Commits in main not in develop (1)
|
||||
- f7bb9d2af7763e0a311099e880e8bf7d6b51bf4d fix: urgent hotfix
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
`git` is the tool that every programmer should know to do collaborative work (not only, `git` is also very powerful even when working alone) and keep track of changes across a set of files.
|
||||
|
@ -1,32 +0,0 @@
|
||||
import type { Plugin, Transformer } from "unified"
|
||||
import type { Literal, Node } from "unist"
|
||||
import { visit } from "unist-util-visit"
|
||||
import type { Highlighter } from "shiki"
|
||||
|
||||
export interface RemarkSyntaxHighlightingPluginOptions {
|
||||
highlighter: Highlighter
|
||||
}
|
||||
|
||||
export interface RemarkSyntaxHighlightingNode extends Node {
|
||||
lang: string
|
||||
meta: string
|
||||
children: undefined
|
||||
value: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const remarkSyntaxHighlightingPlugin: Plugin<
|
||||
[RemarkSyntaxHighlightingPluginOptions],
|
||||
Literal
|
||||
> = (options) => {
|
||||
const transformer: Transformer<RemarkSyntaxHighlightingNode> = (tree) => {
|
||||
visit<RemarkSyntaxHighlightingNode, string>(tree, "code", (node) => {
|
||||
node.type = "html"
|
||||
node.children = undefined
|
||||
node.value = options.highlighter.codeToHtml(node.value, {
|
||||
lang: node.lang,
|
||||
})
|
||||
})
|
||||
}
|
||||
return transformer
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
import { useI18n } from "@/i18n/i18n.client"
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
|
||||
export interface LocaleFlagProps {
|
||||
locale: string
|
||||
@ -14,17 +12,8 @@ export const LocaleFlag = (props: LocaleFlagProps): JSX.Element => {
|
||||
const i18n = useI18n(cookiesStore)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
quality={100}
|
||||
width={35}
|
||||
height={35}
|
||||
src={`/images/locales/${locale}.svg`}
|
||||
alt={locale}
|
||||
/>
|
||||
<p data-cy="locale-flag-text" className="mx-2 text-base">
|
||||
{i18n.translate(`common.${locale}`)}
|
||||
</p>
|
||||
</>
|
||||
<p data-cy="locale-flag-text" className="mx-2 text-lg font-semibold">
|
||||
{i18n.translate(`common.${locale}`)}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export const SwitchTheme = (props: SwitchThemeProps): JSX.Element => {
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"absolute top-[1px] box-border size-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out",
|
||||
"absolute top-px box-border size-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out",
|
||||
{
|
||||
"left-[27px]": theme === "dark",
|
||||
"left-0": theme === "light",
|
||||
@ -70,7 +70,7 @@ export const SwitchTheme = (props: SwitchThemeProps): JSX.Element => {
|
||||
data-cy="switch-theme-input"
|
||||
type="checkbox"
|
||||
aria-label="Dark mode toggle"
|
||||
className="absolute m-[-1px] hidden size-[1px] overflow-hidden border-0 p-0"
|
||||
className="absolute -m-px hidden size-px overflow-hidden border-0 p-0"
|
||||
defaultChecked
|
||||
/>
|
||||
</div>
|
||||
|
@ -14,8 +14,11 @@ export const Header = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black">
|
||||
<Link href="/">
|
||||
<h1 className="flex items-center justify-center">
|
||||
<h1>
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center justify-center transition-all duration-300 ease-in-out hover:scale-105"
|
||||
>
|
||||
<Image
|
||||
quality={100}
|
||||
className="size-16"
|
||||
@ -26,8 +29,8 @@ export const Header = (): JSX.Element => {
|
||||
<strong className="ml-1 hidden font-headline font-semibold text-primary dark:text-primary-dark sm:block sm:text-xl">
|
||||
Théo LUDWIG
|
||||
</strong>
|
||||
</h1>
|
||||
</Link>
|
||||
</Link>
|
||||
</h1>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col items-center justify-center px-6">
|
||||
<Link
|
||||
|
@ -20,14 +20,11 @@ export const InterestParagraph = (
|
||||
const { title, description } = props
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="my-6 text-center text-gray dark:text-gray-dark">
|
||||
<strong className="text-lg font-semibold text-primary dark:text-primary-dark">
|
||||
{title}
|
||||
</strong>
|
||||
<br />
|
||||
<span>{htmlParser(description)}</span>
|
||||
</p>
|
||||
</>
|
||||
<div className="my-6 text-center text-gray dark:text-gray-dark">
|
||||
<h3 className="text-lg font-semibold text-primary dark:text-primary-dark">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="my-2">{htmlParser(description)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ export const Interests = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="max-w-full">
|
||||
{paragraphs.map((paragraph, index) => {
|
||||
return <InterestParagraph key={index} {...paragraph} />
|
||||
{paragraphs.map((paragraph) => {
|
||||
return <InterestParagraph key={paragraph.id} {...paragraph} />
|
||||
})}
|
||||
<InterestsList />
|
||||
</div>
|
||||
|
@ -11,16 +11,18 @@ export const Repository = (props: RepositoryProps): JSX.Element => {
|
||||
const { name, description, href } = props
|
||||
|
||||
return (
|
||||
<ShadowContainer className="relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2">
|
||||
<li>
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
<div className="flex">
|
||||
<GitHubIcon className="mr-2 h-6" />
|
||||
<span className="font-semibold text-primary dark:text-primary-dark">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
<p className="my-4">{description}</p>
|
||||
<ShadowContainer className="relative !mb-4 max-h-32 cursor-pointer p-6 transition-all duration-300 ease-in-out hover:scale-[1.03]">
|
||||
<h3 className="flex">
|
||||
<GitHubIcon className="mr-2 h-6" />
|
||||
<span className="font-semibold text-primary dark:text-primary-dark">
|
||||
{name}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="my-4">{description}</p>
|
||||
</ShadowContainer>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export const OpenSource = (): JSX.Element => {
|
||||
<p className="text-center">
|
||||
{i18n.translate("home.open-source.description")}
|
||||
</p>
|
||||
<div className="my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2">
|
||||
<ul className="my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2">
|
||||
<Repository
|
||||
name="nodejs/node"
|
||||
description="Node.js JavaScript runtime ✨🐢🚀✨"
|
||||
@ -31,7 +31,7 @@ export const OpenSource = (): JSX.Element => {
|
||||
description="The React Framework"
|
||||
href="https://github.com/vercel/next.js/commits?author=theoludwig"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
|
||||
const { title, description, link, image } = props
|
||||
|
||||
return (
|
||||
<ShadowContainer className="relative cursor-pointer items-center sm:ml-10">
|
||||
<li>
|
||||
<a
|
||||
className="group inline-flex justify-center"
|
||||
target="_blank"
|
||||
@ -21,23 +21,25 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
|
||||
href={link}
|
||||
aria-label={title}
|
||||
>
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
className="size-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5"
|
||||
width={300}
|
||||
height={300}
|
||||
src={image}
|
||||
alt={title}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100">
|
||||
<h3 className="my-6 text-2xl font-semibold text-primary dark:text-primary-dark">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mx-4 my-6 font-semibold">{description}</p>
|
||||
</div>
|
||||
<ShadowContainer className="relative cursor-pointer items-center sm:ml-10">
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
className="size-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5"
|
||||
width={300}
|
||||
height={300}
|
||||
src={image}
|
||||
alt={title}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100">
|
||||
<h3 className="my-6 text-2xl font-semibold text-primary dark:text-primary-dark">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mx-4 my-6 font-semibold">{description}</p>
|
||||
</div>
|
||||
</ShadowContainer>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ export const Portfolio = (): JSX.Element => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-wrap justify-center px-3">
|
||||
{items.map((item, index) => {
|
||||
return <PortfolioItem key={index} {...item} />
|
||||
<ul className="flex w-full flex-wrap justify-center px-3">
|
||||
{items.map((item) => {
|
||||
return <PortfolioItem key={item.title} {...item} />
|
||||
})}
|
||||
</div>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
@ -30,14 +30,17 @@ export const ProfileList = (props: ProfileListProps): JSX.Element => {
|
||||
title={i18n.translate("home.about.pronouns")}
|
||||
value={i18n.translate("home.about.pronouns-value")}
|
||||
/>
|
||||
{isMounted ? (
|
||||
<ProfileItem
|
||||
title={i18n.translate("home.about.birth-date")}
|
||||
value={`${BIRTH_DATE_STRING} (${age} ${i18n.translate(
|
||||
"home.about.years-old",
|
||||
)})`}
|
||||
/>
|
||||
) : null}
|
||||
<ProfileItem
|
||||
title={i18n.translate("home.about.birth-date")}
|
||||
value={
|
||||
isMounted
|
||||
? `${BIRTH_DATE_STRING} (${age} ${i18n.translate(
|
||||
"home.about.years-old",
|
||||
)})`
|
||||
: BIRTH_DATE_STRING
|
||||
}
|
||||
/>
|
||||
|
||||
<ProfileItem
|
||||
title={i18n.translate("home.about.nationality")}
|
||||
value="Alsace, France"
|
||||
|
@ -13,7 +13,7 @@ export const SocialMediaItem = (props: SocialMediaItemProps): JSX.Element => {
|
||||
aria-label={ariaLabel}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="relative inline-block bg-transparent"
|
||||
className="relative inline-block bg-transparent transition-all duration-300 ease-in-out hover:scale-110"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
|
@ -27,13 +27,13 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={skillProperties.link}
|
||||
className="mx-2 max-w-xl text-primary hover:underline dark:text-primary-dark"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="text-center">
|
||||
<li>
|
||||
<a
|
||||
href={skillProperties.link}
|
||||
className="mx-2 flex max-w-xl flex-col items-center justify-center text-center text-primary hover:underline dark:text-primary-dark"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="inline size-16"
|
||||
quality={100}
|
||||
@ -43,7 +43,7 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
|
||||
src={getImage()}
|
||||
/>
|
||||
<p className="mt-1 font-semibold">{skill}</p>
|
||||
</div>
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const SkillsSection = (props: SkillsSectionProps): JSX.Element => {
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-around">{children}</div>
|
||||
<ul className="flex flex-wrap justify-around">{children}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,8 +57,8 @@ export const Section = (props: SectionProps): JSX.Element => {
|
||||
</p>
|
||||
) : null}
|
||||
<div className="w-full px-3">
|
||||
<ShadowContainer>
|
||||
<div className="w-full px-16 py-4 leading-8">{children}</div>
|
||||
<ShadowContainer className="w-full px-2 py-4 leading-8 sm:px-16">
|
||||
{children}
|
||||
</ShadowContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
380
curriculum-vitae/package-lock.json
generated
380
curriculum-vitae/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,9 +13,9 @@
|
||||
"modern-normalize": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.7",
|
||||
"date-and-time": "3.1.1",
|
||||
"vite": "5.2.8",
|
||||
"@types/node": "20.12.12",
|
||||
"date-and-time": "3.3.0",
|
||||
"vite": "5.2.11",
|
||||
"vite-plugin-html": "3.2.2"
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ describe("Page /blog/[slug]", () => {
|
||||
cy.visit("/blog/hello-world")
|
||||
cy.get("[data-cy=locale-flag-text]").should("not.exist")
|
||||
cy.get("main h1").should("have.text", "👋 Hello, world!")
|
||||
cy.get(".prose a:visible").should("have.attr", "target", "_blank")
|
||||
})
|
||||
|
||||
it("should redirect to /404 if the blog post doesn't exist", () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"en-US": "English",
|
||||
"fr-FR": "French",
|
||||
"en-US": "🇺🇸 English",
|
||||
"fr-FR": "🇫🇷 French",
|
||||
"all-rights-reserved": "All rights reserved",
|
||||
"home": "Home"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"en-US": "Anglais",
|
||||
"fr-FR": "Français",
|
||||
"en-US": "🇺🇸 Anglais",
|
||||
"fr-FR": "🇫🇷 Français",
|
||||
"all-rights-reserved": "Tous droits réservés",
|
||||
"home": "Accueil"
|
||||
}
|
||||
|
5951
package-lock.json
generated
5951
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
74
package.json
74
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "theoludwig",
|
||||
"version": "3.2.4",
|
||||
"version": "3.3.1",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,7 +13,8 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"start": "next start",
|
||||
"build": "npm run curriculum-vitae:build && next build",
|
||||
"build": "npm run build:curriculum-vitae && next build",
|
||||
"build:curriculum-vitae": "node ./curriculum-vitae/build.js",
|
||||
"lint:commit": "commitlint",
|
||||
"lint:editorconfig": "editorconfig-checker",
|
||||
"lint:markdown": "markdownlint-cli2",
|
||||
@ -24,77 +25,72 @@
|
||||
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"html-w3c-validator\"",
|
||||
"test:e2e": "start-server-and-test \"start\" http://127.0.0.1:3000 \"cypress run\"",
|
||||
"test:dev": "start-server-and-test \"dev\" \"http://127.0.0.1:3000\" \"cypress open\"",
|
||||
"curriculum-vitae:build": "node ./curriculum-vitae/build.js",
|
||||
"release": "semantic-release",
|
||||
"postinstall": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/montserrat": "5.0.17",
|
||||
"@fontsource/montserrat": "5.0.18",
|
||||
"@formatjs/intl-localematcher": "0.5.4",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@giscus/react": "3.0.0",
|
||||
"clsx": "2.1.0",
|
||||
"date-and-time": "3.1.1",
|
||||
"@shikijs/rehype": "1.6.0",
|
||||
"clsx": "2.1.1",
|
||||
"date-and-time": "3.3.0",
|
||||
"gray-matter": "4.0.3",
|
||||
"html-react-parser": "5.1.10",
|
||||
"i18n-js": "4.3.2",
|
||||
"i18n-js": "4.4.3",
|
||||
"katex": "0.16.10",
|
||||
"negotiator": "0.6.3",
|
||||
"next": "14.1.0",
|
||||
"next-mdx-remote": "4.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"next-mdx-remote": "5.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"read-pkg": "9.0.1",
|
||||
"rehype-katex": "6.0.3",
|
||||
"rehype-raw": "6.1.1",
|
||||
"rehype-slug": "5.1.0",
|
||||
"remark-gfm": "3.0.1",
|
||||
"remark-math": "5.1.1",
|
||||
"sharp": "0.33.3",
|
||||
"shiki": "0.14.7",
|
||||
"unified": "10.1.2",
|
||||
"unist-util-visit": "5.0.0",
|
||||
"rehype-katex": "7.0.0",
|
||||
"rehype-raw": "7.0.0",
|
||||
"rehype-slug": "6.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"remark-math": "6.0.0",
|
||||
"sharp": "0.33.4",
|
||||
"shiki": "1.6.0",
|
||||
"universal-cookie": "7.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "19.1.0",
|
||||
"@commitlint/config-conventional": "19.1.0",
|
||||
"@commitlint/cli": "19.2.2",
|
||||
"@commitlint/config-conventional": "19.2.2",
|
||||
"@saithodev/semantic-release-backmerge": "4.0.1",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@tailwindcss/typography": "0.5.12",
|
||||
"@tailwindcss/typography": "0.5.13",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@tsconfig/strictest": "2.0.5",
|
||||
"@types/negotiator": "0.6.3",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/react": "18.2.78",
|
||||
"@types/unist": "3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "7.6.0",
|
||||
"@typescript-eslint/parser": "7.6.0",
|
||||
"@types/node": "20.12.12",
|
||||
"@types/react": "18.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||
"@typescript-eslint/parser": "7.10.0",
|
||||
"autoprefixer": "10.4.19",
|
||||
"curriculum-vitae": "file:./curriculum-vitae",
|
||||
"cypress": "13.7.3",
|
||||
"cypress": "13.10.0",
|
||||
"editorconfig-checker": "5.1.5",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-conventions": "14.1.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-conventions": "14.2.0",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-tailwindcss": "3.14.2",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
"html-w3c-validator": "1.6.1",
|
||||
"eslint-plugin-tailwindcss": "3.17.0",
|
||||
"eslint-plugin-unicorn": "53.0.0",
|
||||
"html-w3c-validator": "1.6.2",
|
||||
"husky": "9.0.11",
|
||||
"lint-staged": "15.2.2",
|
||||
"lint-staged": "15.2.4",
|
||||
"markdownlint-cli2": "0.13.0",
|
||||
"markdownlint-rule-relative-links": "2.3.2",
|
||||
"postcss": "8.4.38",
|
||||
"prettier": "3.2.5",
|
||||
"prettier-plugin-tailwindcss": "0.5.13",
|
||||
"semantic-release": "23.0.8",
|
||||
"prettier-plugin-tailwindcss": "0.5.14",
|
||||
"semantic-release": "23.1.1",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"tailwindcss": "3.4.3",
|
||||
"typescript": "5.4.5"
|
||||
|
Reference in New Issue
Block a user