mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
Compare commits
25 Commits
v3.2.3
...
dd09092842
Author | SHA1 | Date | |
---|---|---|---|
dd09092842
|
|||
f64acb68c7
|
|||
3074945c54
|
|||
fc0dfdda5f
|
|||
f62964c62a
|
|||
8ec113c9cb
|
|||
8a59e9034f
|
|||
d7121ea833
|
|||
c10f690622
|
|||
6915072ab9
|
|||
dd803bcc51
|
|||
efa33f26ec
|
|||
5f3dfad988
|
|||
b231381cb3
|
|||
bbb2e56512
|
|||
66cf6d7438
|
|||
2a635bf3ba
|
|||
9f79b88202
|
|||
23d9caf578
|
|||
7febe6d1f9
|
|||
c4650c34d9
|
|||
0eb780485c
|
|||
cd5e92b64a
|
|||
982b148329
|
|||
0febee5b51
|
@ -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>
|
||||
|
@ -1,9 +1,9 @@
|
||||
FROM node:20.11.1 AS builder-dependencies
|
||||
FROM node:20.12.2 AS builder-dependencies
|
||||
WORKDIR /usr/src/application
|
||||
COPY ./package*.json ./
|
||||
RUN npm clean-install
|
||||
|
||||
FROM node:20.11.1 AS builder
|
||||
FROM node:20.12.2 AS builder
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV IS_STANDALONE=true
|
||||
WORKDIR /usr/src/application
|
||||
@ -11,7 +11,7 @@ COPY --from=builder-dependencies /usr/src/application/node_modules ./node_module
|
||||
COPY ./ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20.11.1-slim AS runner
|
||||
FROM node:20.12.2-slim AS runner
|
||||
ENV NODE_ENV=production
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
@ -25,7 +25,7 @@ const BlogPage = async (): Promise<JSX.Element> => {
|
||||
return (
|
||||
<main className="flex flex-1 flex-col flex-wrap items-center">
|
||||
<div className="mt-10 flex flex-col items-center">
|
||||
<h1 className="text-4xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
<h1 className="text-4xl font-semibold text-primary dark:text-primary-dark">
|
||||
Blog
|
||||
</h1>
|
||||
<p className="mt-6 text-center" data-cy="blog-post-date">
|
||||
|
@ -18,7 +18,7 @@ const ErrorHandling = (props: ErrorHandlingProps): JSX.Element => {
|
||||
<h1 className="my-6 text-4xl font-semibold">
|
||||
Error{" "}
|
||||
<span
|
||||
className="text-yellow dark:text-yellow-dark"
|
||||
className="text-primary dark:text-primary-dark"
|
||||
data-cy="status-code"
|
||||
>
|
||||
500
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
.prose a,
|
||||
.prose strong {
|
||||
@apply !font-semibold text-yellow dark:text-yellow-dark;
|
||||
@apply !font-semibold text-primary dark:text-primary-dark;
|
||||
}
|
||||
|
||||
strong,
|
||||
@ -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({
|
||||
className={classNames(
|
||||
{
|
||||
dark: theme === "dark",
|
||||
light: theme === "light",
|
||||
})}
|
||||
},
|
||||
"scroll-smooth",
|
||||
)}
|
||||
style={{
|
||||
colorScheme: theme,
|
||||
}}
|
||||
|
@ -10,7 +10,7 @@ const NotFound = (): JSX.Element => {
|
||||
<h1 className="my-6 text-4xl font-semibold">
|
||||
{i18n.translate("errors.error")}{" "}
|
||||
<span
|
||||
className="text-yellow dark:text-yellow-dark"
|
||||
className="text-primary dark:text-primary-dark"
|
||||
data-cy="status-code"
|
||||
>
|
||||
404
|
||||
@ -20,7 +20,7 @@ const NotFound = (): JSX.Element => {
|
||||
{i18n.translate("errors.not-found")}{" "}
|
||||
<Link
|
||||
href="/"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
className="text-primary hover:underline dark:text-primary-dark"
|
||||
>
|
||||
{i18n.translate("errors.return-to-home-page")}
|
||||
</Link>
|
||||
|
@ -21,7 +21,7 @@ export const BlogPost = async (props: BlogPostProps): Promise<JSX.Element> => {
|
||||
return (
|
||||
<main className="break-wrap-words flex flex-1 flex-col flex-wrap items-center justify-center">
|
||||
<div className="my-10 flex flex-col items-center text-center">
|
||||
<h1 className="text-3xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
<h1 className="text-3xl font-semibold text-primary dark:text-primary-dark">
|
||||
{blogPost.frontmatter.title}
|
||||
</h1>
|
||||
<p className="mt-2" data-cy="blog-post-date">
|
||||
|
@ -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} />
|
||||
</Link>
|
||||
<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>
|
||||
</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,23 +9,19 @@ 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">
|
||||
<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-yellow dark:text-yellow-dark"
|
||||
className="text-xl font-semibold text-primary dark:text-primary-dark"
|
||||
>
|
||||
{post.frontmatter.title}
|
||||
</h2>
|
||||
@ -37,9 +33,10 @@ export const BlogPosts = async (): Promise<JSX.Element> => {
|
||||
</p>
|
||||
</ShadowContainer>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ It depends of many factors, and is somewhat relative to each one of us. The **pe
|
||||
|
||||
## Why is it so important?
|
||||
|
||||
Code like that works great, but it is not enough, even if the code will be read by the computer and understood by the machine, we should not forget that the code is **written by human** and will be also **read by human** not only a machine.
|
||||
Code that works is great, but not enough, even if the code will be read and understood by the computer, we should not forget that the code is **written by human** and will be also **read by human** not only a machine.
|
||||
|
||||
For example the [Linux kernel](https://www.kernel.org/), is one of the biggest open source project with many contributors worldwide. Last data shows that it is about **20 millions** lines of code.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -15,7 +15,7 @@ We don't want to "reinvent the wheel" and rewrite everything from scratch for ea
|
||||
|
||||
However, it is important to draw a line between what dependencies are worth the cost and which are not.
|
||||
|
||||
Most likely adding a [JavaScript npm package `is-odd`](https://www.npmjs.com/package/is-odd) to check if a number is odd or even for example, is not worth it. Writing it ourselves is easier and allows a better maintenance in the long term.
|
||||
Most likely adding a [JavaScript npm package `is-odd`](https://www.npmjs.com/package/is-odd) to check if a number is odd or even for example, is not worth it. Writing it ourselves allows a better maintenance in the long term.
|
||||
|
||||
Learning **how to solve problems** and how to write efficient code is very important and also a very broad and complicated topic, so this blog post will only be an **introduction to the subject**, and will not go in depth.
|
||||
|
||||
@ -286,7 +286,7 @@ Contiguous subarray is any sub series of elements in a given array that are cont
|
||||
|
||||
**Explanation:** The subarray with the largest sum is `[2, 4, -3, 5, 2]` which has a sum of `10`.
|
||||
|
||||
### Worst solution: Brute force
|
||||
### Worst solution: Brute force ($O(n^3)$)
|
||||
|
||||
```python
|
||||
def maximum_subarray_sum_cubic(array: list[int]) -> int:
|
||||
@ -309,7 +309,7 @@ def maximum_subarray_sum_cubic(array: list[int]) -> int:
|
||||
return best_sum
|
||||
```
|
||||
|
||||
### Better solution: Linear time
|
||||
### Better solution: Linear time ($O(n)$)
|
||||
|
||||
```python
|
||||
def maximum_subarray_sum_linear(array: list[int]) -> int:
|
||||
|
@ -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
|
||||
}
|
@ -9,7 +9,7 @@ export const FooterText = (): JSX.Element => {
|
||||
<p>
|
||||
<Link
|
||||
href="/"
|
||||
className="font-semibold text-yellow hover:underline dark:text-yellow-dark"
|
||||
className="font-semibold text-primary hover:underline dark:text-primary-dark"
|
||||
>
|
||||
Théo LUDWIG
|
||||
</Link>{" "}
|
||||
|
@ -16,7 +16,7 @@ export const FooterVersion = (props: FooterVersionProps): JSX.Element => {
|
||||
Version{" "}
|
||||
<a
|
||||
data-cy="version-link"
|
||||
className="font-semibold text-yellow hover:underline dark:text-yellow-dark"
|
||||
className="font-semibold text-primary hover:underline dark:text-primary-dark"
|
||||
href={versionLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { getVersion } from "@/utils/getVersion"
|
||||
|
||||
import { FooterText } from "./FooterText"
|
||||
import { FooterVersion } from "./FooterVersion"
|
||||
|
||||
export const Footer = async (): Promise<JSX.Element> => {
|
||||
const { readPackage } = await import("read-pkg")
|
||||
const { version } = await readPackage()
|
||||
const version = await getVersion()
|
||||
|
||||
return (
|
||||
<footer className="flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black">
|
||||
|
@ -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">
|
||||
<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"
|
||||
@ -23,17 +26,17 @@ export const Header = (): JSX.Element => {
|
||||
alt="Théo LUDWIG"
|
||||
priority
|
||||
/>
|
||||
<strong className="ml-1 hidden font-headline font-semibold text-yellow dark:text-yellow-dark sm:block sm:text-xl">
|
||||
<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>
|
||||
</h1>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col items-center justify-center px-6">
|
||||
<Link
|
||||
href="/blog"
|
||||
data-cy="header-blog-link"
|
||||
className="font-semibold text-yellow hover:underline dark:text-yellow-dark"
|
||||
className="font-semibold text-primary hover:underline dark:text-primary-dark"
|
||||
>
|
||||
Blog
|
||||
</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-yellow dark:text-yellow-dark">
|
||||
<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}
|
||||
</strong>
|
||||
<br />
|
||||
<span>{htmlParser(description)}</span>
|
||||
</p>
|
||||
</>
|
||||
</h3>
|
||||
<p className="my-2">{htmlParser(description)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export const InterestItem = (props: InterestItemProps): JSX.Element => {
|
||||
return (
|
||||
<li className="m-2 size-8" title={title}>
|
||||
<FontAwesomeIcon
|
||||
className="block size-full text-yellow dark:text-yellow-dark"
|
||||
className="block size-full text-primary dark:text-primary-dark"
|
||||
icon={fontAwesomeIcon}
|
||||
/>
|
||||
</li>
|
||||
|
@ -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">
|
||||
<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-yellow dark:text-yellow-dark">
|
||||
<span className="font-semibold text-primary dark:text-primary-dark">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
</h3>
|
||||
<p className="my-4">{description}</p>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
</a>
|
||||
</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,6 +21,7 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
|
||||
href={link}
|
||||
aria-label={title}
|
||||
>
|
||||
<ShadowContainer className="relative cursor-pointer items-center sm:ml-10">
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
@ -32,12 +33,13 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
|
||||
/>
|
||||
</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-yellow dark:text-yellow-dark">
|
||||
<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>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
</a>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export const ProfileDescriptionBottom = (): JSX.Element => {
|
||||
<br />
|
||||
<a
|
||||
href="/curriculum-vitae/index.html"
|
||||
className="font-semibold text-yellow hover:underline dark:text-yellow-dark"
|
||||
className="font-semibold text-primary hover:underline dark:text-primary-dark"
|
||||
>
|
||||
Curriculum vitæ ({i18n.translate("common.fr-FR")})
|
||||
</a>
|
||||
|
@ -5,7 +5,7 @@ export const ProfileInformation = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="mb-6 border-b-2 border-gray-600 pb-2 font-headline dark:border-gray-400">
|
||||
<h1 className="mb-2 text-4xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
<h1 className="mb-2 text-4xl font-semibold text-primary dark:text-primary-dark">
|
||||
Théo LUDWIG
|
||||
</h1>
|
||||
<h2 className="mb-3 text-base">
|
||||
|
@ -5,6 +5,7 @@ import { useMemo } from "react"
|
||||
import { useI18n } from "@/i18n/i18n.client"
|
||||
import { BIRTH_DATE, BIRTH_DATE_STRING, getAge } from "@/utils/getAge"
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
import { useIsMounted } from "@/hooks/useIsMounted"
|
||||
|
||||
import { ProfileItem } from "./ProfileItem"
|
||||
|
||||
@ -21,6 +22,8 @@ export const ProfileList = (props: ProfileListProps): JSX.Element => {
|
||||
return getAge(BIRTH_DATE)
|
||||
}, [])
|
||||
|
||||
const { isMounted } = useIsMounted()
|
||||
|
||||
return (
|
||||
<ul className="m-0 list-none p-0">
|
||||
<ProfileItem
|
||||
@ -29,10 +32,15 @@ export const ProfileList = (props: ProfileListProps): JSX.Element => {
|
||||
/>
|
||||
<ProfileItem
|
||||
title={i18n.translate("home.about.birth-date")}
|
||||
value={`${BIRTH_DATE_STRING} (${age} ${i18n.translate(
|
||||
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 (
|
||||
<li>
|
||||
<a
|
||||
href={skillProperties.link}
|
||||
className="mx-2 max-w-xl text-yellow hover:underline dark:text-yellow-dark"
|
||||
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"
|
||||
>
|
||||
<div className="text-center">
|
||||
<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>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ export const SkillsSection = (props: SkillsSectionProps): JSX.Element => {
|
||||
<div className="flex flex-wrap px-4 py-6">
|
||||
<div className="flex-1">
|
||||
<div className="mb-8 border-b border-gray-600 dark:border-white/10">
|
||||
<h3 className="my-3 text-xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
<h3 className="my-3 text-xl font-semibold text-primary dark:text-primary-dark">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-around">{children}</div>
|
||||
<ul className="flex flex-wrap justify-around">{children}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@ export const Loader = (props: LoaderProps): JSX.Element => {
|
||||
height,
|
||||
}}
|
||||
className={classNames(
|
||||
"inline-block animate-spin rounded-full border-[3px] border-current border-t-transparent text-yellow dark:text-yellow-dark",
|
||||
"inline-block animate-spin rounded-full border-[3px] border-current border-t-transparent text-primary dark:text-primary-dark",
|
||||
className,
|
||||
)}
|
||||
role="status"
|
||||
|
@ -6,7 +6,7 @@ export const SectionHeading = (props: SectionHeadingProps): JSX.Element => {
|
||||
return (
|
||||
<h2
|
||||
{...rest}
|
||||
className="mb-3 mt-1 text-center text-4xl font-semibold text-yellow dark:text-yellow-dark"
|
||||
className="mb-3 mt-1 text-center text-4xl font-semibold text-primary dark:text-primary-dark"
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
|
@ -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>
|
||||
|
646
curriculum-vitae/package-lock.json
generated
646
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.11.17",
|
||||
"date-and-time": "3.1.1",
|
||||
"vite": "5.1.2",
|
||||
"@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", () => {
|
||||
|
15
hooks/useIsMounted.ts
Normal file
15
hooks/useIsMounted.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export interface UseIsMountedResult {
|
||||
isMounted: boolean
|
||||
}
|
||||
|
||||
export const useIsMounted = (): UseIsMountedResult => {
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true)
|
||||
}, [])
|
||||
|
||||
return { isMounted }
|
||||
}
|
@ -25,6 +25,6 @@ const translations = {
|
||||
|
||||
export const i18n = new I18n(translations, {
|
||||
defaultLocale: DEFAULT_LOCALE,
|
||||
availableLocales: LOCALES.slice(),
|
||||
availableLocales: [...LOCALES],
|
||||
enableFallback: true,
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"en-US": "English",
|
||||
"fr-FR": "French",
|
||||
"en-US": "🇺🇸 English",
|
||||
"fr-FR": "🇫🇷 French",
|
||||
"all-rights-reserved": "All rights reserved",
|
||||
"home": "Home"
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Open-Source Enthusiast",
|
||||
"description": "I value the <strong>sharing of knowledge and collaboration</strong> to collectively resolve problems. <br /> The source code of the website is available on <a class='text-yellow dark:text-yellow-dark hover:underline font-semibold' href='https://github.com/theoludwig/theoludwig' target='_blank' rel='noopener noreferrer'>GitHub</a>.",
|
||||
"description": "I value the <strong>sharing of knowledge and collaboration</strong> to collectively resolve problems. <br /> The source code of the website is available on <a class='text-primary dark:text-primary-dark hover:underline font-semibold' href='https://github.com/theoludwig/theoludwig' target='_blank' rel='noopener noreferrer'>GitHub</a>.",
|
||||
"id": "open-source"
|
||||
}
|
||||
]
|
||||
@ -34,7 +34,7 @@
|
||||
{
|
||||
"title": "Carolo",
|
||||
"description": "Strategy board game similar to chess which allows grandiose moves (only available in French).",
|
||||
"link": "https://carolo.org/",
|
||||
"link": "https://carolo.theoludwig.fr/",
|
||||
"image": "/images/portfolio/Carolo.png"
|
||||
},
|
||||
{
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Enthousiaste de l'Open-Source",
|
||||
"description": "J'apprécie le <strong>partage des connaissances et la collaboration</strong> pour résoudre des défis collectivement. <br /> Le code source du site est accessible sur <a class='text-yellow dark:text-yellow-dark hover:underline font-semibold' href='https://github.com/theoludwig/theoludwig' target='_blank' rel='noopener noreferrer'>GitHub</a>.",
|
||||
"description": "J'apprécie le <strong>partage des connaissances et la collaboration</strong> pour résoudre des défis collectivement. <br /> Le code source du site est accessible sur <a class='text-primary dark:text-primary-dark hover:underline font-semibold' href='https://github.com/theoludwig/theoludwig' target='_blank' rel='noopener noreferrer'>GitHub</a>.",
|
||||
"id": "open-source"
|
||||
}
|
||||
]
|
||||
@ -34,7 +34,7 @@
|
||||
{
|
||||
"title": "Carolo",
|
||||
"description": "Jeu de plateau stratégique similaire aux échecs qui permet des coups grandioses, reposant sur des enchaînements remarquables.",
|
||||
"link": "https://carolo.org/",
|
||||
"link": "https://carolo.theoludwig.fr/",
|
||||
"image": "/images/portfolio/Carolo.png"
|
||||
},
|
||||
{
|
||||
|
10649
package-lock.json
generated
10649
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
104
package.json
104
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "theoludwig",
|
||||
"version": "3.2.3",
|
||||
"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,79 +25,74 @@
|
||||
"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.16",
|
||||
"@fontsource/montserrat": "5.0.18",
|
||||
"@formatjs/intl-localematcher": "0.5.4",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@giscus/react": "2.4.0",
|
||||
"clsx": "2.1.0",
|
||||
"date-and-time": "3.1.1",
|
||||
"@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.2",
|
||||
"@giscus/react": "3.0.0",
|
||||
"@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.5",
|
||||
"i18n-js": "4.3.2",
|
||||
"katex": "0.16.9",
|
||||
"html-react-parser": "5.1.10",
|
||||
"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.2",
|
||||
"shiki": "0.14.7",
|
||||
"unified": "10.1.2",
|
||||
"unist-util-visit": "5.0.0",
|
||||
"universal-cookie": "7.0.2"
|
||||
"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": "18.6.1",
|
||||
"@commitlint/config-conventional": "18.6.2",
|
||||
"@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.10",
|
||||
"@tailwindcss/typography": "0.5.13",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@tsconfig/strictest": "2.0.3",
|
||||
"@tsconfig/strictest": "2.0.5",
|
||||
"@types/negotiator": "0.6.3",
|
||||
"@types/node": "20.11.17",
|
||||
"@types/react": "18.2.55",
|
||||
"@types/unist": "3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "7.0.1",
|
||||
"@typescript-eslint/parser": "7.0.1",
|
||||
"autoprefixer": "10.4.17",
|
||||
"@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.6.4",
|
||||
"editorconfig-checker": "5.1.4",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-conventions": "14.0.0",
|
||||
"cypress": "13.10.0",
|
||||
"editorconfig-checker": "5.1.5",
|
||||
"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",
|
||||
"markdownlint-cli2": "0.12.1",
|
||||
"markdownlint-rule-relative-links": "2.3.1",
|
||||
"postcss": "8.4.35",
|
||||
"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.11",
|
||||
"semantic-release": "23.0.2",
|
||||
"prettier-plugin-tailwindcss": "0.5.14",
|
||||
"semantic-release": "23.1.1",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"tailwindcss": "3.4.1",
|
||||
"typescript": "5.3.3"
|
||||
"tailwindcss": "3.4.3",
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const tailwindConfig = {
|
||||
DEFAULT: "#333333",
|
||||
dark: "#b7c0c9",
|
||||
},
|
||||
yellow: {
|
||||
primary: {
|
||||
DEFAULT: "#006cff",
|
||||
dark: "#00aeff",
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const BIRTH_DATE_DAY = "31" as const
|
||||
export const BIRTH_DATE_MONTH = "03" as const
|
||||
export const BIRTH_DATE_YEAR = "2003" as const
|
||||
export const BIRTH_DATE_DAY = "31"
|
||||
export const BIRTH_DATE_MONTH = "03"
|
||||
export const BIRTH_DATE_YEAR = "2003"
|
||||
export const BIRTH_DATE_STRING =
|
||||
`${BIRTH_DATE_DAY}/${BIRTH_DATE_MONTH}/${BIRTH_DATE_YEAR}` as const
|
||||
export const BIRTH_DATE_ISO_8601 =
|
||||
|
8
utils/getVersion.ts
Normal file
8
utils/getVersion.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export const getVersion = async (): Promise<string> => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return "0.0.0-development"
|
||||
}
|
||||
const { readPackage } = await import("read-pkg")
|
||||
const { version } = await readPackage()
|
||||
return version
|
||||
}
|
Reference in New Issue
Block a user