mirror of
https://github.com/valentineus/popov.link.git
synced 2025-07-03 08:00:26 +03:00
Compare commits
157 Commits
b6a06ed787
...
master
Author | SHA1 | Date | |
---|---|---|---|
29385b1edf | |||
ae7dac3099 | |||
d8a59ef4c4 | |||
4412049beb | |||
c32aa4e773 | |||
c9d67d9210 | |||
d99c901b42 | |||
15fdeb46f1
|
|||
15795a5337
|
|||
bc4f65c1f6
|
|||
a81117972d
|
|||
3d0f485746
|
|||
25ebd94466
|
|||
0473060773
|
|||
a65e9c8455
|
|||
17f9a467d7
|
|||
3df02c5304
|
|||
9777d996d1
|
|||
1c15151ef5
|
|||
968b379ff0 | |||
f408fd6327 | |||
67f245a48f
|
|||
16fa8a3b5d
|
|||
423344fca5
|
|||
78a9c2abc5
|
|||
604e507b31
|
|||
3d6aedd272
|
|||
6fe5df4e32
|
|||
26de615385
|
|||
77e65cb92c | |||
34ce9f6162 | |||
f3cc07e92c | |||
d74eec1c47 | |||
9ebcd40f60
|
|||
4e8c17a6ea
|
|||
6a47cb4165
|
|||
bb7481670e
|
|||
d322487420 | |||
dfe9115ac9 | |||
2f535ac598 | |||
b1c85503d6
|
|||
009dca3402
|
|||
e56c4f3edf
|
|||
e9612c756b
|
|||
eb36082087
|
|||
81bcfbdc65
|
|||
4266100d7f | |||
2dcf837bee | |||
9b937e2736
|
|||
6d0f766671
|
|||
aaeefd48ee | |||
27caca159e
|
|||
cf5901f8c1
|
|||
547b008398
|
|||
1d79bd154b
|
|||
24c710bd41
|
|||
3660982271
|
|||
ad6903f7ee
|
|||
5c89158bdb
|
|||
bff792581a
|
|||
a170c87cdb
|
|||
dca4c61251
|
|||
d0b675b944
|
|||
cb4820f8f1
|
|||
39e28d06ea
|
|||
6f0260f7bc
|
|||
d74d8acdc2
|
|||
c47b9f9037
|
|||
1ed689ed71
|
|||
1d7fbe8fbb
|
|||
2bb3139579
|
|||
f88a1eaa69
|
|||
1f18536f7a | |||
a560f6d2e0
|
|||
6adb475581
|
|||
725ec5df89
|
|||
a6078cdf0b
|
|||
b7005555c7
|
|||
5364a666fd
|
|||
b5bc0bc56a
|
|||
da49ba84ba
|
|||
a1895c8bc8
|
|||
cfa3cc7be8
|
|||
0db17945d4
|
|||
2b4908aa92
|
|||
001585b8a6
|
|||
8bce6939d1
|
|||
e296d4a801 | |||
f99f41dfc7 | |||
827ee4c225 | |||
badac0763d | |||
dc18c50c4e | |||
7e12cdc213 | |||
0460ff06d9 | |||
549e1ac8b8 | |||
5cb421cfec | |||
86efbe695c | |||
71a7c3cb77 | |||
97a4c5b168 | |||
7fe8a10f02 | |||
6986070ec8 | |||
0deb65fde2
|
|||
88464d0240
|
|||
fd32a6b8f7 | |||
2202c45aba | |||
29e3b8e224 | |||
0bba3fe744
|
|||
f826ef31c7 | |||
2cc827ef55 | |||
6c083b7b0e | |||
7a3c63e85d | |||
d2ac0ca590 | |||
a468e4e7a4 | |||
e48033a97d | |||
1d139b0872 | |||
9b62213886
|
|||
c694648c2c
|
|||
7724e9617d
|
|||
0cbdbbad7f
|
|||
849d637880
|
|||
6a81db4cfc
|
|||
1b6b61ba31 | |||
5c0ed9cf56 | |||
f2bd75ac67
|
|||
90a0ba8b97
|
|||
aa94393f4d
|
|||
f2025da4fd
|
|||
5e4542edcb | |||
8a203a3502 | |||
f1416bf181
|
|||
0d0f2923ac | |||
231375202d | |||
347636d1bb | |||
2f5d40dadd | |||
b841e04f60 | |||
a01ded06c1 | |||
f84ecc39b0 | |||
077c2320d4 | |||
e619a27d51 | |||
cfb4026385 | |||
8597054d13 | |||
cab6ad95f8 | |||
cf75ecfe6b | |||
4d4eb86bfa | |||
566685adc9 | |||
01b82f0524 | |||
b886cd95c5 | |||
d466742e89 | |||
563e197897 | |||
18fb312ca4 | |||
493042f450 | |||
874d7fa715 | |||
471c98d958 | |||
6a05b1f22f | |||
058299a1fa | |||
f4ba238874 | |||
f7f601e619 |
2
.env
2
.env
@ -1,2 +0,0 @@
|
||||
DEFAULT_TITLE=Valentin Popov’s Blog
|
||||
DEFAULT_DESCRIPTION=Tech insights and coding best practices from an OpenSource enthusiast and ethical hacker.
|
@ -1,14 +0,0 @@
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
container: gitea/runner-images:ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- run: npm ci
|
||||
- run: npm run check
|
@ -1,16 +0,0 @@
|
||||
name: RenovateBot
|
||||
on:
|
||||
schedule:
|
||||
- cron: "@daily"
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
container: ghcr.io/renovatebot/renovate:38
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: renovate
|
||||
env:
|
||||
RENOVATE_CONFIG_FILE: renovate.config.cjs
|
||||
RENOVATE_REPOSITORIES: ${{ gitea.repository }}
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
14
.github/dependabot.yml
vendored
Normal file
14
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
16
.github/renovate.json
vendored
Normal file
16
.github/renovate.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", ":disableDependencyDashboard"],
|
||||
"assignees": ["valentineus"],
|
||||
"labels": ["dependencies", "automated"],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Group patch & minor updates together",
|
||||
"groupName": "all digest updates",
|
||||
"groupSlug": "all-digest",
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"matchPackageNames": ["*"],
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
}
|
0
.github/workflows/.gitkeep
vendored
0
.github/workflows/.gitkeep
vendored
28
.github/workflows/ci.yml
vendored
Normal file
28
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run checks
|
||||
run: npm run check
|
||||
- name: Run type checks
|
||||
run: npm run typecheck
|
25
.github/workflows/mirror.yml
vendored
Normal file
25
.github/workflows/mirror.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Mirror
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Mirror to remote repository
|
||||
uses: yesolutions/mirror-action@master
|
||||
with:
|
||||
REMOTE: "https://git.popov.link/popov.link.git"
|
||||
GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
|
||||
GIT_PASSWORD: ${{ secrets.GIT_PASSWORD }}
|
@ -30,7 +30,7 @@ export default {
|
||||
},
|
||||
],
|
||||
plugins: ["prettier-plugin-astro"],
|
||||
printWidth: 120,
|
||||
printWidth: 256,
|
||||
proseWrap: "never",
|
||||
quoteProps: "consistent",
|
||||
requirePragma: false,
|
||||
|
24
.renovaterc
24
.renovaterc
@ -1,24 +0,0 @@
|
||||
{
|
||||
"assignees": [
|
||||
"valentineus"
|
||||
],
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":disableDependencyDashboard"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "all digest updates",
|
||||
"groupSlug": "all-digest",
|
||||
"matchPackagePatterns": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch",
|
||||
"pin",
|
||||
"digest"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
7
LICENSE.txt
Normal file
7
LICENSE.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2025 Valentin Popov <valentin@popov.link>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
53
README.md
53
README.md
@ -1,49 +1,54 @@
|
||||
# Personal site
|
||||
# popov.link
|
||||
|
||||
This is my main site. The site publishes articles, useful information and notes. Also, the site serves as a hosting of free and personal images.
|
||||
[](https://512kb.club)
|
||||
|
||||
Principles of site development:
|
||||
Personal website source code built with [Astro](https://astro.build/).
|
||||
|
||||
- Lightness and minimalism. The site is designed to store information, it's not a heavy application;
|
||||
## Requirements
|
||||
|
||||
- Maximum cross-platform. Information should be read from any device and software;
|
||||
|
||||
- Focusing on content perception. Only useful information, nothing superfluous;
|
||||
|
||||
- No JS on the site. The site should be completely safe for the user;
|
||||
- Node.js v22 or later
|
||||
- npm v11 or later
|
||||
|
||||
## Development
|
||||
|
||||
To start a local server for development, you need:
|
||||
|
||||
1. Clone the source repository:
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
git clone "https://github.com/valentineus/valentineus.github.io.git"
|
||||
npm ci
|
||||
```
|
||||
|
||||
2. Install packages:
|
||||
2. Start the development server:
|
||||
|
||||
```bash
|
||||
cd valentineus.github.io && bundle update
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. Start the server:
|
||||
3. Open your browser and go to http://localhost:3000 to view changes live.
|
||||
|
||||
## Build & Preview
|
||||
|
||||
- To build the project for production:
|
||||
|
||||
```bash
|
||||
bundle exec jekyll serve --host "${IP}" --port "${PORT}" --trace
|
||||
npm run build
|
||||
```
|
||||
|
||||
Please note in the executable command uses environment variables `IP` and `PORT`.
|
||||
- To preview the production build locally:
|
||||
|
||||
## License hosted material
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
<img width="256px" alt="CC BY-NC 3.0 License" src="https://raw.githubusercontent.com/valentineus/valentineus.github.io/master/assets/images/87624cb5-4a8f-4be4-90b6-0ec5b9a90333.png" />
|
||||
## Project Info
|
||||
|
||||
Material on the site is published on the CC BY-NC 3.0 license.
|
||||
- Issues: [GitHub](https://github.com/valentineus/popov.link/issues)
|
||||
- Read-only mirror: [git.popov.link](https://git.popov.link/popov.link/)
|
||||
- Maintained by [Valentin Popov](mailto:valentin@popov.link)
|
||||
|
||||
## Source Code License
|
||||
## Comments
|
||||
|
||||
<img width="256px" alt="MIT License" src="https://raw.githubusercontent.com/valentineus/valentineus.github.io/master/assets/images/7d05cad0-d553-42c7-be1f-7007926ba720.png" />
|
||||
Comments on the site are powered by [giscus.app](https://giscus.app) and stored in [GitHub Discussions](https://github.com/valentineus/popov.link/discussions).
|
||||
|
||||
[MIT](LICENSE.txt). Copyright (c) [Valentin Popov](https://valentineus.link/).
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](LICENSE.txt).
|
||||
|
@ -1,21 +1,19 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import { remarkReadingTime } from "./src/plugins/remarkReadingTime";
|
||||
import ogImages from "./src/integrations/ogImages";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
|
||||
export default defineConfig({
|
||||
site: "https://popov.link",
|
||||
output: "static",
|
||||
integrations: [sitemap()],
|
||||
integrations: [sitemap(), ogImages()],
|
||||
build: {
|
||||
inlineStylesheets: "always",
|
||||
},
|
||||
markdown: {
|
||||
remarkPlugins: [remarkReadingTime],
|
||||
},
|
||||
redirects: {
|
||||
"/blog": {
|
||||
destination: "/",
|
||||
status: 301,
|
||||
shikiConfig: {
|
||||
theme: "vitesse-dark",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
5073
package-lock.json
generated
5073
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@ -1,10 +1,17 @@
|
||||
{
|
||||
"name": "website",
|
||||
"type": "module",
|
||||
"version": "2024.10.24",
|
||||
"version": "2025.01.24",
|
||||
"private": true,
|
||||
"packageManager": "npm@11.4.2",
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"IE 11"
|
||||
],
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"check": "astro check",
|
||||
@ -14,20 +21,28 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/rss": "^4.0.9",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"astro": "^4.16.7",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cssnano": "^7.0.6",
|
||||
"cssnano-preset-advanced": "^7.0.6",
|
||||
"@astrojs/rss": "^4.0.12",
|
||||
"@astrojs/sitemap": "^3.4.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"astro": "^5.9.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cssnano": "^7.0.7",
|
||||
"cssnano-preset-advanced": "^7.0.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"geist": "^1.4.2",
|
||||
"globby": "^14.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"sass": "^1.80.4",
|
||||
"typescript": "^5.6.3"
|
||||
"sass": "^1.89.1",
|
||||
"satori": "^0.15.2",
|
||||
"satori-html": "^0.3.2",
|
||||
"schema-dts": "^1.1.5",
|
||||
"sharp": "^0.34.2",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-astro": "^0.14.1"
|
||||
}
|
||||
}
|
||||
|
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
Before Width: | Height: | Size: 749 B |
BIN
public/google-touch-icon.png
Normal file
BIN
public/google-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 389 KiB |
BIN
public/images/photo.png
Normal file
BIN
public/images/photo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 389 KiB |
2
public/images/preview/.gitignore
vendored
Normal file
2
public/images/preview/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
24
public/manifest.json
Normal file
24
public/manifest.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Valentin Popov's Blog",
|
||||
"short_name": "popov.link",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "google-touch-icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ffffff",
|
||||
"display": "fullscreen"
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
endpoint: "https://code.popov.link",
|
||||
gitAuthor: "RenovateBot <renovatebot@noreply.localhost>",
|
||||
optimizeForDisabled: true,
|
||||
platform: "gitea",
|
||||
};
|
BIN
src/assets/JetBrainsMono/JetBrainsMono-Bold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-BoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraBold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraBoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraLight.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraLightItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-Italic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Italic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-Light.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Light.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-LightItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-LightItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-Medium.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Medium.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-MediumItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-Regular.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Regular.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-SemiBold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-SemiBold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-SemiBoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-Thin.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-Thin.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMono-ThinItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMono-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Bold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-BoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraBold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraBoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraLight.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraLightItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Italic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Italic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Light.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Light.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-LightItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-LightItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Medium.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Medium.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-MediumItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Regular.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Regular.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-SemiBold.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-SemiBold.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-SemiBoldItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Thin.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-Thin.ttf
Normal file
Binary file not shown.
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ThinItalic.ttf
Normal file
BIN
src/assets/JetBrainsMono/JetBrainsMonoNL-ThinItalic.ttf
Normal file
Binary file not shown.
@ -1,17 +1 @@
|
||||
---
|
||||
type Props = {
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
const path = Astro.url.pathname;
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- AppMetrix -->
|
||||
<script is:inline src="https://appmetrix.com/pixel/T5X0z12SoASBV8Dv"></script>
|
||||
|
||||
<!-- GoatCounter -->
|
||||
<script is:inline data-goatcounter="https://analytics.popov.link/count" src="//gc.zgo.at/count.js"></script>
|
||||
<noscript>
|
||||
<img alt="pixel" src={`https://analytics.popov.link/count?p=${encodeURI(path)}&t=${encodeURI(title)}`} />
|
||||
</noscript>
|
||||
<script is:inline defer src="https://appmetrix.com/pixel/T5X0z12SoASBV8Dv"></script>
|
||||
|
@ -15,6 +15,7 @@ const theme = "transparent_dark";
|
||||
|
||||
<script
|
||||
is:inline
|
||||
defer
|
||||
src="https://giscus.app/client.js"
|
||||
data-category-id={categoryId}
|
||||
data-category={category}
|
||||
|
@ -1,14 +1,22 @@
|
||||
---
|
||||
import type { WithContext, Thing } from "schema-dts";
|
||||
import JsonLd from "./JsonLd.astro";
|
||||
|
||||
type Props = {
|
||||
readonly description: string;
|
||||
readonly preview: string;
|
||||
readonly schema: WithContext<Thing>;
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
const { description, title } = Astro.props;
|
||||
const { description, preview, schema, title } = Astro.props;
|
||||
|
||||
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||
const previewUrl = new URL(preview, Astro.site);
|
||||
---
|
||||
|
||||
<head>
|
||||
<!-- Meta Tags -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
|
||||
@ -18,7 +26,29 @@ const { description, title } = Astro.props;
|
||||
|
||||
<link href="/feed.xml" rel="alternate" title="RSS" type="application/atom+xml" />
|
||||
<link href="/sitemap-index.xml" rel="sitemap" />
|
||||
<link href={canonicalURL} rel="canonical" />
|
||||
<link href={canonicalUrl} rel="canonical" />
|
||||
|
||||
<title>{title}</title>
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={previewUrl} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
|
||||
<!-- Twitter Cards -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={previewUrl} />
|
||||
|
||||
<JsonLd schema={schema} />
|
||||
</head>
|
||||
|
16
src/components/Header.astro
Normal file
16
src/components/Header.astro
Normal file
@ -0,0 +1,16 @@
|
||||
<style lang="scss">
|
||||
a {
|
||||
margin-right: 1.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<header>
|
||||
<nav aria-label="Navigation">
|
||||
<a href="/" lang="en" aria-label="Home">Home</a>
|
||||
<a href="/blog/" lang="en" aria-label="Blog">Blog</a>
|
||||
</nav>
|
||||
</header>
|
20
src/components/Icons/Email.astro
Normal file
20
src/components/Icons/Email.astro
Normal file
@ -0,0 +1,20 @@
|
||||
<style lang="scss">
|
||||
@use "../../scss/variables" as *;
|
||||
|
||||
a {
|
||||
color: $colorText;
|
||||
display: inline-block;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="mailto:valentin@popov.link" title="E-Mail" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="E-Mail" aria-hidden="true">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</a>
|
22
src/components/Icons/GitHub.astro
Normal file
22
src/components/Icons/GitHub.astro
Normal file
@ -0,0 +1,22 @@
|
||||
<style lang="scss">
|
||||
@use "../../scss/variables" as *;
|
||||
|
||||
a {
|
||||
color: $colorText;
|
||||
display: inline-block;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="https://github.com/valentineus" title="GitHub" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="GitHub" aria-hidden="true">
|
||||
<path
|
||||
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
21
src/components/Icons/LinkedIn.astro
Normal file
21
src/components/Icons/LinkedIn.astro
Normal file
@ -0,0 +1,21 @@
|
||||
<style lang="scss">
|
||||
@use "../../scss/variables" as *;
|
||||
|
||||
a {
|
||||
color: $colorText;
|
||||
display: inline-block;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="https://www.linkedin.com/in/valentineus/" title="LinkedIn" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="LinkedIn" aria-hidden="true">
|
||||
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path>
|
||||
<rect x="2" y="9" width="4" height="12"></rect>
|
||||
<circle cx="4" cy="4" r="2"></circle>
|
||||
</svg>
|
||||
</a>
|
17
src/components/Icons/RSS.astro
Normal file
17
src/components/Icons/RSS.astro
Normal file
@ -0,0 +1,17 @@
|
||||
<style lang="scss">
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="/feed.xml" title="RSS Feed" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="RSS Feed" aria-hidden="true">
|
||||
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
||||
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
||||
<circle cx="5" cy="19" r="1"></circle>
|
||||
</svg>
|
||||
</a>
|
13
src/components/JsonLd.astro
Normal file
13
src/components/JsonLd.astro
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
import type { WithContext, Thing } from "schema-dts";
|
||||
|
||||
type Props = {
|
||||
readonly schema: WithContext<Thing>;
|
||||
};
|
||||
|
||||
const { schema } = Astro.props;
|
||||
const json = JSON.stringify(schema);
|
||||
---
|
||||
|
||||
<!-- JSON-LD -->
|
||||
<script is:inline type="application/ld+json" set:html={json} />
|
@ -1,35 +0,0 @@
|
||||
---
|
||||
type Props = {
|
||||
readonly nextUrl?: string;
|
||||
readonly prevUrl?: string;
|
||||
};
|
||||
|
||||
const { nextUrl, prevUrl } = Astro.props;
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0 2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
{
|
||||
prevUrl && (
|
||||
<span>
|
||||
<a href={prevUrl}>< Prev</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
nextUrl && (
|
||||
<span>
|
||||
<a href={nextUrl}>Next ></a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
40
src/components/PostElement.astro
Normal file
40
src/components/PostElement.astro
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
import { type CollectionEntry } from "astro:content";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
type Props = {
|
||||
readonly post: CollectionEntry<"blog">;
|
||||
};
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { remarkPluginFrontmatter } = await post.render();
|
||||
|
||||
const formattedDate = dayjs(post.data.datePublished.toString()).format("MMMM DD, YYYY");
|
||||
const datePublished = post.data.datePublished.toISOString();
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@use "../scss/variables" as *;
|
||||
|
||||
a {
|
||||
color: $colorText;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: $fontSizeBase * 0.75;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<li>
|
||||
<article>
|
||||
<a href={`/blog/${post.slug}`} lang={post.data.lang}>{post.data.title}</a>
|
||||
<div>
|
||||
<small>
|
||||
<time datetime={datePublished} lang="en">{formattedDate}</time>
|
||||
<span>•</span>
|
||||
<span>{remarkPluginFrontmatter.minutesRead}</span>
|
||||
</small>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
@ -1,49 +0,0 @@
|
||||
---
|
||||
import { type CollectionEntry } from "astro:content";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
type Props = {
|
||||
readonly post: CollectionEntry<"blog">;
|
||||
};
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { remarkPluginFrontmatter } = await post.render();
|
||||
const formattedDate = dayjs(post.data.pubDate.toString()).format("MMMM DD, YYYY");
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@import "../scss/_variables.scss";
|
||||
|
||||
a {
|
||||
color: $colorText;
|
||||
display: block;
|
||||
padding-bottom: 3rem;
|
||||
|
||||
&:visited {
|
||||
color: $colorText;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $colorBlossom;
|
||||
font-size: 1.25em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
div {
|
||||
font-size: $fontSizeBase * 0.75;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href={`/blog/${post.slug}`}>
|
||||
<article>
|
||||
<div>
|
||||
<time datetime={post.data.pubDate.toISOString()}>{formattedDate}</time>
|
||||
<span>•</span>
|
||||
<span>{remarkPluginFrontmatter.minutesRead}</span>
|
||||
</div>
|
||||
<h2>{post.data.title}</h2>
|
||||
<p>{post.data.description}</p>
|
||||
</article>
|
||||
</a>
|
43
src/components/Sections/LatestPosts.astro
Normal file
43
src/components/Sections/LatestPosts.astro
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import dayjs from "dayjs";
|
||||
import RSSIcon from "../Icons/RSS.astro";
|
||||
|
||||
const posts = await getCollection("blog", ({ data }) => {
|
||||
return data.draft !== true;
|
||||
});
|
||||
|
||||
posts.sort((a, b) => b.data.datePublished.getTime() - a.data.datePublished.getTime());
|
||||
|
||||
const latestPosts = posts.slice(0, 5);
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@use "../../scss/variables" as *;
|
||||
|
||||
small {
|
||||
font-size: $fontSizeBase * 0.75;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section>
|
||||
<h2>Latest posts <RSSIcon /></h2>
|
||||
<ul>
|
||||
{
|
||||
latestPosts.map((post) => (
|
||||
<li>
|
||||
<a href={`/blog/${post.slug}`} lang={post.data.lang}>
|
||||
{post.data.title}
|
||||
</a>
|
||||
|
||||
<small>
|
||||
<time datetime={post.data.datePublished.toISOString()} lang="en">
|
||||
{dayjs(post.data.datePublished.toString()).format("MMMM DD, YYYY")}
|
||||
</time>
|
||||
</small>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
19
src/components/Sections/SocialLinks.astro
Normal file
19
src/components/Sections/SocialLinks.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
import GitHubIcon from "../Icons/GitHub.astro";
|
||||
import LinkedInIcon from "../Icons/LinkedIn.astro";
|
||||
import EmailIcon from "../Icons/Email.astro";
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section>
|
||||
<div>
|
||||
<GitHubIcon />
|
||||
<LinkedInIcon />
|
||||
<EmailIcon />
|
||||
</div>
|
||||
</section>
|
7
src/components/Sections/Welcome.astro
Normal file
7
src/components/Sections/Welcome.astro
Normal file
@ -0,0 +1,7 @@
|
||||
<section>
|
||||
<div>
|
||||
<h1>Hi, I'm Valentin 👋</h1>
|
||||
<p>I'm a professional software developer currently working as a project manager and team lead. On my personal website, I share thoughts on tech, leadership, and digital life.</p>
|
||||
<p>Welcome, and feel free to explore!</p>
|
||||
</div>
|
||||
</section>
|
29
src/config.ts
Normal file
29
src/config.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const config = {
|
||||
author: {
|
||||
name: "Valentin Popov",
|
||||
email: "valentin@popov.link",
|
||||
url: "https://popov.link/",
|
||||
sameAs: ["https://www.linkedin.com/in/valentineus/", "https://github.com/valentineus"],
|
||||
},
|
||||
|
||||
// Open Graph
|
||||
og: {
|
||||
color: {
|
||||
bg: "#181818",
|
||||
bgCode: "#3b3d42",
|
||||
blossom: "#6da13f",
|
||||
text: "#dee2e6",
|
||||
},
|
||||
defaultPreview: "/images/photo.png",
|
||||
dimensions: {
|
||||
height: 630,
|
||||
width: 1200,
|
||||
},
|
||||
fonts: {
|
||||
bold: "./src/assets/JetBrainsMono/JetBrainsMono-Bold.ttf",
|
||||
regular: "./src/assets/JetBrainsMono/JetBrainsMono-Regular.ttf",
|
||||
},
|
||||
photo: "./public/images/photo.png",
|
||||
website: "popov.link",
|
||||
},
|
||||
};
|
@ -1,8 +1,10 @@
|
||||
---
|
||||
basedOn: "https://adrianhenke.wordpress.com/2008/12/05/create-lib-file-from-dll/"
|
||||
title: 'Create ".lib" file from ".dll" (archive)'
|
||||
author: "Adrian Henke"
|
||||
pubDate: "2023-05-04"
|
||||
description: "Learn how to generate a *.lib file from a *.dll with this comprehensive guide. Using the Visual Studio Command Prompt and Microsoft's recommended tools, this article walks you through the steps for a seamless process. Perfect for developers working with 3rd party win dll's."
|
||||
datePublished: "2023-05-04"
|
||||
dateModified: "2023-05-04"
|
||||
lang: "en"
|
||||
---
|
||||
|
||||
> This's a copy of a non-my post. The original article [is here](https://adrianhenke.wordpress.com/2008/12/05/create-lib-file-from-dll/) ([archive](https://web.archive.org/web/20161118122539/https://adrianhenke.wordpress.com/2008/12/05/create-lib-file-from-dll/)).
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
title: "Горячая перезагрузка ElectronJS приложения"
|
||||
author: "Valentin Popov"
|
||||
pubDate: "2019-08-15"
|
||||
description: "Руководство по автоматической перезагрузке приложений на Electron с помощью пакетов electron-reload и electron-webpack. Обход проблем с совместимостью и использование HMR для renderer процесса."
|
||||
datePublished: "2019-08-15"
|
||||
dateModified: "2019-08-15"
|
||||
lang: "ru"
|
||||
---
|
||||
|
||||
## Main процесс
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
title: "Example Content"
|
||||
author: "Example User"
|
||||
pubDate: "2018-01-01"
|
||||
description: "Howdy! This is an example blog post that shows several types of HTML content supported in this theme."
|
||||
datePublished: "2018-01-01"
|
||||
dateModified: "2018-01-01"
|
||||
lang: "en"
|
||||
draft: true
|
||||
---
|
||||
|
||||
@ -16,12 +17,12 @@ Etiam porta **sem malesuada magna** mollis euismod. Cras mattis consectetur puru
|
||||
|
||||
HTML defines a long list of available inline tags, a complete list of which can be found on the [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
||||
|
||||
- **To bold text**, use `<strong>`.
|
||||
- _To italicize text_, use `<em>`.
|
||||
- Abbreviations, like <abbr title="HyperText Markup Langage">HTML</abbr> should use `<abbr>`, with an optional `title` attribute for the full phrase.
|
||||
- Citations, like <cite>— Mark otto</cite>, should use `<cite>`.
|
||||
- <del>Deleted</del> text should use `<del>` and <ins>inserted</ins> text should use `<ins>`.
|
||||
- Superscript <sup>text</sup> uses `<sup>` and subscript <sub>text</sub> uses `<sub>`.
|
||||
- **To bold text**, use `<strong>`.
|
||||
- _To italicize text_, use `<em>`.
|
||||
- Abbreviations, like <abbr title="HyperText Markup Langage">HTML</abbr> should use `<abbr>`, with an optional `title` attribute for the full phrase.
|
||||
- Citations, like <cite>— Mark otto</cite>, should use `<cite>`.
|
||||
- <del>Deleted</del> text should use `<del>` and <ins>inserted</ins> text should use `<ins>`.
|
||||
- Superscript <sup>text</sup> uses `<sup>` and subscript <sub>text</sub> uses `<sub>`.
|
||||
|
||||
Most of these elements are styled by browsers with few modifications on our part.
|
||||
|
||||
@ -58,9 +59,9 @@ Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. N
|
||||
|
||||
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
|
||||
|
||||
- Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
|
||||
- Donec id elit non mi porta gravida at eget metus.
|
||||
- Nulla vitae elit libero, a pharetra augue.
|
||||
- Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
|
||||
- Donec id elit non mi porta gravida at eget metus.
|
||||
- Nulla vitae elit libero, a pharetra augue.
|
||||
|
||||
Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
title: 'Получение исходного кода "Chromium Projects"'
|
||||
author: "Valentin Popov"
|
||||
pubDate: "2012-01-30"
|
||||
description: "Изучение исходных кодов Chromium: подготовка системы и установка необходимых программных компонентов. Руководство для начинающих разработчиков. Получите инструкции по установке Microsoft Visual Studio, Cygwin, Python и других инструментов. Действительно на январь-февраль 2012 года."
|
||||
datePublished: "2012-01-30"
|
||||
dateModified: "2012-01-30"
|
||||
lang: "ru"
|
||||
---
|
||||
|
||||
> Перенос [оригинальной статьи](https://adeptus-mechanicus.blogspot.com/2012/01/chromium-projects.html) 2012 года из моего [старого блога](https://adeptus-mechanicus.blogspot.com/) ([зеркало](https://web.archive.org/web/20160217052148/http://adeptus-mechanicus.blogspot.com/)).
|
||||
@ -15,26 +16,26 @@ description: "Изучение исходных кодов Chromium: подго
|
||||
|
||||
Для начала немного введения. Весь процесс от подготовки системы, до работы с исходными кодами я условно разделю на четыре пункта, это:
|
||||
|
||||
- Подготовка операционной системы, установка стандартных программных комплектов разработчика;
|
||||
- Установка и настройка программы "Cygwin";
|
||||
- Установка и настройка пакета "depot_tools";
|
||||
- Получение и подготовка для работы исходных кодов "Chromium";
|
||||
- Подготовка операционной системы, установка стандартных программных комплектов разработчика;
|
||||
- Установка и настройка программы "Cygwin";
|
||||
- Установка и настройка пакета "depot_tools";
|
||||
- Получение и подготовка для работы исходных кодов "Chromium";
|
||||
|
||||
## Подготовка начального набора программ
|
||||
|
||||
Ниже приведу список и краткое описание программного обеспечения, которое требуется установить перед работой с исходными кодами:
|
||||
|
||||
- "**Microsoft Visual Studio 2010**" — Среда разработки, требуется для работы с исходным кодом. Имеется возможность использовать Microsoft Visual Studio 2008 и Microsoft Visual C++ Express 2010'го и 2008'го годов соответственно. Бесплатные Express версии продуктов можно взять на [официальном сайте](http://www.microsoft.com/express) Microsoft.
|
||||
- "**Microsoft Visual Studio 2010**" — Среда разработки, требуется для работы с исходным кодом. Имеется возможность использовать Microsoft Visual Studio 2008 и Microsoft Visual C++ Express 2010'го и 2008'го годов соответственно. Бесплатные Express версии продуктов можно взять на [официальном сайте](http://www.microsoft.com/express) Microsoft.
|
||||
|
||||
- "**Microsoft Windows SDK**" — Пакет для предоставления заголовочных файлов, библиотек, компиляторов и пр. для разработчиков программного обеспечения под операционную систему Windows. Требуется для успешной сборки и компиляции проекта под Windows систему. Бесплатно можно скачать с [официальной страницы](https://www.microsoft.com/downloads/en/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b).
|
||||
- "**Microsoft Windows SDK**" — Пакет для предоставления заголовочных файлов, библиотек, компиляторов и пр. для разработчиков программного обеспечения под операционную систему Windows. Требуется для успешной сборки и компиляции проекта под Windows систему. Бесплатно можно скачать с [официальной страницы](https://www.microsoft.com/downloads/en/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b).
|
||||
|
||||
- "**Microsoft DirectX SDK**" — Пакет с библиотеками мультимедийной подсистемы DirectX. Требуется для успешной сборки и компиляции проекта. Бесплатно доступен на [официальном сайте](http://msdn.microsoft.com/en-us/directx/default.aspx).
|
||||
- "**Microsoft DirectX SDK**" — Пакет с библиотеками мультимедийной подсистемы DirectX. Требуется для успешной сборки и компиляции проекта. Бесплатно доступен на [официальном сайте](http://msdn.microsoft.com/en-us/directx/default.aspx).
|
||||
|
||||
- "**Python 2.x**" — Высокоуровневый язык программирования. Требуется для начальной подготовки исходных кодов. Установка не обязательная, но желательная. Про третью версию Python официальной информации нет, у меня установлены обе версии для опытов и "Path" системы направлен на вторую версию. Установку Python коротко разберу ниже. Python бесплатно можно взять на [официальном сайте](http://python.org/).
|
||||
- "**Python 2.x**" — Высокоуровневый язык программирования. Требуется для начальной подготовки исходных кодов. Установка не обязательная, но желательная. Про третью версию Python официальной информации нет, у меня установлены обе версии для опытов и "Path" системы направлен на вторую версию. Установку Python коротко разберу ниже. Python бесплатно можно взять на [официальном сайте](http://python.org/).
|
||||
|
||||
- "**Cygwin**" — Unix-подобная среда и интерфейс командной строки для систем Microsoft Windows. Требуется для работы с исходными кодами, их подготовки, обновлением и проверки на ошибки. Сам инструмент бесплатно доступен на [официальной странице](http://www.cygwin.com/). Его установку я распишу в следующих пунктах.
|
||||
- "**Cygwin**" — Unix-подобная среда и интерфейс командной строки для систем Microsoft Windows. Требуется для работы с исходными кодами, их подготовки, обновлением и проверки на ошибки. Сам инструмент бесплатно доступен на [официальной странице](http://www.cygwin.com/). Его установку я распишу в следующих пунктах.
|
||||
|
||||
- "**TortoiseSVN**" — Клиент Subversion под систему Windows. Установка не обязательная, но желательная. Требуется для более простого обновления пакета "depot_tools" из официального репозитория SVN от корпорации Google. Бесплатно доступен на [официальной странице](http://tortoisesvn.net/).
|
||||
- "**TortoiseSVN**" — Клиент Subversion под систему Windows. Установка не обязательная, но желательная. Требуется для более простого обновления пакета "depot_tools" из официального репозитория SVN от корпорации Google. Бесплатно доступен на [официальной странице](http://tortoisesvn.net/).
|
||||
|
||||
Над установкой Microsoft Visual Studio, Microsoft Windows SDK и Microsoft DirectX SDK я подробно останавливаться не буду. Установка данных программных комплектов проста, и особой сложности вызывать не должна. В случае возникновения каких-либо проблем, имеются огромные сообщества разработчиков, которые помогут с установкой.
|
||||
|
||||
@ -64,8 +65,8 @@ python --version
|
||||
|
||||
На диске `C:\` я создал каталог `OpenSource`, в нём я создал каталог `ChromiumProjects`, в котором появилось два подкаталога `depot_tools` и `trunk`. Т.е. система каталогов выглядит так:
|
||||
|
||||
- `C:\OpenSource\ChromiumProjects\depot_tools` — Пакет для работы с исходным кодом;
|
||||
- `C:\OpenSource\ChromiumProjects\trunk` — Место хранения исходных кодов;
|
||||
- `C:\OpenSource\ChromiumProjects\depot_tools` — Пакет для работы с исходным кодом;
|
||||
- `C:\OpenSource\ChromiumProjects\trunk` — Место хранения исходных кодов;
|
||||
|
||||
В случае использование других каталогов в вашей системе, используйте собственные пути, подставляя их в мои примеры.
|
||||
|
||||
@ -77,31 +78,31 @@ python --version
|
||||
|
||||
Первым делом получите установочный файл последней версии по этой [ссылке](http://cygwin.com/setup.exe). Установочный файл во время установки скачивает необходимые пакеты из интернета. Когда запустите файл, следуйте инструкциям. После выбор зеркала пакетов, Вам покажут список доступных пакетов с зеркала. Требуется найти и отметить для установки следующие пакеты:
|
||||
|
||||
- `apache`;
|
||||
- `bc`;
|
||||
- `bison`;
|
||||
- `curl`;
|
||||
- `diffutils`;
|
||||
- `e2fsprogs`;
|
||||
- `emacs`;
|
||||
- `flex`;
|
||||
- `gcc`;
|
||||
- `gperf`;
|
||||
- `keychain`;
|
||||
- `make`;
|
||||
- `nano`;
|
||||
- `openssh`;
|
||||
- `patch`;
|
||||
- `perl`;
|
||||
- `perl-libwin32`;
|
||||
- `python`;
|
||||
- `rebase`;
|
||||
- `rsync`;
|
||||
- `ruby`;
|
||||
- `subversion`;
|
||||
- `unzip`;
|
||||
- `vim`;
|
||||
- `zip`;
|
||||
- `apache`;
|
||||
- `bc`;
|
||||
- `bison`;
|
||||
- `curl`;
|
||||
- `diffutils`;
|
||||
- `e2fsprogs`;
|
||||
- `emacs`;
|
||||
- `flex`;
|
||||
- `gcc`;
|
||||
- `gperf`;
|
||||
- `keychain`;
|
||||
- `make`;
|
||||
- `nano`;
|
||||
- `openssh`;
|
||||
- `patch`;
|
||||
- `perl`;
|
||||
- `perl-libwin32`;
|
||||
- `python`;
|
||||
- `rebase`;
|
||||
- `rsync`;
|
||||
- `ruby`;
|
||||
- `subversion`;
|
||||
- `unzip`;
|
||||
- `vim`;
|
||||
- `zip`;
|
||||
|
||||
Имена пакетов должны полностью совпадать. Воспользуйтесь поиском по пакетам, включённым в саму программу установки. Это должно сильно облегчить задачу.
|
||||
|
||||
@ -177,9 +178,9 @@ gclient sync
|
||||
|
||||
## Полезные ссылки
|
||||
|
||||
- [http://dev.chromium.org/Home](http://dev.chromium.org/Home) — Официальная страница проекта "The Chromium Projects";
|
||||
- [http://dev.chromium.org/developers/how-tos/build-instructions-windows](http://dev.chromium.org/developers/how-tos/build-instructions-windows) — Официальная страница по подготовке операционной системы Windows, перед работой с исходными кодами;
|
||||
- [http://dev.chromium.org/developers/how-tos/get-the-code](http://dev.chromium.org/developers/how-tos/get-the-code) — Официальная инструкция по получению, настройке и подготовке исходных кодов проекта "The Chromium Projects";
|
||||
- [http://dev.chromium.org/developers/how-tos/install-depot-tools](http://dev.chromium.org/developers/how-tos/install-depot-tools) — Официальная инструкция по установке и настройке пакета "depot_tools";
|
||||
- [http://dev.chromium.org/developers/how-tos/cygwin](http://dev.chromium.org/developers/how-tos/cygwin) — Страница по установки и настройке терминала "Cygwin";
|
||||
- [http://groups.google.com/a/chromium.org/group/chromium-discuss/topics](http://groups.google.com/a/chromium.org/group/chromium-discuss/topics) — Официальная дискуссия разработчиков браузера "Chromium";
|
||||
- [http://dev.chromium.org/Home](http://dev.chromium.org/Home) — Официальная страница проекта "The Chromium Projects";
|
||||
- [http://dev.chromium.org/developers/how-tos/build-instructions-windows](http://dev.chromium.org/developers/how-tos/build-instructions-windows) — Официальная страница по подготовке операционной системы Windows, перед работой с исходными кодами;
|
||||
- [http://dev.chromium.org/developers/how-tos/get-the-code](http://dev.chromium.org/developers/how-tos/get-the-code) — Официальная инструкция по получению, настройке и подготовке исходных кодов проекта "The Chromium Projects";
|
||||
- [http://dev.chromium.org/developers/how-tos/install-depot-tools](http://dev.chromium.org/developers/how-tos/install-depot-tools) — Официальная инструкция по установке и настройке пакета "depot_tools";
|
||||
- [http://dev.chromium.org/developers/how-tos/cygwin](http://dev.chromium.org/developers/how-tos/cygwin) — Страница по установки и настройке терминала "Cygwin";
|
||||
- [http://groups.google.com/a/chromium.org/group/chromium-discuss/topics](http://groups.google.com/a/chromium.org/group/chromium-discuss/topics) — Официальная дискуссия разработчиков браузера "Chromium";
|
||||
|
@ -1,15 +1,16 @@
|
||||
---
|
||||
title: "Установка Moodle в Fedora"
|
||||
author: "Valentin Popov"
|
||||
pubDate: "2018-07-23"
|
||||
description: "Решение проблем установки Moodle из-за SELinux: как настроить правила доступа для устранения ошибок в веб-интерфейсе и при работе с cURL. Практические советы и команды."
|
||||
datePublished: "2018-07-23"
|
||||
dateModified: "2018-07-23"
|
||||
lang: "ru"
|
||||
---
|
||||
|
||||
Во время установки Moodle, сталкиваешься со следующими проблемами:
|
||||
|
||||
- Веб-интерфейс не продолжает установку после настройки базы данных;
|
||||
- Если установить через консольный интерфейс, проявляются артефакты;
|
||||
- Нет доступа к сети, появляется ошибка `unexpected cURL error`.
|
||||
- Веб-интерфейс не продолжает установку после настройки базы данных;
|
||||
- Если установить через консольный интерфейс, проявляются артефакты;
|
||||
- Нет доступа к сети, появляется ошибка `unexpected cURL error`.
|
||||
|
||||
Главная причина, это [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux). Решение, это настроить правила доступа:
|
||||
|
||||
|
@ -1,20 +1,21 @@
|
||||
---
|
||||
title: "Компиляция Rust на TL-MR3020"
|
||||
author: "Valentin Popov"
|
||||
pubDate: "2023-05-01"
|
||||
description: 'Как настроить и оптимизировать проект Rust для кросс-компиляции на TP-Link TL-MR3020 с использованием Fedora Linux 38 и OpenWrt 22.03.4. Шаг за шагом от базового "Hello, World!" до асинхронного TCP сервера.'
|
||||
datePublished: "2023-05-01"
|
||||
dateModified: "2023-05-01"
|
||||
lang: "ru"
|
||||
---
|
||||
|
||||
Информация в статье актуальна для дистрибутива [Fedora Linux 38](https://docs.fedoraproject.org/en-US/releases/f38/), прошивки [OpenWrt 22.03.4](https://openwrt.org/releases/22.03/notes-22.03.4) и устройства [TP-Link TL-MR3020](https://www.tp-link.com/en/home-networking/3g-4g-router/tl-mr3020/) ревизии v3.20.
|
||||
|
||||
Потребуется:
|
||||
|
||||
- Установленный [rustup](https://rustup.rs/) инструментарий.
|
||||
- Установленный пакет [cross-rs](https://github.com/cross-rs/cross) для кросс-компиляции.
|
||||
- Упаковщик исполняемых файлов [upx](https://github.com/upx/upx).
|
||||
- Контейнеризатор [Docker](https://docs.docker.com/engine/install/) (рекомендуется) или [Podman](https://podman.io/getting-started/installation).
|
||||
- SSH подключение к маршрутизатору.
|
||||
- Установленный [SFTP сервер](https://openwrt.org/docs/guide-user/services/nas/sftp.server) на TL-MR3020.
|
||||
- Установленный [rustup](https://rustup.rs/) инструментарий.
|
||||
- Установленный пакет [cross-rs](https://github.com/cross-rs/cross) для кросс-компиляции.
|
||||
- Упаковщик исполняемых файлов [upx](https://github.com/upx/upx).
|
||||
- Контейнеризатор [Docker](https://docs.docker.com/engine/install/) (рекомендуется) или [Podman](https://podman.io/getting-started/installation).
|
||||
- SSH подключение к маршрутизатору.
|
||||
- Установленный [SFTP сервер](https://openwrt.org/docs/guide-user/services/nas/sftp.server) на TL-MR3020.
|
||||
|
||||
> Требуется rustup инструментарий с официального сайта. Rust и Cargo из репозитория дистрибутива не подойдут. Пакет кросс-компиляции требует rustup, который в репозиториях дистрибутива отсутствует.
|
||||
|
||||
@ -166,7 +167,7 @@ curl -L "http://10.0.0.2:3000"
|
||||
|
||||
## Полезные ссылки и источники
|
||||
|
||||
- [Building Rust code for my OpenWrt Wi-Fi router](https://blog.dend.ro/building-rust-for-routers/)
|
||||
- [Cross Compile Rust For OpenWRT](https://www.kiloleaf.com/posts/cross-compile-rust-for-openwrt/)
|
||||
- [Minimizing Rust Binary Size](https://github.com/johnthagen/min-sized-rust)
|
||||
- [Кросс-компиляция программ Rust для запуска на маршрутизаторе](https://dzen.ru/media/nuancesprog.ru/krosskompiliaciia-programm-rust-dlia-zapuska-na-marshrutizatore-5f6457b8bdfa745d402cd1ec)
|
||||
- [Building Rust code for my OpenWrt Wi-Fi router](https://blog.dend.ro/building-rust-for-routers/)
|
||||
- [Cross Compile Rust For OpenWRT](https://www.kiloleaf.com/posts/cross-compile-rust-for-openwrt/)
|
||||
- [Minimizing Rust Binary Size](https://github.com/johnthagen/min-sized-rust)
|
||||
- [Кросс-компиляция программ Rust для запуска на маршрутизаторе](https://dzen.ru/media/nuancesprog.ru/krosskompiliaciia-programm-rust-dlia-zapuska-na-marshrutizatore-5f6457b8bdfa745d402cd1ec)
|
||||
|
@ -3,10 +3,12 @@ import { defineCollection, z } from "astro:content";
|
||||
const blog = defineCollection({
|
||||
type: "content",
|
||||
schema: z.object({
|
||||
author: z.string(),
|
||||
basedOn: z.optional(z.string()),
|
||||
dateModified: z.coerce.date(),
|
||||
datePublished: z.coerce.date(),
|
||||
description: z.string(),
|
||||
draft: z.optional(z.boolean()),
|
||||
pubDate: z.coerce.date(),
|
||||
lang: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
});
|
||||
|
47
src/integrations/ogImages.ts
Normal file
47
src/integrations/ogImages.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { AstroIntegration } from "astro";
|
||||
import { createOgImage } from "../utils/createOgImage";
|
||||
import { globby } from "globby";
|
||||
import fs from "fs/promises";
|
||||
import matter from "gray-matter";
|
||||
import path from "path";
|
||||
|
||||
const postsDir = path.resolve("./src/content/blog");
|
||||
const outDir = path.resolve("./public/images/preview");
|
||||
|
||||
export default function ogImageGenerator(): AstroIntegration {
|
||||
return {
|
||||
name: "og-images",
|
||||
hooks: {
|
||||
"astro:build:setup": async ({ logger }) => {
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
const mdFiles = await globby("*.md", { cwd: postsDir });
|
||||
logger.info(`${mdFiles.length} posts found`);
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
mdFiles.map(async (file) => {
|
||||
const slug = file.replace(/\.md$/, "");
|
||||
const content = await fs.readFile(path.join(postsDir, file), "utf-8");
|
||||
const { data } = matter(content);
|
||||
|
||||
const png = await createOgImage(data.title, data.datePublished);
|
||||
const outPath = path.join(outDir, `${slug}.png`);
|
||||
await fs.writeFile(outPath, png);
|
||||
|
||||
logger.info(`OG image created: ${slug}`);
|
||||
})
|
||||
);
|
||||
|
||||
results.forEach((r) => {
|
||||
if (r.status === "rejected") {
|
||||
logger.error(`Error for ${r.reason.slug}: ${r.reason.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
const failures = results.filter((r) => r.status === "rejected");
|
||||
if (failures.length) {
|
||||
throw new Error(`Failed to generate OG images for ${failures.length} posts`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@ -1,26 +1,32 @@
|
||||
---
|
||||
import type { WithContext, Thing } from "schema-dts";
|
||||
import Analytics from "../components/Analytics.astro";
|
||||
import Head from "../components/Head.astro";
|
||||
import Header from "../components/Header.astro";
|
||||
import "../scss/global.scss";
|
||||
|
||||
type Props = {
|
||||
readonly description?: string;
|
||||
readonly title?: string;
|
||||
readonly description: string;
|
||||
readonly lang: string;
|
||||
readonly preview: string;
|
||||
readonly schema: WithContext<Thing>;
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
const { description, title } = Astro.props;
|
||||
const { description, lang, preview, schema, title } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="ru">
|
||||
<Head
|
||||
description={description ?? import.meta.env.DEFAULT_DESCRIPTION}
|
||||
title={title ?? import.meta.env.DEFAULT_TITLE}
|
||||
/>
|
||||
<html lang={lang}>
|
||||
<Head title={title} description={description} preview={preview} schema={schema} />
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<section>
|
||||
<Header />
|
||||
</section>
|
||||
|
||||
<slot />
|
||||
</main>
|
||||
<Analytics title={title ?? import.meta.env.DEFAULT_TITLE} />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,17 +1,30 @@
|
||||
---
|
||||
import { config } from "../config";
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import pageSchema from "../utils/schemas/pageSchema";
|
||||
|
||||
const title = "404 — Page Not Found | Valentin Popov";
|
||||
const description = "The page you're looking for doesn't exist!";
|
||||
const preview = config.og.defaultPreview;
|
||||
const lang = "en";
|
||||
|
||||
const schema = pageSchema({
|
||||
siteUrl: new URL("/", Astro.site).toString(),
|
||||
page: "/404",
|
||||
title,
|
||||
description,
|
||||
lang,
|
||||
});
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div style="text-align:center;">
|
||||
<Layout title={title} description={description} preview={preview} lang={lang} schema={schema}>
|
||||
<div style={{ "text-align": "center" }}>
|
||||
<h1>404</h1>
|
||||
<p><strong>Page not found</strong></p>
|
||||
<p>
|
||||
<small>
|
||||
If you see this message, please
|
||||
<a href=`mailto:valentin@popov.link?subject=${encodeURIComponent('I found a broken page')}`>
|
||||
let me know
|
||||
</a>
|
||||
<a href=`mailto:valentin@popov.link?subject=${encodeURIComponent('I found a broken page')}`>let me know</a>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,33 +0,0 @@
|
||||
---
|
||||
import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import Pagination from "../components/Pagination.astro";
|
||||
import PostSummary from "../components/PostSummary.astro";
|
||||
|
||||
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||
|
||||
export const getStaticPaths = (async ({ paginate }) => {
|
||||
const posts = await getCollection("blog", ({ data }) => {
|
||||
return data.draft !== true;
|
||||
});
|
||||
|
||||
posts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
||||
|
||||
return paginate(posts, {
|
||||
pageSize: 10,
|
||||
});
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { page } = Astro.props;
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<section style={{ "margin-top": "3rem" }}>
|
||||
{page.data.map((post) => <PostSummary post={post} />)}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Pagination nextUrl={page.url.next} prevUrl={page.url.prev} />
|
||||
</section>
|
||||
</Layout>
|
@ -2,6 +2,7 @@
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import Comments from "../../components/Comments.astro";
|
||||
import Layout from "../../layouts/BaseLayout.astro";
|
||||
import blogPostSchema from "../../utils/schemas/blogPostSchema";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
type Props = CollectionEntry<"blog">;
|
||||
@ -18,35 +19,55 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const post = Astro.props;
|
||||
|
||||
const { Content, remarkPluginFrontmatter } = await post.render();
|
||||
const formattedDate = dayjs(post.data.pubDate.toString()).format("MMMM DD, YYYY");
|
||||
|
||||
const description = post.data.description;
|
||||
const isBasedOn = post.data.basedOn;
|
||||
const lang = post.data.lang;
|
||||
const preview = `/images/preview/${post.slug}.png`;
|
||||
const slug = post.slug;
|
||||
const title = post.data.title;
|
||||
|
||||
const dateModified = post.data.dateModified?.toISOString();
|
||||
const datePublished = post.data.datePublished.toISOString();
|
||||
const formattedDate = dayjs(post.data.datePublished.toString()).format("MMMM DD, YYYY");
|
||||
|
||||
const schema = blogPostSchema({
|
||||
siteUrl: new URL("/", Astro.site).toString(),
|
||||
dateModified,
|
||||
datePublished,
|
||||
description,
|
||||
isBasedOn,
|
||||
lang,
|
||||
preview,
|
||||
slug,
|
||||
title,
|
||||
});
|
||||
---
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../scss/_variables.scss";
|
||||
@use "../../scss/variables" as *;
|
||||
|
||||
p {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout description={post.data.description} title={post.data.title}>
|
||||
<Layout title={title} description={description} preview={preview} lang={lang} schema={schema}>
|
||||
<article>
|
||||
<section>
|
||||
<header>
|
||||
<h1>{title}</h1>
|
||||
|
||||
<p>
|
||||
<small>
|
||||
Posted
|
||||
<time datetime={post.data.pubDate.toISOString()}>{formattedDate}</time>
|
||||
by {post.data.author}
|
||||
<time datetime={datePublished} lang="en">{formattedDate}</time>
|
||||
<span> • </span>
|
||||
<span>{remarkPluginFrontmatter.minutesRead}</span>
|
||||
</small>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>{post.data.title}</h1>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<Content />
|
||||
|
61
src/pages/blog/index.astro
Normal file
61
src/pages/blog/index.astro
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import { config } from "../../config";
|
||||
import { getCollection } from "astro:content";
|
||||
import blogSchema from "../../utils/schemas/blogSchema";
|
||||
import Layout from "../../layouts/BaseLayout.astro";
|
||||
import PostElement from "../../components/PostElement.astro";
|
||||
import RSSIcon from "../../components/Icons/RSS.astro";
|
||||
|
||||
const posts = await getCollection("blog", ({ data }) => {
|
||||
return data.draft !== true;
|
||||
});
|
||||
|
||||
posts.sort((a, b) => b.data.datePublished.getTime() - a.data.datePublished.getTime());
|
||||
|
||||
const postsByYear = posts.reduce<Record<string, CollectionEntry<"blog">[]>>((acc, post) => {
|
||||
const year = post.data.datePublished.getFullYear().toString();
|
||||
if (!acc[year]) {
|
||||
acc[year] = [];
|
||||
}
|
||||
acc[year].push(post);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a));
|
||||
|
||||
const title = "Valentin Popov's Blog | Software Development, Leadership & Open-Source";
|
||||
const description = "Explore Valentin Popov's blog on software development, tech leadership, and open-source experiments. Stay updated with in-depth tutorials and expert insights.";
|
||||
const preview = config.og.defaultPreview;
|
||||
const lang = "en";
|
||||
|
||||
const schema = blogSchema({
|
||||
siteUrl: new URL("/", Astro.site).toString(),
|
||||
title,
|
||||
posts,
|
||||
});
|
||||
---
|
||||
|
||||
<Layout title={title} description={description} preview={preview} lang={lang} schema={schema}>
|
||||
<section>
|
||||
<h1>
|
||||
Blog posts
|
||||
<RSSIcon />
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
{
|
||||
years.map((year) => (
|
||||
<div>
|
||||
<h2>{year}</h2>
|
||||
<ul>
|
||||
{postsByYear[year].map((post) => (
|
||||
<PostElement post={post} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</Layout>
|
@ -2,13 +2,16 @@ import { getCollection } from "astro:content";
|
||||
import rss from "@astrojs/rss";
|
||||
|
||||
export async function GET(context) {
|
||||
const title = "RSS Feed | Valentin Popov Blog";
|
||||
const description = "Follow the latest posts from Valentin Popov via RSS.";
|
||||
|
||||
const posts = await getCollection("blog", ({ data }) => {
|
||||
return data.draft !== true;
|
||||
});
|
||||
|
||||
return rss({
|
||||
customData: `<language>ru-ru</language>`,
|
||||
description: import.meta.env.DEFAULT_DESCRIPTION,
|
||||
customData: `<language>en</language>`,
|
||||
description: description,
|
||||
items: posts.map((post) => ({
|
||||
customData: post.data.customData,
|
||||
description: post.data.description,
|
||||
@ -17,6 +20,6 @@ export async function GET(context) {
|
||||
title: post.data.title,
|
||||
})),
|
||||
site: context.site,
|
||||
title: import.meta.env.DEFAULT_TITLE,
|
||||
title: title,
|
||||
});
|
||||
}
|
||||
|
27
src/pages/index.astro
Normal file
27
src/pages/index.astro
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
import { config } from "../config";
|
||||
import LatestPostsSection from "../components/Sections/LatestPosts.astro";
|
||||
import Layout from "../layouts/BaseLayout.astro";
|
||||
import pageSchema from "../utils/schemas/pageSchema";
|
||||
import SocialLinksSection from "../components/Sections/SocialLinks.astro";
|
||||
import WelcomeSection from "../components/Sections/Welcome.astro";
|
||||
|
||||
const title = "Valentin Popov – Software Developer & Team Lead | Tech Insights";
|
||||
const description = "Blog by Valentin Popov — software developer and team lead writing about code, side projects, digital tools, and fun experiments.";
|
||||
const preview = config.og.defaultPreview;
|
||||
const lang = "en";
|
||||
|
||||
const schema = pageSchema({
|
||||
siteUrl: new URL("/", Astro.site).toString(),
|
||||
page: "/",
|
||||
title,
|
||||
description,
|
||||
lang,
|
||||
});
|
||||
---
|
||||
|
||||
<Layout title={title} description={description} preview={preview} lang={lang} schema={schema}>
|
||||
<WelcomeSection />
|
||||
<SocialLinksSection />
|
||||
<LatestPostsSection />
|
||||
</Layout>
|
@ -1,3 +1,5 @@
|
||||
@use "variables" as *;
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
|
@ -1,5 +1,7 @@
|
||||
@media print {
|
||||
body {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@ -18,4 +20,25 @@
|
||||
img {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
* {
|
||||
background: none;
|
||||
border-color: #000;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
background: none;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
$colorBg: #212529;
|
||||
$colorBgAlt: hwb(0deg 0% 100% / 20%);
|
||||
$colorBg: #181818;
|
||||
$colorBgAlt: rgba(0, 0, 0, 0.2);
|
||||
$colorBgCode: #3b3d42;
|
||||
$colorBlossom: #6da13f;
|
||||
$colorFade: #598332;
|
||||
|
@ -1,3 +1,3 @@
|
||||
@import "variables";
|
||||
@import "framework";
|
||||
@import "print";
|
||||
@use "variables";
|
||||
@use "framework";
|
||||
@use "print";
|
||||
|
52
src/utils/createOgImage.ts
Normal file
52
src/utils/createOgImage.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { config } from "../config";
|
||||
import { html } from "satori-html";
|
||||
import { resources } from "./ogResources";
|
||||
import { Resvg } from "@resvg/resvg-js";
|
||||
import dayjs from "dayjs";
|
||||
import satori from "satori";
|
||||
|
||||
export async function createOgImage(title: string, datePublished: Date): Promise<Buffer> {
|
||||
const formattedDate = dayjs(datePublished).format("MMMM DD, YYYY");
|
||||
|
||||
const markup = await satori(
|
||||
html(`
|
||||
<div tw="flex flex-col w-full h-full" style="background-color: ${config.og.color.bg}">
|
||||
<div tw="flex flex-col w-full h-4/5 p-10 justify-center">
|
||||
<div tw="text-2xl mb-6" style="color: ${config.og.color.text}">${formattedDate}</div>
|
||||
<div tw="flex text-6xl w-full font-bold" style="color: ${config.og.color.text}">${title}</div>
|
||||
</div>
|
||||
<div tw="w-full h-1/5 flex p-10 items-center justify-between text-2xl" style="border-top: 1px solid ${config.og.color.bgCode}">
|
||||
<div tw="flex items-center">
|
||||
<span tw="ml-3" style="color: ${config.og.color.text}">${config.og.website.toLocaleUpperCase()}</span>
|
||||
</div>
|
||||
<div tw="flex items-center">
|
||||
<img src="${resources.photoBase64}" tw="w-15 h-15 rounded-full" />
|
||||
<div tw="flex flex-col ml-4">
|
||||
<span style="color: ${config.og.color.text}">${config.author.name}</span>
|
||||
<span style="color: ${config.og.color.blossom}">${config.author.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`),
|
||||
{
|
||||
width: config.og.dimensions.width,
|
||||
height: config.og.dimensions.height,
|
||||
fonts: [
|
||||
{
|
||||
name: "Inter",
|
||||
data: resources.fonts.regular,
|
||||
weight: 400,
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: resources.fonts.bold,
|
||||
weight: 700,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const image = new Resvg(markup, { fitTo: { mode: "width", value: config.og.dimensions.width } });
|
||||
return image.render().asPng();
|
||||
}
|
15
src/utils/ogResources.ts
Normal file
15
src/utils/ogResources.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { config } from "../config";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
|
||||
export const resources = {
|
||||
fonts: {
|
||||
regular: await fs.readFile(path.resolve(config.og.fonts.regular)),
|
||||
bold: await fs.readFile(path.resolve(config.og.fonts.bold)),
|
||||
},
|
||||
photoBase64: await (async () => {
|
||||
const buf = await fs.readFile(path.resolve(config.og.photo));
|
||||
return "data:image/png;base64," + (await sharp(buf).resize(120, 120).png({ quality: 95 }).toBuffer()).toString("base64");
|
||||
})(),
|
||||
};
|
37
src/utils/schemas/blogPostSchema.ts
Normal file
37
src/utils/schemas/blogPostSchema.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { WithContext, BlogPosting } from "schema-dts";
|
||||
import { config } from "../../config";
|
||||
|
||||
export type BlogPostSchemaParams = {
|
||||
readonly dateModified: string;
|
||||
readonly datePublished: string;
|
||||
readonly description: string;
|
||||
readonly isBasedOn?: string;
|
||||
readonly lang: string;
|
||||
readonly preview: string;
|
||||
readonly siteUrl: string;
|
||||
readonly slug: string;
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, slug, title, description, preview, datePublished, dateModified, lang, isBasedOn }: BlogPostSchemaParams): WithContext<BlogPosting> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"url": new URL(`/blog/${slug}`, siteUrl).toString(),
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": new URL(preview, siteUrl).toString(),
|
||||
"datePublished": datePublished,
|
||||
"dateModified": dateModified,
|
||||
"inLanguage": lang,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": config.author.name,
|
||||
"url": config.author.url,
|
||||
"sameAs": config.author.sameAs,
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": new URL(`/blog/${slug}`, siteUrl).toString(),
|
||||
},
|
||||
...(isBasedOn && { isBasedOn: isBasedOn }),
|
||||
});
|
26
src/utils/schemas/blogSchema.ts
Normal file
26
src/utils/schemas/blogSchema.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { WithContext, CollectionPage } from "schema-dts";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
export type BlogSchemaParams = {
|
||||
readonly posts: CollectionEntry<"blog">[];
|
||||
readonly siteUrl: string;
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, title, posts }: BlogSchemaParams): WithContext<CollectionPage> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"url": new URL("/blog/", siteUrl).toString(),
|
||||
"name": title,
|
||||
"mainEntity": {
|
||||
"@type": "ItemList",
|
||||
"itemListOrder": "https://schema.org/ItemListOrderDescending",
|
||||
"numberOfItems": posts.length,
|
||||
"itemListElement": posts.map((post, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 1,
|
||||
"url": new URL(`/blog/${post.slug}`, siteUrl).toString(),
|
||||
"name": post.data.title,
|
||||
})),
|
||||
},
|
||||
});
|
23
src/utils/schemas/pageSchema.ts
Normal file
23
src/utils/schemas/pageSchema.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { WithContext, WebPage } from "schema-dts";
|
||||
|
||||
export type WebsiteSchemaParams = {
|
||||
readonly description: string;
|
||||
readonly page: string;
|
||||
readonly siteUrl: string;
|
||||
readonly title: string;
|
||||
readonly lang: string;
|
||||
};
|
||||
|
||||
export default ({ siteUrl, page, title, description, lang }: WebsiteSchemaParams): WithContext<WebPage> => ({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"@id": new URL(page, siteUrl).toString(),
|
||||
"url": new URL(page, siteUrl).toString(),
|
||||
"name": title,
|
||||
"description": description,
|
||||
"inLanguage": lang,
|
||||
"mainEntity": {
|
||||
"@type": "WebSite",
|
||||
"@id": new URL("/", siteUrl).toString(),
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user