mirror of
https://github.com/mastermindzh/rickvanlieshout.com
synced 2025-07-18 00:13:40 +02:00
commit
31597d25a6
@ -1,4 +0,0 @@
|
||||
*.*
|
||||
!*.ts
|
||||
!*.tsx
|
||||
/public/
|
53
.eslintrc
53
.eslintrc
@ -1,53 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
|
||||
"extends": [
|
||||
"plugin:import/typescript",
|
||||
"plugin:import/errors",
|
||||
"airbnb-typescript",
|
||||
"@mastermindzh/eslint-config",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"jest",
|
||||
"@typescript-eslint",
|
||||
"simple-import-sort",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/internal/**/*.ts"
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": ["off"],
|
||||
"@typescript-eslint/no-use-before-define": ["off"],
|
||||
"@typescript-eslint/quotes": ["error", "double"],
|
||||
"@typescript-eslint/naming-convention": ["error", {
|
||||
"format": ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],
|
||||
"leadingUnderscore": "allow",
|
||||
"selector": "parameter"
|
||||
}],
|
||||
"react/static-property-placement": ["off"],
|
||||
"react/prop-types": ["off"],
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
}
|
||||
}
|
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@ -26,8 +26,3 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
# - name: Release
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# run: npm run semantic-release
|
||||
|
@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
...require("@mastermindzh/prettier-config")
|
||||
...require("@mastermindzh/prettier-config"),
|
||||
trailingComma: "all",
|
||||
};
|
||||
|
22
.prettierrc.json
Normal file
22
.prettierrc.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf",
|
||||
"semi": true,
|
||||
"singleAttributePerLine": false,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"experimentalTernaries": false
|
||||
}
|
@ -9,13 +9,16 @@
|
||||
"font-weight-notation": "named-where-possible",
|
||||
"function-url-no-scheme-relative": true,
|
||||
"function-url-quotes": "always",
|
||||
"max-empty-lines": 1,
|
||||
"no-descending-specificity": true,
|
||||
"no-duplicate-selectors": true,
|
||||
"order/order": ["custom-properties", "declarations"],
|
||||
"order/order": [
|
||||
[
|
||||
"custom-properties",
|
||||
"declarations"
|
||||
]
|
||||
],
|
||||
"order/properties-alphabetical-order": true,
|
||||
"property-no-unknown": [true, { "ignoreProperties": ["/^lost-/"] }],
|
||||
"string-quotes": "double",
|
||||
"value-keyword-case": "lower"
|
||||
}
|
||||
}
|
||||
|
954
CHANGELOG.md
954
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -66,4 +66,4 @@
|
||||
"phone": "+31614436562"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
content/pages/resume/DDD-vijfhart.pdf
Normal file
BIN
content/pages/resume/DDD-vijfhart.pdf
Normal file
Binary file not shown.
@ -18,18 +18,26 @@ template: "page"
|
||||
|
||||
**Open-source aficionado**<br />
|
||||
_Always_<br />
|
||||
A fair share of my private work is done through open-source media. I don't have a portfolio but I have a [Github](https://github.com/mastermindzh) where I share most things.
|
||||
A fair share of my private work is done through open-source media. I don't have a portfolio but I have a [Github](https://github.com/mastermindzh) where I share most things. If you're interested in what else I do, or why, let me know and I'll share a detailed description.
|
||||
|
||||
**CTO at Frontliners (previously INFORIT)**<br />
|
||||
_2018 - Now_<br />
|
||||
At Frontliners I am responsible for the entire technical architecture of the new TFX stack that I launched shortly after 2018. This involves setting up and maintaining a modern event-based microservice architecture on a Kubernetes-based SaaS cloud solution and migrating to modern programming languages such as dotnetcore and React.
|
||||
At Frontliners I am responsible for the entire technical architecture of the new TFX stack that I launched shortly after 2018. This involves setting up and maintaining a modern event-based microservice architecture on a Kubernetes-based SaaS cloud solution and migrating to modern programming languages such as dotnetcore, Supabase and React. We've delivered a few smaller pieces of software but mainly focus on building a next-gen Transport Management System called TFX.
|
||||
|
||||
The other main responsibility I have is creating and maintaining a coherent team of lead developers by sharing knowledge, making decisions, and empowering leads to explore technologies outside of their comfort zone.
|
||||
The other main responsibility I have is creating and maintaining, and hiring, a coherent team of (lead) engineers by sharing knowledge, making decisions, and empowering them to explore technologies outside of their comfort zone.
|
||||
This includes everything from platform to e2e engineering.
|
||||
During my time at Frontliners, the company has grown from 5-7 in-house developers on a single product to 35-40 technical engineers on multiple projects.
|
||||
|
||||
At Frontliners I also help with the recruitment of new employees, setting up and maintaining agile strategies including proper test management, and general software development.
|
||||
During my time at Frontliners, we've grown from 5-7 in-house developers to 10, full-sized, SCRUM teams.
|
||||
The teams I manage will be listed below. Some of the core concepts they work with will also be listed.
|
||||
Apart from the last one I also play an active role in them:
|
||||
|
||||
Most relevant technologies used: dotnetcore, React, Angular, Kafka, RabbitMQ, Mongo, PostgreSQL, Growthbook, Docker & Kubernetes
|
||||
- 4 Full-fledged (6-8 people) Scrum development teams working on the latest product.
|
||||
- day-to-day management of people done by "Chapter leads"
|
||||
- React, Docker, .NET 9+ (core), Mongo, Kafka, Debezium, DDD, CQRS
|
||||
- Business Integration team (Supabase, Kysely, PostgreSQL)
|
||||
- Platform team (Kubernetes, Terraform, ArgoCD, CI/CD pipelines, Vault, Keycloak, etc...)
|
||||
- Legacy team (ASP.NET, MSSQL, MongoDB, Elasticsearch)
|
||||
- Legacy team (Clarion, SQL-Sybase)
|
||||
|
||||
**Hanflex employee**<br />
|
||||
_September 2015 - July 2018_<br />
|
||||
@ -64,6 +72,7 @@ Building an interactive and fully customizable dashboard on top of VAA's existin
|
||||
I'll list some of the training courses I've had whilst working.
|
||||
Some of these have (official) certificates, some don't, but all have brought value and knowledge.
|
||||
|
||||
- Domain Driven Design [Vijfhart](./DDD-vijfhart.pdf)
|
||||
- Young Executive Program (YEP) - [De Baak](https://debaak.nl/trainingen/young-executives-program)
|
||||
- Hiring & interview training ([icm.nl](https://www.icm.nl/opleidingen-en-trainingen/hrm/selectiegesprekken-voeren/))
|
||||
- Conversation techniques & de-escalation ([TIJDwinst.com](https://gesprekstechnieken.com/cursus-gesprekstechnieken/)](<https://gesprekstechnieken.com/cursus-gesprekstechnieken/>)).
|
||||
|
@ -102,7 +102,7 @@ First, let's add a script to the `package.json` that will do our commit for us:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"commit": "git-cz",
|
||||
"commit": "git-cz"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -138,14 +138,7 @@ module.exports = {
|
||||
"subject-full-stop": [2, "never", "."],
|
||||
"type-case": [2, "always", "lower-case"],
|
||||
"type-empty": [2, "never"],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"first type",
|
||||
"second type",
|
||||
],
|
||||
],
|
||||
"type-enum": [2, "always", ["first type", "second type"]],
|
||||
},
|
||||
prompt: {
|
||||
questions: {
|
||||
@ -155,7 +148,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
As you can see we have configured a whole bunch of things, but there are 2 things that matter for now:
|
||||
@ -234,8 +226,7 @@ module.exports = {
|
||||
emoji: "💎",
|
||||
},
|
||||
refactor: {
|
||||
description:
|
||||
"A code change that neither fixes a bug nor adds a feature",
|
||||
description: "A code change that neither fixes a bug nor adds a feature",
|
||||
title: "Code Refactoring",
|
||||
emoji: "📦",
|
||||
},
|
||||
@ -274,12 +265,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
description:
|
||||
"What is the scope of this change (e.g. component or file name)",
|
||||
description: "What is the scope of this change (e.g. component or file name)",
|
||||
},
|
||||
subject: {
|
||||
description:
|
||||
"Write a short, imperative tense description of the change",
|
||||
description: "Write a short, imperative tense description of the change",
|
||||
},
|
||||
body: {
|
||||
description: "Provide a longer description of the change",
|
||||
@ -377,7 +366,7 @@ First, let's add some npm scripts again:
|
||||
"release": "standard-version",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
"release:major": "standard-version --release-as major",
|
||||
"release:major": "standard-version --release-as major"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -95,11 +95,11 @@ Below you'll find a list of some of the biggest things I've changed.
|
||||
- I added a Dark mode with a little [react-toggle](https://github.com/aaronshaf/react-toggle) to switch between light/dark.
|
||||
- Blog posts now show a header that includes the "back to articles" button and my name.
|
||||
- Added code block theming for both light and dark mode
|
||||

|
||||

|
||||
- I added support for tables with a bit of styling around them (slight indent and row styling)
|
||||

|
||||

|
||||
- I added the ability to quote others in a beautiful way
|
||||

|
||||

|
||||
- Medium like image zooming (click any of the images above)
|
||||
|
||||
#### The posts query bug
|
||||
|
@ -14,7 +14,6 @@ description: "Flashing the LSI-9211 used to be way more difficult, luckily the E
|
||||
socialImage: ./media/flash-result.jpg
|
||||
---
|
||||
|
||||
|
||||
I've been building a new storage-oriented server for a while now and have yet again decided to go with (3 +1 backup) LSI 9211 raid controllers.
|
||||
|
||||
The reason I keep going for these specific raid controllers is quite simple. The card can theoretically support (8x500MB) 4GB/s in throughput divided over 8 drives (2x SFF-8087) which is near the maximum for consumer hard drives.
|
||||
|
@ -5,14 +5,14 @@ date: 2025-05-04
|
||||
template: "post"
|
||||
category: "life"
|
||||
tags:
|
||||
- life
|
||||
- grief
|
||||
- memory
|
||||
- loss
|
||||
- trauma
|
||||
- healing
|
||||
- personal
|
||||
- poetry
|
||||
- life
|
||||
- grief
|
||||
- memory
|
||||
- loss
|
||||
- trauma
|
||||
- healing
|
||||
- personal
|
||||
- poetry
|
||||
coverImage: ./media/cover.png
|
||||
---
|
||||
|
||||
@ -31,7 +31,7 @@ But then there's another kind.
|
||||
|
||||
The kind that settles into your bones. The kind that haunts you during the quiet hours. The kind that never finds a conclusion.
|
||||
|
||||
That kind is not absence. It's ***gone***. Irrevocable, final, cruel. A door slammed shut that you still find yourself knocking on, long after you’ve forgotten why you started.
|
||||
That kind is not absence. It's **_gone_**. Irrevocable, final, cruel. A door slammed shut that you still find yourself knocking on, long after you’ve forgotten why you started.
|
||||
|
||||
It never opens. And still, you try the handle every now and then.
|
||||
|
||||
@ -39,13 +39,13 @@ It never opens. And still, you try the handle every now and then.
|
||||
|
||||
Losing a loved one doesn’t stay in one room of your house though, it isn't one door that is locked. It leaks under doors and through cracks. You find it in the most unexpected places.
|
||||
|
||||
You're sipping coffee with someone, and they mention a trip they took. Suddenly, you're remembering the trip *you* never took. The one you'd planned. The one that cancer took from you.
|
||||
You're sipping coffee with someone, and they mention a trip they took. Suddenly, you're remembering the trip _you_ never took. The one you'd planned. The one that cancer took from you.
|
||||
|
||||
They talk about a shared playlist. You remember the song you danced to in the rain, soaking wet but laughing anyway. The same song you now skip every time it comes on because it hurts too much
|
||||
|
||||
Someone laughs about chipped mugs. You think of the one she cracked and called "vintage," and how it's still at the back of your cabinet. Untouched. Sacred.
|
||||
|
||||
It’s like walking through a museum where only *you* know what’s behind the glass.
|
||||
It’s like walking through a museum where only _you_ know what’s behind the glass.
|
||||
|
||||
> They talk, they laugh, the world goes on,<br />
|
||||
> Yet I sit with shadows, from dusk to dawn. <br />
|
||||
@ -56,9 +56,9 @@ It’s like walking through a museum where only *you* know what’s behind the g
|
||||
<img src="/media/the-flashback-no-one-sees.png" alt="A foggy bathroom mirror with a red lipstick kiss at the center, surrounded by a shadowy, intimate atmosphere.">
|
||||
</figure>
|
||||
|
||||
These are what I call *mind pops*. Short for Involuntary Autobiographical Memory Chains. I wrote a journal-like book once, and later a smaller one. I wanted to share them. I tried. But I was never brave enough to.
|
||||
These are what I call _mind pops_. Short for Involuntary Autobiographical Memory Chains. I wrote a journal-like book once, and later a smaller one. I wanted to share them. I tried. But I was never brave enough to.
|
||||
|
||||
Mind pops come uninvited. And they come *hard*.
|
||||
Mind pops come uninvited. And they come _hard_.
|
||||
Here are some examples from my book:
|
||||
|
||||
- The eyeliner she never quite got even.
|
||||
@ -77,7 +77,7 @@ They’re not just memories. They’re grenades. Quiet ones. You never know when
|
||||
|
||||
## Unfair doesn’t even begin to cover it
|
||||
|
||||
We grow up believing that the world has some kind of order to it. That bad things happen, sure—but not *this* bad. Not *this* unfair.
|
||||
We grow up believing that the world has some kind of order to it. That bad things happen, sure—but not _this_ bad. Not _this_ unfair.
|
||||
You lose someone who made life feel infinite. You watch them disappear into the haze of hospital lights and soft-spoken specialists.
|
||||
|
||||
Cancer doesn’t care if you’re young. Or kind.<br />
|
||||
@ -99,18 +99,18 @@ For a long time, no one knew. Most don't, still.
|
||||
|
||||
People see the version of you they expect: Smiling. Joking. Coping. Existing.<br />
|
||||
But behind the eyes, you’re unraveling. Constantly. Because grief doesn’t pause.<br />
|
||||
Not ***really***.
|
||||
Not **_really_**.
|
||||
|
||||
Sharing this is hard. Not because I don’t want to, but because it makes you so vulnerable. Because as you're writing or speaking, memories pop up uninvited. They derail your words. They choke your sentences. They just pop in... bastards...
|
||||
|
||||
And even when you manage to share, others often don’t understand. They compare. They say things like “I lost someone too,” but it’s different. This kind of grief? This kind of loss? It’s heavier. It cuts deeper. And hearing it treated like it’s the same as all the rest—*that* makes you angry.
|
||||
And even when you manage to share, others often don’t understand. They compare. They say things like “I lost someone too,” but it’s different. This kind of grief? This kind of loss? It’s heavier. It cuts deeper. And hearing it treated like it’s the same as all the rest—_that_ makes you angry.
|
||||
|
||||
Here’s something I once wrote, when I thought no one would ever read it:
|
||||
|
||||
> I'm surrounded by many figures, but still alone...<br />
|
||||
>No one sees me, nor the pain behind my mask.<br />
|
||||
>They see the smiling, happy guy I show them.<br />
|
||||
>You can't blame them though, how can I let them see the darkness in my heart? <br />
|
||||
> No one sees me, nor the pain behind my mask.<br />
|
||||
> They see the smiling, happy guy I show them.<br />
|
||||
> You can't blame them though, how can I let them see the darkness in my heart? <br />
|
||||
> It would scare them, for it has been torn apart.
|
||||
|
||||
I didn’t want to scare people. Or burden them. So I said nothing.<br />
|
||||
@ -135,19 +135,19 @@ If I eat alone, at a restaurant, I order what she would’ve. (who do I kid, I s
|
||||
And I wrote a song. It’s unfinished though—like she was.
|
||||
|
||||
**🎵 (Refrain)**<br />
|
||||
*A strand of blonde hair, as delicate as sun,*<br />
|
||||
*A mystery found, in silence it's spun.*<br />
|
||||
*But memory, like a fleeting breeze,*<br />
|
||||
*Fades away as a haze, in the time we seize.*<br /><br />
|
||||
_A strand of blonde hair, as delicate as sun,_<br />
|
||||
_A mystery found, in silence it's spun._<br />
|
||||
_But memory, like a fleeting breeze,_<br />
|
||||
_Fades away as a haze, in the time we seize._<br /><br />
|
||||
|
||||
**🎶(Verse)**<br />
|
||||
*Days pass like rustling leaves,*<br />
|
||||
*Stories fade, like quiet thieves.*<br />
|
||||
*How swiftly memory slips away,*<br />
|
||||
*Like an old melody, lost in the fray.*<br />
|
||||
_Days pass like rustling leaves,_<br />
|
||||
_Stories fade, like quiet thieves._<br />
|
||||
_How swiftly memory slips away,_<br />
|
||||
_Like an old melody, lost in the fray._<br />
|
||||
|
||||
*A mysterious gate, in the labyrinth of the mind,*<br />
|
||||
*Locked and hidden, what secrets behind?*
|
||||
_A mysterious gate, in the labyrinth of the mind,_<br />
|
||||
_Locked and hidden, what secrets behind?_
|
||||
|
||||
As I'm writing this blog however, I have a sudden urge to add sound to the lyrics, it'll still be unfinished, but a little farther along.
|
||||
I used both a piano and a viola to give it a more interesting sound than just the piano.
|
||||
@ -175,7 +175,7 @@ She once told me something that still echoes today:
|
||||
> "You have to grieve me, for a while... and then you move on. <br />
|
||||
> You find someone else to love. Someone who makes you feel something again. <br /> You deserve that."
|
||||
|
||||
I shook my head. *"I can’t. I **won’t**. Not because I don’t want to feel again. But because abstinence, to me, feels like remembering you as you are. Eternal."* <br />
|
||||
I shook my head. _"I can’t. I **won’t**. Not because I don’t want to feel again. But because abstinence, to me, feels like remembering you as you are. Eternal."_ <br />
|
||||
We both cried after that. For different reasons, I think.
|
||||
|
||||
### My words weren't true though
|
||||
@ -192,7 +192,7 @@ I know grief isn’t rational. It doesn’t follow clean lines.<br />
|
||||
And maybe that’s what hurts most of all—knowing I tried, and still came back to the same place. <br />
|
||||
Alone, but full of someone who isn’t here.
|
||||
|
||||
One day... *maybe*.
|
||||
One day... _maybe_.
|
||||
|
||||
## Why I’m finally speaking
|
||||
|
||||
@ -227,12 +227,12 @@ I’ve met people over the years who’ve carried this same grief. And some of t
|
||||
On the edge.
|
||||
|
||||
Wondering if there’s peace on the other side of absence.<br />
|
||||
Let me say this clearly: I have never thought of taking that road for myself. If anything, I would *ask* for eternal life. Even with the grief.<br />
|
||||
Let me say this clearly: I have never thought of taking that road for myself. If anything, I would _ask_ for eternal life. Even with the grief.<br />
|
||||
But if you are someone who has stood on that edge:
|
||||
|
||||
**Don’t. Please.**
|
||||
|
||||
The pain is real. The grief is heavy. But life—*even broken life*—has light in it still. And sometimes, all you need is one person willing to sit with you in the dark.
|
||||
The pain is real. The grief is heavy. But life—_even broken life_—has light in it still. And sometimes, all you need is one person willing to sit with you in the dark.
|
||||
|
||||
Let me be that person for a moment. Just long enough to remind you: the door might be closed, but the room isn’t empty.
|
||||
|
||||
@ -241,7 +241,7 @@ Whatever you do, don't pass on the pain.
|
||||
|
||||
## What help looks like
|
||||
|
||||
Over the past two years, I’ve slowly—*achingly*—learned to live with it.
|
||||
Over the past two years, I’ve slowly—_achingly_—learned to live with it.
|
||||
And honestly, I'm happier now than I've ever been since.
|
||||
|
||||
I’ve had friends. Real ones. Some with the training to guide me through the murk. Others who simply sat there while I unraveled, trying their best.
|
||||
@ -252,7 +252,7 @@ They had always been there, I just didn't allow them to be there for me before.
|
||||
I’ve learned that grief isn’t a wall to climb or a puzzle to solve. It’s a landscape.
|
||||
|
||||
You don’t conquer it.<br />
|
||||
You *walk it*. One aching step at a time.<br />
|
||||
You _walk it_. One aching step at a time.<br />
|
||||
And if you're lucky... you don’t walk it alone.
|
||||
|
||||
### To those who held me together
|
||||
@ -287,7 +287,7 @@ Still holding her memory, but not drowning in it.
|
||||
## Before I finally sign off
|
||||
|
||||
This entire blog was something I never thought I could write.
|
||||
And what comes next,this next part, is something I swore I *never* would share.
|
||||
And what comes next,this next part, is something I swore I _never_ would share.
|
||||
|
||||
But here we are.
|
||||
|
||||
@ -311,7 +311,7 @@ We, humans, perceive the vast majority of other humans we encounter as bystander
|
||||
|
||||
This book will give you a small glimpse into my life. By the end, you might find that I'm more complex than you've thought till now. That there's another side to the happy, carefree guy I show the world for the majority of my time.
|
||||
|
||||
Truth is, that life hasn't always been easy. In fact, I'd say that after the turning point my life has never felt easy anymore. In fact, for at least 10 years now I've been living with prodigious amounts of a single emotion: bereavement.
|
||||
Truth is, that life hasn't always been easy. In fact, I'd say that after the turning point my life has never felt easy anymore. In fact, for at least 10 years now I've been living with prodigious amounts of a single emotion: bereavement.
|
||||
|
||||
And though I've tried not to let the bereavement take over my life, or in fact make it known to others. it's always been there. I've effectively come to think of it as a zit. Constantly there, constantly annoying and once it's gone away it's only a matter of time before it shows up again somewhere else on your body.
|
||||
|
||||
@ -353,4 +353,4 @@ A happy one">
|
||||
If you’ve read this far—thank you.
|
||||
That alone means more than you know.
|
||||
|
||||
*- Rick, mastermindzh, lycan, xxxroosjexxx, and other alias' you might know me from.*
|
||||
_- Rick, mastermindzh, lycan, xxxroosjexxx, and other alias' you might know me from._
|
||||
|
@ -44,7 +44,7 @@ One of the most satisfying aspects of IaC is the ability to automate even the sm
|
||||
|
||||
Setting up UptimeKuma is straightforward, you can simply use our old friend Docker:
|
||||
|
||||
```docker run -d --name uptime-kuma -v ./data/uptimekuma:/app/data -p 3001:3001 louislam/uptime-kuma```
|
||||
`docker run -d --name uptime-kuma -v ./data/uptimekuma:/app/data -p 3001:3001 louislam/uptime-kuma`
|
||||
|
||||
And access the Dashboard by navigating to <http://localhost:3001> to configure your monitors.
|
||||
But that isn't automatic enough for me, I like to put my things in compose files for home usage.
|
||||
@ -65,8 +65,8 @@ labels:
|
||||
|
||||
This actually does 2 things:
|
||||
|
||||
- creates a group with the *key/id* `monitoring` and the name `Monitoring`
|
||||
- Adds a monitor with the *key/id* `uptime_kuma` to UptimeKuma with the type `http`, name `Kuma status monitoring`, and url `http://${HOST_IP}:3001`
|
||||
- creates a group with the _key/id_ `monitoring` and the name `Monitoring`
|
||||
- Adds a monitor with the _key/id_ `uptime_kuma` to UptimeKuma with the type `http`, name `Kuma status monitoring`, and url `http://${HOST_IP}:3001`
|
||||
|
||||
Adding these labels, whilst AutoKuma is running and configured to pick up labels starting with `kuma` is enough for monitors to show up (after restarting the containers).
|
||||
All in all, my `docker-compose.yml` file for both UptimeKuma and AutoKuma now looks like this:
|
||||
|
65
eslint.config.js
Normal file
65
eslint.config.js
Normal file
@ -0,0 +1,65 @@
|
||||
const mastermindzhConfig = require("@mastermindzh/eslint-config");
|
||||
const typescriptEslint = require("@typescript-eslint/eslint-plugin");
|
||||
const typescriptParser = require("@typescript-eslint/parser");
|
||||
const importPlugin = require("eslint-plugin-import");
|
||||
const reactPlugin = require("eslint-plugin-react");
|
||||
|
||||
module.exports = [
|
||||
// Global ignores (replaces .eslintignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/**",
|
||||
"build/**",
|
||||
"public/**",
|
||||
".cache/**",
|
||||
"node_modules/**",
|
||||
"*.config.js",
|
||||
"*.config.ts",
|
||||
],
|
||||
},
|
||||
...mastermindzhConfig,
|
||||
{
|
||||
// Only lint TypeScript files (matching your .eslintignore pattern)
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
import: importPlugin,
|
||||
react: reactPlugin,
|
||||
},
|
||||
// Your project-specific rules
|
||||
rules: {
|
||||
// Import rules
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
devDependencies: ["**/*.test.ts", "**/*.test.tsx", "**/internal/**/*.ts"],
|
||||
},
|
||||
],
|
||||
// TypeScript rules
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
// Use the correct rule name for quotes
|
||||
quotes: ["error", "double"],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
format: ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],
|
||||
leadingUnderscore: "allow",
|
||||
selector: "parameter",
|
||||
},
|
||||
],
|
||||
// React rules
|
||||
"react/static-property-placement": "off",
|
||||
"react/prop-types": "off",
|
||||
// Shadow rules
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
},
|
||||
},
|
||||
];
|
@ -1,18 +1,10 @@
|
||||
import path from "path";
|
||||
|
||||
const templates = Object.freeze({
|
||||
indexTemplate: path.resolve(
|
||||
"./src/templates/IndexTemplate/IndexTemplate.tsx",
|
||||
),
|
||||
notFoundTemplate: path.resolve(
|
||||
"./src/templates/NotFoundTemplate/NotFoundTemplate.tsx",
|
||||
),
|
||||
categoryTemplate: path.resolve(
|
||||
"./src/templates/CategoryTemplate/CategoryTemplate.tsx",
|
||||
),
|
||||
categoriesTemplate: path.resolve(
|
||||
"./src/templates/CategoriesTemplate/CategoriesTemplate.tsx",
|
||||
),
|
||||
indexTemplate: path.resolve("./src/templates/IndexTemplate/IndexTemplate.tsx"),
|
||||
notFoundTemplate: path.resolve("./src/templates/NotFoundTemplate/NotFoundTemplate.tsx"),
|
||||
categoryTemplate: path.resolve("./src/templates/CategoryTemplate/CategoryTemplate.tsx"),
|
||||
categoriesTemplate: path.resolve("./src/templates/CategoriesTemplate/CategoriesTemplate.tsx"),
|
||||
tagTemplate: path.resolve("./src/templates/TagTemplate/TagTemplate.tsx"),
|
||||
tagsTemplate: path.resolve("./src/templates/TagsTemplate/TagsTemplate.tsx"),
|
||||
pageTemplate: path.resolve("./src/templates/PageTemplate/PageTemplate.tsx"),
|
||||
|
@ -99,7 +99,7 @@ const createPages: GatsbyNode["createPages"] = async ({ graphql, actions }) => {
|
||||
const path = utils.concat(
|
||||
constants.routes.categoryRoute,
|
||||
"/",
|
||||
utils.toKebabCase(category.fieldValue)
|
||||
utils.toKebabCase(category.fieldValue),
|
||||
);
|
||||
|
||||
for (let page = 0; page < total; page += 1) {
|
||||
|
@ -28,7 +28,7 @@ const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) =>
|
||||
|
||||
if (tags) {
|
||||
const value = tags.map((tag) =>
|
||||
utils.concat(constants.routes.tagRoute, "/", utils.toKebabCase(tag), "/")
|
||||
utils.concat(constants.routes.tagRoute, "/", utils.toKebabCase(tag), "/"),
|
||||
);
|
||||
|
||||
createNodeField({ node, name: "tagSlugs", value });
|
||||
@ -39,7 +39,7 @@ const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) =>
|
||||
constants.routes.categoryRoute,
|
||||
"/",
|
||||
utils.toKebabCase(category),
|
||||
"/"
|
||||
"/",
|
||||
);
|
||||
|
||||
createNodeField({ node, name: "categorySlug", value });
|
||||
|
@ -5,24 +5,12 @@ const gatsby = jest.requireActual("gatsby");
|
||||
export default {
|
||||
...gatsby,
|
||||
graphql: jest.fn(),
|
||||
Link: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
({
|
||||
activeClassName,
|
||||
activeStyle,
|
||||
getProps,
|
||||
innerRef,
|
||||
ref,
|
||||
replace,
|
||||
to,
|
||||
...rest
|
||||
}) =>
|
||||
React.createElement("a", {
|
||||
...rest,
|
||||
href: to,
|
||||
}),
|
||||
),
|
||||
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
|
||||
React.createElement("a", {
|
||||
...rest,
|
||||
href: to,
|
||||
}),
|
||||
),
|
||||
StaticQuery: jest.fn(),
|
||||
useStaticQuery: jest.fn(),
|
||||
};
|
||||
|
@ -22,10 +22,7 @@ const jestConfig: Config.InitialOptions = {
|
||||
"identity-obj-proxy",
|
||||
"^gatsby-page-utils/(.*)$": "gatsby-page-utils/$1",
|
||||
"^gatsby-core-utils/(.*)$": "gatsby-core-utils/dist/$1",
|
||||
"^gatsby-plugin-utils/(.*)$": [
|
||||
"gatsby-plugin-utils/dist/$1",
|
||||
"gatsby-plugin-utils/$1",
|
||||
],
|
||||
"^gatsby-plugin-utils/(.*)$": ["gatsby-plugin-utils/dist/$1", "gatsby-plugin-utils/$1"],
|
||||
},
|
||||
transform: { "^.+\\.[jt]sx?$": ["@swc/jest", swc] },
|
||||
setupFiles: ["<rootDir>/internal/testing/jest-setup.ts"],
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"MD033": false
|
||||
"MD033": false
|
||||
}
|
||||
|
15763
package-lock.json
generated
15763
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
99
package.json
99
package.json
@ -22,11 +22,12 @@
|
||||
"commit": "git-cz",
|
||||
"format": "npm run format:ts && npm run format:scss",
|
||||
"format:scss": "stylelint \"src/**/*.scss\" --fix",
|
||||
"format:ts": "eslint \"src\" --ext .tsx,.ts --fix && prettier --write .",
|
||||
"format:ts": "eslint \"src/**/*.{ts,tsx}\" --fix && prettier --write .",
|
||||
"install:legacy": "npm install --legacy-peer-deps",
|
||||
"lint": "npm run lint:ts && npm run lint:scss",
|
||||
"lint:scss": "stylelint \"src/**/*.scss\"",
|
||||
"lint:staged": "lint-staged",
|
||||
"lint:ts": "eslint \"src\" --ext .tsx,.ts && prettier --check .",
|
||||
"lint:ts": "eslint \"src/**/*.{ts,tsx}\" && prettier --check .",
|
||||
"prepare": "husky install",
|
||||
"release": "standard-version",
|
||||
"release:major": "standard-version --release-as major",
|
||||
@ -58,8 +59,8 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"classnames": "^2.5.1",
|
||||
"disqus-react": "^1.1.6",
|
||||
"gatsby": "^5.14.4",
|
||||
"disqus-react": "^1.1.7",
|
||||
"gatsby": "^5.14.5",
|
||||
"gatsby-link": "^5.14.1",
|
||||
"gatsby-plugin-catch-links": "^5.14.0",
|
||||
"gatsby-plugin-feed": "^5.14.0",
|
||||
@ -84,7 +85,7 @@
|
||||
"gatsby-source-filesystem": "^5.14.0",
|
||||
"gatsby-transformer-remark": "^6.14.0",
|
||||
"gatsby-transformer-sharp": "^5.14.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-cookie-consent": "^9.0.0",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -93,69 +94,67 @@
|
||||
"reading-time": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^17.8.1",
|
||||
"@commitlint/cz-commitlint": "^19.6.1",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@mastermindzh/eslint-config": "^1.0.2",
|
||||
"@mastermindzh/prettier-config": "^1.0.0",
|
||||
"@semantic-release/exec": "6.0.3",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@swc/core": "^1.10.4",
|
||||
"@swc/jest": "^0.2.37",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/cz-commitlint": "^19.8.1",
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@jest/globals": "^30.0.4",
|
||||
"@mastermindzh/eslint-config": "^3.1.0",
|
||||
"@mastermindzh/prettier-config": "^1.1.0",
|
||||
"@swc/core": "^1.12.14",
|
||||
"@swc/jest": "^0.2.39",
|
||||
"@types/gatsby-transformer-remark": "^2.9.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@types/react-toggle": "^4.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"browserslist": "^4.24.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"browserslist": "^4.25.1",
|
||||
"codecov": "^3.8.3",
|
||||
"commitizen": "^4.3.1",
|
||||
"commitlint": "^19.6.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-import-resolver-typescript": "^3.7.0",
|
||||
"eslint-plugin-flowtype": "^8.0.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"commitlint": "^19.8.1",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^8.0.3",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-cli": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest": "^30.0.4",
|
||||
"jest-cli": "^30.0.4",
|
||||
"jest-environment-jsdom": "^30.0.4",
|
||||
"jest-svg-transformer": "^1.0.0",
|
||||
"lint-staged": "^15.3.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"lost": "9.0.2",
|
||||
"markdownlint": "^0.37.3",
|
||||
"postcss": "^8.4.49",
|
||||
"markdownlint": "^0.38.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-packagejson": "^2.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-packagejson": "^2.5.18",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.83.0",
|
||||
"sass": "^1.89.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"standard-version": "^9.5.0",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-recommended-scss": "^14.1.0",
|
||||
"stylelint-order": "^6.0.4",
|
||||
"stylelint-scss": "^6.10.0",
|
||||
"stylelint": "^16.21.1",
|
||||
"stylelint-config-recommended-scss": "^15.0.1",
|
||||
"stylelint-order": "^7.0.0",
|
||||
"stylelint-scss": "^6.12.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.8.3",
|
||||
"unist-util-find": "3.0.0"
|
||||
}
|
||||
}
|
||||
|
11
renovate.json
Normal file
11
renovate.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"ignoreDeps": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"@types/react",
|
||||
"@types/react-dom",
|
||||
"@types/react-test-renderer",
|
||||
"react-test-renderer"
|
||||
]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "base/generic";
|
||||
@use "base/generic";
|
||||
|
||||
.showInPrintView {
|
||||
display: none;
|
||||
|
@ -1,6 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "mixins/breakpoints";
|
||||
@import "mixins/line-height";
|
||||
@import "mixins/padding";
|
||||
@import "mixins/margin";
|
||||
@forward "mixins/breakpoints";
|
||||
@forward "mixins/line-height";
|
||||
@forward "mixins/padding";
|
||||
@forward "mixins/margin";
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../variables";
|
||||
@import "../mixins";
|
||||
@use "../variables" as *;
|
||||
@use "../mixins" as *;
|
||||
|
||||
html {
|
||||
font-size: $typographic-root-font-size;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
@import "base";
|
||||
@use "variables";
|
||||
@use "mixins";
|
||||
@use "base";
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin breakpoint-xs {
|
||||
@content;
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin line-height($number) {
|
||||
line-height: #{$number * $typographic-leading + "px"};
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin margin-auto($number: 0) {
|
||||
margin: #{$number * $typographic-leading + "px"} auto;
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin padding-left($number) {
|
||||
padding-left: #{$number * $typographic-leading + "px"};
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
|
||||
$shadow: 0px -1px 5px rgba(50, 50, 50, 0.75);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.feed {
|
||||
.item {
|
||||
|
@ -3,7 +3,6 @@ import React from "react";
|
||||
import * as styles from "./Icon.module.scss";
|
||||
import { ICONS } from "@/constants";
|
||||
|
||||
|
||||
interface Props {
|
||||
name: keyof typeof ICONS;
|
||||
icon: {
|
||||
|
@ -1,11 +1,7 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { graphql, StaticQuery } from "gatsby";
|
||||
import {
|
||||
GatsbyImage,
|
||||
GatsbyImageProps,
|
||||
IGatsbyImageData,
|
||||
} from "gatsby-plugin-image";
|
||||
import { GatsbyImage, GatsbyImageProps, IGatsbyImageData } from "gatsby-plugin-image";
|
||||
import { FileSystemNode } from "gatsby-source-filesystem";
|
||||
|
||||
interface Props extends Omit<GatsbyImageProps, "image"> {
|
||||
@ -28,9 +24,7 @@ const Image: FC<Props> = ({ path, ...rest }: Props) => (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
images: allFile(
|
||||
filter: { ext: { regex: "/png|jpg|jpeg|webp|tif|tiff/" } }
|
||||
) {
|
||||
images: allFile(filter: { ext: { regex: "/png|jpg|jpeg|webp|tif|tiff/" } }) {
|
||||
edges {
|
||||
node {
|
||||
absolutePath
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.layout {
|
||||
lost-center: $layout-width;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import Helmet from "react-helmet";
|
||||
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import { CookieBar } from "../Cookiebar/CookieBar";
|
||||
import * as styles from "./Layout.module.scss";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@ -20,7 +20,6 @@ const Layout: React.FC<Props> = ({
|
||||
description,
|
||||
socialImage = "",
|
||||
noIndex = false,
|
||||
slug,
|
||||
}: Props) => {
|
||||
const { author, url } = useSiteMetadata();
|
||||
const metaImage = socialImage || author.photo;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.page {
|
||||
@include margin-bottom(2);
|
||||
|
@ -11,9 +11,7 @@ describe("Page", () => {
|
||||
title: mocks.markdownRemark.frontmatter.title,
|
||||
};
|
||||
|
||||
const tree = renderer
|
||||
.create(<Page {...props}>{props.children}</Page>)
|
||||
.toJSON();
|
||||
const tree = renderer.create(<Page {...props}>{props.children}</Page>).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
import type { Nullable } from "@/types";
|
||||
import { Helmet } from "react-helmet";
|
||||
import * as styles from "./Page.module.scss";
|
||||
import type { Nullable } from "@/types";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
@ -1,7 +1,7 @@
|
||||
@use "sass:color";
|
||||
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { PAGINATION } from "@/constants";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "gatsby";
|
||||
import React from "react";
|
||||
import * as styles from "./Pagination.module.scss";
|
||||
import { PAGINATION } from "@/constants";
|
||||
|
||||
type Props = {
|
||||
prevPagePath: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.author {
|
||||
border-top: 1px solid $color-gray-border;
|
||||
|
@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Author", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import React from "react";
|
||||
import * as styles from "./Author.module.scss";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
|
||||
const Author = () => {
|
||||
const { author } = useSiteMetadata();
|
||||
@ -13,6 +13,7 @@ const Author = () => {
|
||||
<strong>{author.name}</strong>
|
||||
</a>
|
||||
{typeof window !== "undefined" ? (
|
||||
// eslint-disable-next-line no-undef
|
||||
<span className="showInPrintView"> {window ? `@ ${window.location}` : ""}</span>
|
||||
) : (
|
||||
""
|
||||
|
@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Comments", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.content {
|
||||
@include margin-auto();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Content } from "@/components/Post/Content";
|
||||
import * as mocks from "@/mocks";
|
||||
import { StaticQuery, useStaticQuery } from "gatsby";
|
||||
import React from "react";
|
||||
import renderer from "react-test-renderer";
|
||||
import * as mocks from "@/mocks";
|
||||
import { Content } from "@/components/Post/Content";
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.meta {
|
||||
.date {
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.post {
|
||||
|
||||
|
@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Post", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import type { Node } from "@/types";
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useSiteMetadata } from "../../hooks";
|
||||
@ -8,6 +7,7 @@ import { Content } from "./Content";
|
||||
import { Meta } from "./Meta";
|
||||
import * as styles from "./Post.module.scss";
|
||||
import { Tags } from "./Tags";
|
||||
import type { Node } from "@/types";
|
||||
|
||||
interface Props {
|
||||
post: Node;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.tags {
|
||||
@include margin-bottom(0.5);
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.header {
|
||||
align-items: baseline;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Image } from "@/components/Image";
|
||||
import { Link, navigate } from "gatsby";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
|
||||
import * as styles from "./PostHeader.module.scss";
|
||||
import { Image } from "@/components/Image";
|
||||
|
||||
type Props = { author: { name: string; photo: string } };
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.author {
|
||||
.photo {
|
||||
|
@ -5,7 +5,6 @@ import { Link } from "gatsby";
|
||||
import * as styles from "./Author.module.scss";
|
||||
import { Image } from "@/components/Image";
|
||||
|
||||
|
||||
type Props = {
|
||||
author: {
|
||||
name: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.contacts {
|
||||
@include margin-bottom(1);
|
||||
|
@ -25,7 +25,7 @@ const Contacts: React.FC<Props> = ({ contacts }: Props) => (
|
||||
<Icon name={name} icon={getIcon(name)} />
|
||||
</a>
|
||||
</li>
|
||||
) : null
|
||||
) : null,
|
||||
)}
|
||||
<li className={styles.item} key="keys">
|
||||
<a className={styles.link} href="/keys.json" rel="noopener noreferrer" target="_blank">
|
||||
|
@ -1,7 +1,7 @@
|
||||
@use "sass:color";
|
||||
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.copyright {
|
||||
color: $color-gray;
|
||||
|
@ -6,8 +6,6 @@ type Props = {
|
||||
copyright: string;
|
||||
};
|
||||
|
||||
const Copyright = ({ copyright }: Props) => (
|
||||
<div className={styles.copyright}>{copyright}</div>
|
||||
);
|
||||
const Copyright = ({ copyright }: Props) => <div className={styles.copyright}>{copyright}</div>;
|
||||
|
||||
export default Copyright;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.menu {
|
||||
.list {
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
|
@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Sidebar", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import React from "react";
|
||||
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
|
||||
import { Author } from "./Author";
|
||||
@ -6,6 +5,7 @@ import { Contacts } from "./Contacts";
|
||||
import { Copyright } from "./Copyright";
|
||||
import { Menu } from "./Menu";
|
||||
import * as styles from "./Sidebar.module.scss";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
|
||||
type Props = {
|
||||
isIndex?: boolean;
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-len */
|
||||
const ICONS = {
|
||||
twitter: {
|
||||
path: "M25.312 6.375c-0.688 1-1.547 1.891-2.531 2.609 0.016 0.219 0.016 0.438 0.016 0.656 0 6.672-5.078 14.359-14.359 14.359-2.859 0-5.516-0.828-7.75-2.266 0.406 0.047 0.797 0.063 1.219 0.063 2.359 0 4.531-0.797 6.266-2.156-2.219-0.047-4.078-1.5-4.719-3.5 0.313 0.047 0.625 0.078 0.953 0.078 0.453 0 0.906-0.063 1.328-0.172-2.312-0.469-4.047-2.5-4.047-4.953v-0.063c0.672 0.375 1.453 0.609 2.281 0.641-1.359-0.906-2.25-2.453-2.25-4.203 0-0.938 0.25-1.797 0.688-2.547 2.484 3.062 6.219 5.063 10.406 5.281-0.078-0.375-0.125-0.766-0.125-1.156 0-2.781 2.25-5.047 5.047-5.047 1.453 0 2.766 0.609 3.687 1.594 1.141-0.219 2.234-0.641 3.203-1.219-0.375 1.172-1.172 2.156-2.219 2.781 1.016-0.109 2-0.391 2.906-0.781z",
|
||||
|
@ -10,20 +10,18 @@ interface CategoriesQueryResult {
|
||||
}
|
||||
|
||||
const useCategoriesList = () => {
|
||||
const { allMarkdownRemark } = useStaticQuery<CategoriesQueryResult>(
|
||||
graphql`
|
||||
query CategoriesListQuery {
|
||||
allMarkdownRemark(
|
||||
filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
|
||||
) {
|
||||
group(field: { frontmatter: { category: SELECT } }) {
|
||||
fieldValue
|
||||
totalCount
|
||||
}
|
||||
const { allMarkdownRemark } = useStaticQuery<CategoriesQueryResult>(graphql`
|
||||
query CategoriesListQuery {
|
||||
allMarkdownRemark(
|
||||
filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
|
||||
) {
|
||||
group(field: { frontmatter: { category: SELECT } }) {
|
||||
fieldValue
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
`);
|
||||
|
||||
return allMarkdownRemark.group ?? [];
|
||||
};
|
||||
|
@ -1,52 +1,50 @@
|
||||
import { graphql, useStaticQuery } from "gatsby";
|
||||
|
||||
const useSiteMetadata = () => {
|
||||
const { site } = useStaticQuery(
|
||||
graphql`
|
||||
query SiteMetaData {
|
||||
site {
|
||||
siteMetadata {
|
||||
author {
|
||||
bio
|
||||
name
|
||||
photo
|
||||
contacts {
|
||||
rss
|
||||
line
|
||||
email
|
||||
weibo
|
||||
gitlab
|
||||
medium
|
||||
github
|
||||
twitter
|
||||
codepen
|
||||
youtube
|
||||
facebook
|
||||
linkedin
|
||||
telegram
|
||||
instagram
|
||||
soundcloud
|
||||
phone
|
||||
}
|
||||
const { site } = useStaticQuery(graphql`
|
||||
query SiteMetaData {
|
||||
site {
|
||||
siteMetadata {
|
||||
author {
|
||||
bio
|
||||
name
|
||||
photo
|
||||
contacts {
|
||||
rss
|
||||
line
|
||||
email
|
||||
weibo
|
||||
gitlab
|
||||
medium
|
||||
github
|
||||
twitter
|
||||
codepen
|
||||
youtube
|
||||
facebook
|
||||
linkedin
|
||||
telegram
|
||||
instagram
|
||||
soundcloud
|
||||
phone
|
||||
}
|
||||
menu {
|
||||
path
|
||||
label
|
||||
}
|
||||
legalMenu {
|
||||
path
|
||||
label
|
||||
}
|
||||
url
|
||||
title
|
||||
subtitle
|
||||
copyright
|
||||
disqusShortname
|
||||
}
|
||||
menu {
|
||||
path
|
||||
label
|
||||
}
|
||||
legalMenu {
|
||||
path
|
||||
label
|
||||
}
|
||||
url
|
||||
title
|
||||
subtitle
|
||||
copyright
|
||||
disqusShortname
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
`);
|
||||
|
||||
return site.siteMetadata;
|
||||
};
|
||||
|
@ -10,20 +10,18 @@ interface TagsQueryResult {
|
||||
}
|
||||
|
||||
const useTagsList = () => {
|
||||
const { allMarkdownRemark } = useStaticQuery<TagsQueryResult>(
|
||||
graphql`
|
||||
query TagsListQuery {
|
||||
allMarkdownRemark(
|
||||
filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
|
||||
) {
|
||||
group(field: { frontmatter: { tags: SELECT } }) {
|
||||
fieldValue
|
||||
totalCount
|
||||
}
|
||||
const { allMarkdownRemark } = useStaticQuery<TagsQueryResult>(graphql`
|
||||
query TagsListQuery {
|
||||
allMarkdownRemark(
|
||||
filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
|
||||
) {
|
||||
group(field: { frontmatter: { tags: SELECT } }) {
|
||||
fieldValue
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
`);
|
||||
|
||||
return allMarkdownRemark.group || [];
|
||||
};
|
||||
|
@ -6,7 +6,6 @@ import { StaticQuery, useStaticQuery } from "gatsby";
|
||||
import CategoriesTemplate from "./CategoriesTemplate";
|
||||
import * as mocks from "@/mocks";
|
||||
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
|
@ -6,15 +6,12 @@ import { StaticQuery, useStaticQuery } from "gatsby";
|
||||
import CategoryTemplate from "./CategoryTemplate";
|
||||
import * as mocks from "@/mocks";
|
||||
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("CategoryTemplate", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -6,15 +6,12 @@ import { StaticQuery, useStaticQuery } from "gatsby";
|
||||
import IndexTemplate from "./IndexTemplate";
|
||||
import * as mocks from "@/mocks";
|
||||
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("IndexTemplate", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -6,15 +6,12 @@ import NotFoundTemplate from "./NotFoundTemplate";
|
||||
|
||||
import * as mocks from "@/mocks";
|
||||
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("NotFoundTemplate", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
import { Link } from "gatsby";
|
||||
import { Layout } from "@/components/Layout";
|
||||
import { Page } from "@/components/Page";
|
||||
import { Sidebar } from "@/components/Sidebar";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import { Link } from "gatsby";
|
||||
|
||||
const NotFoundTemplate: React.FC = () => {
|
||||
const { title, subtitle } = useSiteMetadata();
|
||||
|
@ -17,9 +17,7 @@ describe("PostTemplate", () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -9,9 +9,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("TagTemplate", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,6 @@ import TagsTemplate from "./TagsTemplate";
|
||||
|
||||
import * as mocks from "@/mocks";
|
||||
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user