mirror of
https://github.com/morten-olsen/morten-olsen.github.io.git
synced 2026-02-08 01:46:28 +01:00
init
This commit is contained in:
committed by
Morten Olsen
parent
98e39a54cc
commit
ee37ac9d90
@@ -1,2 +1,3 @@
|
||||
/node_modules/
|
||||
/.astro/
|
||||
/.astro/
|
||||
/.vscode/
|
||||
24
.eslintrc.cjs
Normal file
24
.eslintrc.cjs
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
extends: ['plugin:astro/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.astro'],
|
||||
parser: 'astro-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
extraFileExtensions: ['.astro']
|
||||
},
|
||||
rules: {
|
||||
// override/add rules settings here, such as:
|
||||
// "astro/no-set-html-directive": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
5
.github/workflows/release.yaml
vendored
5
.github/workflows/release.yaml
vendored
@@ -1,7 +1,8 @@
|
||||
name: Deploy Astro site to Pages
|
||||
on:
|
||||
push:
|
||||
branches: [$default-branch]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -60,4 +61,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
13
.prettierrc.cjs
Normal file
13
.prettierrc.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
module.exports = {
|
||||
...require('prettier-config-standard'),
|
||||
plugins: [require.resolve('prettier-plugin-astro')],
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
plugins: ['prettier-plugin-astro'],
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro',
|
||||
},
|
||||
},
|
||||
],
|
||||
printWidth: 100,
|
||||
singleQuote: true,
|
||||
};
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"recommendations": ["astro-build.astro-vscode", "esbenp.prettier-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
||||
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@@ -1,3 +1,14 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"astro",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"[astro]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import icon from "astro-icon";
|
||||
import compress from "astro-compress";
|
||||
import robotsTxt from 'astro-robots-txt';
|
||||
import { defineConfig } from 'astro/config'
|
||||
import mdx from '@astrojs/mdx'
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
import icon from 'astro-icon'
|
||||
import compress from 'astro-compress'
|
||||
import robotsTxt from 'astro-robots-txt'
|
||||
const getSiteInfo = () => {
|
||||
const siteUrl = process.env.SITE_URL;
|
||||
const siteUrl = process.env.SITE_URL
|
||||
if (!siteUrl) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
const url = new URL(siteUrl);
|
||||
const url = new URL(siteUrl)
|
||||
return {
|
||||
site: `${url.protocol}//${url.host}`,
|
||||
base: url.pathname
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
@@ -26,4 +26,4 @@ export default defineConfig({
|
||||
assetsInlineLimit: 1024 * 10
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginAstro from 'eslint-plugin-astro';
|
||||
import eslintPluginImport from 'eslint-plugin-import';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
|
||||
/** @type {import('eslint').ESLint.ConfigData}*/
|
||||
export default [
|
||||
eslint.configs.recommended,
|
||||
{
|
||||
plugins: {
|
||||
'simple-import-sort': simpleImportSort,
|
||||
},
|
||||
rules: {
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
import: eslintPluginImport,
|
||||
},
|
||||
rules: {
|
||||
// "import/no-unresolved": "off",
|
||||
'import/first': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
},
|
||||
},
|
||||
eslintPluginPrettierRecommended,
|
||||
...eslintPluginAstro.configs['flat/all'],
|
||||
{
|
||||
rules: {
|
||||
'astro/no-set-html-directive': 'off',
|
||||
'astro/no-unused-css-selector': 'off',
|
||||
'one-var': ['error', 'never'],
|
||||
},
|
||||
},
|
||||
];
|
||||
10
package.json
10
package.json
@@ -5,7 +5,8 @@
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"lint:apply": "eslint ./src --fix && prettier --write ./src",
|
||||
"lint": "prettier \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"",
|
||||
"lint:apply": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\"",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"serve": "SITE_URL=http://localhost:3000 pnpm build && serve dist",
|
||||
@@ -35,17 +36,14 @@
|
||||
"astro-icon": "^1.1.0",
|
||||
"astro-robots-txt": "^1.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-astro": "^0.33.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"husky": "^9.0.11",
|
||||
"json-schema-to-typescript": "^13.1.2",
|
||||
"less": "^4.2.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "3.2.5",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-config-standard": "^7.0.0",
|
||||
"prettier-plugin-astro": "^0.13.0",
|
||||
"sass": "^1.72.0",
|
||||
"serve": "^14.2.1",
|
||||
|
||||
209
pnpm-lock.yaml
generated
209
pnpm-lock.yaml
generated
@@ -58,24 +58,12 @@ devDependencies:
|
||||
eslint:
|
||||
specifier: ^8.57.0
|
||||
version: 8.57.0
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0(eslint@8.57.0)
|
||||
eslint-plugin-astro:
|
||||
specifier: ^0.33.1
|
||||
version: 0.33.1(eslint@8.57.0)
|
||||
eslint-plugin-import:
|
||||
specifier: ^2.29.1
|
||||
version: 2.29.1(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)
|
||||
eslint-plugin-jsx-a11y:
|
||||
specifier: ^6.8.0
|
||||
version: 6.8.0(eslint@8.57.0)
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.1.3
|
||||
version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5)
|
||||
eslint-plugin-simple-import-sort:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0(eslint@8.57.0)
|
||||
husky:
|
||||
specifier: ^9.0.11
|
||||
version: 9.0.11
|
||||
@@ -89,8 +77,11 @@ devDependencies:
|
||||
specifier: ^15.2.2
|
||||
version: 15.2.2
|
||||
prettier:
|
||||
specifier: 3.2.5
|
||||
specifier: ^3.2.5
|
||||
version: 3.2.5
|
||||
prettier-config-standard:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0(prettier@3.2.5)
|
||||
prettier-plugin-astro:
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.0
|
||||
@@ -2328,10 +2319,6 @@ packages:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
dev: true
|
||||
|
||||
/@types/json5@0.0.29:
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@types/jsonld@1.5.13:
|
||||
resolution: {integrity: sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A==}
|
||||
dev: true
|
||||
@@ -2725,18 +2712,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/array.prototype.findlastindex@1.2.5:
|
||||
resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.2
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.0.0
|
||||
es-shim-unscopables: 1.0.2
|
||||
dev: true
|
||||
|
||||
/array.prototype.flat@1.3.2:
|
||||
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3583,17 +3558,6 @@ packages:
|
||||
ms: 2.0.0
|
||||
dev: true
|
||||
|
||||
/debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -3715,13 +3679,6 @@ packages:
|
||||
/dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
/doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/doctrine@3.0.0:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -4026,54 +3983,6 @@ packages:
|
||||
semver: 7.6.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-prettier@9.1.0(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
eslint: '>=7.0.0'
|
||||
dependencies:
|
||||
eslint: 8.57.0
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-node@0.3.9:
|
||||
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.13.1
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils@2.8.1(@typescript-eslint/parser@7.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint: '*'
|
||||
eslint-import-resolver-node: '*'
|
||||
eslint-import-resolver-typescript: '*'
|
||||
eslint-import-resolver-webpack: '*'
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
eslint:
|
||||
optional: true
|
||||
eslint-import-resolver-node:
|
||||
optional: true
|
||||
eslint-import-resolver-typescript:
|
||||
optional: true
|
||||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.2)
|
||||
debug: 3.2.7
|
||||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-astro@0.33.1(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-wVyxAf8Ulmljv5qJQLgspWe17LR4hLXcksIENtUlEC3W7rleBVEKXS+hIqzBfCbpkBLZpl1tsYes1AGpYHd13w==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@@ -4093,41 +4002,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.5.0)(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.2)
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
array.prototype.flat: 1.3.2
|
||||
array.prototype.flatmap: 1.3.2
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.13.1
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
object.fromentries: 2.0.8
|
||||
object.groupby: 1.0.3
|
||||
object.values: 1.2.0
|
||||
semver: 6.3.1
|
||||
tsconfig-paths: 3.15.0
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -4153,35 +4027,6 @@ packages:
|
||||
object.fromentries: 2.0.8
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5):
|
||||
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
'@types/eslint': '>=8.0.0'
|
||||
eslint: '>=8.0.0'
|
||||
eslint-config-prettier: '*'
|
||||
prettier: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/eslint':
|
||||
optional: true
|
||||
eslint-config-prettier:
|
||||
optional: true
|
||||
dependencies:
|
||||
eslint: 8.57.0
|
||||
eslint-config-prettier: 9.1.0(eslint@8.57.0)
|
||||
prettier: 3.2.5
|
||||
prettier-linter-helpers: 1.0.0
|
||||
synckit: 0.8.8
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-simple-import-sort@12.0.0(eslint@8.57.0):
|
||||
resolution: {integrity: sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ==}
|
||||
peerDependencies:
|
||||
eslint: '>=5.0.0'
|
||||
dependencies:
|
||||
eslint: 8.57.0
|
||||
dev: true
|
||||
|
||||
/eslint-scope@7.2.2:
|
||||
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -4411,10 +4256,6 @@ packages:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-diff@1.3.0:
|
||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||
dev: true
|
||||
|
||||
/fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
requiresBuild: true
|
||||
@@ -5461,13 +5302,6 @@ packages:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
dev: true
|
||||
|
||||
/json5@1.0.2:
|
||||
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
dev: true
|
||||
|
||||
/json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -6620,15 +6454,6 @@ packages:
|
||||
es-object-atoms: 1.0.0
|
||||
dev: true
|
||||
|
||||
/object.groupby@1.0.3:
|
||||
resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.2
|
||||
dev: true
|
||||
|
||||
/object.values@1.2.0:
|
||||
resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -6931,11 +6756,12 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/prettier-linter-helpers@1.0.0:
|
||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
/prettier-config-standard@7.0.0(prettier@3.2.5):
|
||||
resolution: {integrity: sha512-NgZy4TYupJR6aMMuV/Aqs0ONnVhlFT8PXVkYRskxREq8EUhJHOddVfBxPV6fWpgcASpJSgvvhVLk0CBO5M3Hvw==}
|
||||
peerDependencies:
|
||||
prettier: ^2.6.0 || ^3.0.0
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
prettier: 3.2.5
|
||||
dev: true
|
||||
|
||||
/prettier-plugin-astro@0.13.0:
|
||||
@@ -7890,14 +7716,6 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/synckit@0.8.8:
|
||||
resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
dependencies:
|
||||
'@pkgr/core': 0.1.1
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/synckit@0.9.0:
|
||||
resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@@ -8057,15 +7875,6 @@ packages:
|
||||
dependencies:
|
||||
typescript: 5.4.2
|
||||
|
||||
/tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
json5: 1.0.2
|
||||
minimist: 1.2.8
|
||||
strip-bom: 3.0.0
|
||||
dev: true
|
||||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
requiresBuild: true
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { fileURLToPath} from 'url';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { writeFile, mkdir } from 'fs/promises';
|
||||
import { compile } from 'json-schema-to-typescript';
|
||||
import { fileURLToPath } from 'url'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { writeFile, mkdir } from 'fs/promises'
|
||||
import { compile } from 'json-schema-to-typescript'
|
||||
|
||||
const root = fileURLToPath(new URL('..', import.meta.url));
|
||||
const root = fileURLToPath(new URL('..', import.meta.url))
|
||||
|
||||
const response = await fetch('https://raw.githubusercontent.com/jsonresume/resume-schema/master/schema.json');
|
||||
const schema = await response.json();
|
||||
const response = await fetch(
|
||||
'https://raw.githubusercontent.com/jsonresume/resume-schema/master/schema.json'
|
||||
)
|
||||
const schema = await response.json()
|
||||
|
||||
const types = await compile(schema, 'ResumeSchema', { bannerComment: '' });
|
||||
const types = await compile(schema, 'ResumeSchema', { bannerComment: '' })
|
||||
|
||||
const location = resolve(root, 'src/types/resume-schema.ts');
|
||||
console.log(`Writing to ${location}`);
|
||||
await mkdir(dirname(location), { recursive: true });
|
||||
await writeFile(location, types);
|
||||
const location = resolve(root, 'src/types/resume-schema.ts')
|
||||
console.log(`Writing to ${location}`)
|
||||
await mkdir(dirname(location), { recursive: true })
|
||||
await writeFile(location, types)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getImage } from 'astro:assets';
|
||||
import { data } from '@/data/data.js';
|
||||
import { getImage } from 'astro:assets'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
const imageSizes = [16, 32, 48, 64, 96, 128, 256, 512];
|
||||
const imageSizes = [16, 32, 48, 64, 96, 128, 256, 512]
|
||||
|
||||
const pngs = await Promise.all(
|
||||
imageSizes.map(async (size) => {
|
||||
@@ -10,14 +10,14 @@ const pngs = await Promise.all(
|
||||
src: data.profile.image,
|
||||
format: 'png',
|
||||
width: size,
|
||||
height: size,
|
||||
height: size
|
||||
})),
|
||||
size: `${size}x${size}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
size: `${size}x${size}`
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const icons = {
|
||||
pngs,
|
||||
};
|
||||
export { icons };
|
||||
pngs
|
||||
}
|
||||
export { icons }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
import { data } from '@/data/data';
|
||||
import { data } from '@/data/data'
|
||||
|
||||
const { basics } = data.profile;
|
||||
const { basics } = data.profile
|
||||
---
|
||||
|
||||
<nav>
|
||||
<a href="/">{basics.name}</a>
|
||||
<a href='/'>{basics.name}</a>
|
||||
<div>{basics.tagline}</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
nav {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
type Props = {
|
||||
datetime: Date | undefined;
|
||||
format?: Intl.DateTimeFormatOptions;
|
||||
};
|
||||
const { datetime, format, ...rest } = Astro.props;
|
||||
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime);
|
||||
datetime: Date | undefined
|
||||
format?: Intl.DateTimeFormatOptions
|
||||
}
|
||||
const { datetime, format, ...rest } = Astro.props
|
||||
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime)
|
||||
---
|
||||
|
||||
{
|
||||
|
||||
@@ -6,11 +6,11 @@ color: '#e7d9ac'
|
||||
heroImage: ./assets/cover.png
|
||||
---
|
||||
|
||||
import { Image } from 'astro:assets';
|
||||
import TaskBounds from './assets/TaskBounds.png';
|
||||
import Frame1 from './assets/Frame1.png';
|
||||
import Graph1 from './assets/Graph1.png';
|
||||
import Graph2 from './assets/Graph2.png';
|
||||
import { Image } from 'astro:assets'
|
||||
import TaskBounds from './assets/TaskBounds.png'
|
||||
import Frame1 from './assets/Frame1.png'
|
||||
import Graph1 from './assets/Graph1.png'
|
||||
import Graph2 from './assets/Graph2.png'
|
||||
|
||||
Allow me to introduce Bob. Bob is an algorithm, and he has just accepted a role as my assistant.
|
||||
|
||||
@@ -26,7 +26,7 @@ Also, I wanted a planning algorithm that was not only for productivity. I did no
|
||||
|
||||
Bob is still pretty young and still learning new things, but he has gotten to the point where I believe he is good enough to start to use on a day to day basis.
|
||||
|
||||
<Image src={Frame1} alt="Frame1" />
|
||||
<Image src={Frame1} alt='Frame1' />
|
||||
|
||||
How does Bob work? Bob gets a list of tasks, some from my calendar (both my work and my personal calendar), some from "routines" (which are daily tasks that I want to do most days, such as eating breakfast or picking up the kid), and some tasks come from "goals" which are a list of completable items. These tasks go into Bob, and he tries to create a plan for the next couple of days where I get everything done that I set out to do.
|
||||
|
||||
@@ -38,7 +38,7 @@ An "earliest start time" and a "latest start time". These define when the task c
|
||||
- If the task is required.
|
||||
- A priority
|
||||
|
||||
<Image src={TaskBounds} alt="Task bounds" />
|
||||
<Image src={TaskBounds} alt='Task bounds' />
|
||||
|
||||
Bob uses a graph walk to create the optimal plan, where each node contains a few different things
|
||||
|
||||
@@ -53,11 +53,11 @@ Bob starts by figuring out which locations I can go to complete the remaining ta
|
||||
He then gets a list of all the remaining tasks for the current node which can be completed at the current location, again figuring out when I would be done with the task, updating the list of impossible tasks and scoring the node.
|
||||
If any node adds a required task to the impossible list, that node is considered dead, and Bob will not analyze it further.
|
||||
|
||||
<Image src={Graph1} alt="Graph1" />
|
||||
<Image src={Graph1} alt='Graph1' />
|
||||
|
||||
Now we have a list of active leaves, and from that list, we find the node with the highest score and redo the process from above.
|
||||
|
||||
<Image src={Graph2} alt="Graph2" />
|
||||
<Image src={Graph2} alt='Graph2' />
|
||||
|
||||
Bob has four different strategies for finding a plan.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ description: ''
|
||||
heroImage: ./assets/cover.png
|
||||
---
|
||||
|
||||
import graph from './assets/graph.png';
|
||||
import { Image } from 'astro:assets';
|
||||
import graph from './assets/graph.png'
|
||||
import { Image } from 'astro:assets'
|
||||
|
||||
I have been playing around with smart homes for a long time; I have used most of the platforms out there, I have developed quite a few myself, and one thing I keep coming back to is Redux.
|
||||
|
||||
@@ -66,7 +66,7 @@ Now comes the part I have feared, where I need to draw a diagram.
|
||||
|
||||
...sorry
|
||||
|
||||
<Image src={graph} alt="graph" />
|
||||
<Image src={graph} alt='graph' />
|
||||
|
||||
So this shows our final setup.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { defineCollection, z } from 'astro:content'
|
||||
|
||||
const articles = defineCollection({
|
||||
schema: ({ image }) =>
|
||||
@@ -10,10 +10,10 @@ const articles = defineCollection({
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
heroImage: image().refine((img) => img.width >= 320, {
|
||||
message: 'Cover image must be at least 1080 pixels wide!',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
message: 'Cover image must be at least 1080 pixels wide!'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const work = defineCollection({
|
||||
schema: ({ image }) =>
|
||||
@@ -26,16 +26,16 @@ const work = defineCollection({
|
||||
url: z.string().optional(),
|
||||
logo: image()
|
||||
.refine((img) => img.width >= 200, {
|
||||
message: 'Logo must be at least 320 pixels wide!',
|
||||
message: 'Logo must be at least 320 pixels wide!'
|
||||
})
|
||||
.optional(),
|
||||
banner: image()
|
||||
.refine((img) => img.height >= 50, {
|
||||
message: 'Logo must be at least 320 pixels wide!',
|
||||
message: 'Logo must be at least 320 pixels wide!'
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
.optional()
|
||||
})
|
||||
})
|
||||
|
||||
const references = defineCollection({
|
||||
schema: () =>
|
||||
@@ -45,16 +45,16 @@ const references = defineCollection({
|
||||
company: z.string(),
|
||||
date: z.coerce.date(),
|
||||
relation: z.string(),
|
||||
profile: z.string(),
|
||||
}),
|
||||
});
|
||||
profile: z.string()
|
||||
})
|
||||
})
|
||||
|
||||
const skills = defineCollection({
|
||||
schema: () =>
|
||||
z.object({
|
||||
name: z.string(),
|
||||
technologies: z.array(z.string()),
|
||||
}),
|
||||
});
|
||||
technologies: z.array(z.string())
|
||||
})
|
||||
})
|
||||
|
||||
export const collections = { articles, work, references, skills };
|
||||
export const collections = { articles, work, references, skills }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js';
|
||||
import { Content } from './description.md';
|
||||
import image from './profile.jpg';
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js'
|
||||
import { Content } from './description.md'
|
||||
import image from './profile.jpg'
|
||||
|
||||
const basics = {
|
||||
name: 'Morten Olsen',
|
||||
@@ -11,38 +11,38 @@ const basics = {
|
||||
location: {
|
||||
city: 'Copenhagen',
|
||||
countryCode: 'DK',
|
||||
region: 'Capital Region of Denmark',
|
||||
region: 'Capital Region of Denmark'
|
||||
},
|
||||
profiles: [
|
||||
{
|
||||
network: 'GitHub',
|
||||
icon: 'mdi:github',
|
||||
username: 'morten-olsen',
|
||||
url: 'https://github.com/morten-olsen',
|
||||
url: 'https://github.com/morten-olsen'
|
||||
},
|
||||
{
|
||||
network: 'LinkedIn',
|
||||
icon: 'mdi:linkedin',
|
||||
username: 'mortenolsendk',
|
||||
url: 'https://www.linkedin.com/in/mortenolsendk',
|
||||
},
|
||||
url: 'https://www.linkedin.com/in/mortenolsendk'
|
||||
}
|
||||
],
|
||||
languages: [
|
||||
{
|
||||
name: 'English',
|
||||
fluency: 'Conversational',
|
||||
fluency: 'Conversational'
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
fluency: 'Native speaker',
|
||||
},
|
||||
],
|
||||
} satisfies ResumeSchema['basics'];
|
||||
fluency: 'Native speaker'
|
||||
}
|
||||
]
|
||||
} satisfies ResumeSchema['basics']
|
||||
|
||||
const profile = {
|
||||
basics,
|
||||
image,
|
||||
Content,
|
||||
};
|
||||
Content
|
||||
}
|
||||
|
||||
export { profile };
|
||||
export { profile }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Articles {
|
||||
public find = () => getCollection('articles');
|
||||
public find = () => getCollection('articles')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Article = Exclude<Awaited<ReturnType<Articles['get']>>, undefined>;
|
||||
type Article = Exclude<Awaited<ReturnType<Articles['get']>>, undefined>
|
||||
|
||||
export { Articles, type Article };
|
||||
export { Articles, type Article }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class References {
|
||||
public find = () => getCollection('references');
|
||||
public find = () => getCollection('references')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Reference = Exclude<Awaited<ReturnType<References['get']>>, undefined>;
|
||||
type Reference = Exclude<Awaited<ReturnType<References['get']>>, undefined>
|
||||
|
||||
export { References, type Reference };
|
||||
export { References, type Reference }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const site = {
|
||||
theme: '#30E130',
|
||||
};
|
||||
theme: '#30E130'
|
||||
}
|
||||
|
||||
export { site };
|
||||
export { site }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Skills {
|
||||
public find = () => getCollection('skills');
|
||||
public find = () => getCollection('skills')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Skill = Exclude<Awaited<ReturnType<Skills['get']>>, undefined>;
|
||||
type Skill = Exclude<Awaited<ReturnType<Skills['get']>>, undefined>
|
||||
|
||||
export { Skills, type Skill };
|
||||
export { Skills, type Skill }
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { profile } from '../content/profile/profile.js';
|
||||
import { type Article, Articles } from './data.articles.js';
|
||||
import { References } from './data.references.ts';
|
||||
import { site } from './data.site.ts';
|
||||
import { Skills } from './data.skills.ts';
|
||||
import { getJsonLDResume, getJsonResume } from './data.utils.js';
|
||||
import { Work, type WorkItem } from './data.work.js';
|
||||
import { profile } from '../content/profile/profile.js'
|
||||
import { type Article, Articles } from './data.articles.js'
|
||||
import { References } from './data.references.ts'
|
||||
import { site } from './data.site.ts'
|
||||
import { Skills } from './data.skills.ts'
|
||||
import { getJsonLDResume, getJsonResume } from './data.utils.js'
|
||||
import { Work, type WorkItem } from './data.work.js'
|
||||
|
||||
class Data {
|
||||
public articles = new Articles();
|
||||
public work = new Work();
|
||||
public references = new References();
|
||||
public skills = new Skills();
|
||||
public profile = profile;
|
||||
public site = site;
|
||||
public articles = new Articles()
|
||||
public work = new Work()
|
||||
public references = new References()
|
||||
public skills = new Skills()
|
||||
public profile = profile
|
||||
public site = site
|
||||
|
||||
public getJsonResume = getJsonResume.bind(null, this);
|
||||
public getJsonLDResume = getJsonLDResume.bind(null, this);
|
||||
public getJsonResume = getJsonResume.bind(null, this)
|
||||
public getJsonLDResume = getJsonLDResume.bind(null, this)
|
||||
}
|
||||
|
||||
const data = new Data();
|
||||
const data = new Data()
|
||||
|
||||
type Profile = typeof profile;
|
||||
export type { Article, Profile, WorkItem };
|
||||
export { data, Data };
|
||||
type Profile = typeof profile
|
||||
export type { Article, Profile, WorkItem }
|
||||
export { data, Data }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js';
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js'
|
||||
|
||||
import type { Article, Data } from './data';
|
||||
import type { Article, Data } from './data'
|
||||
|
||||
const getJsonResume = async (data: Data) => {
|
||||
const profile = data.profile;
|
||||
const profile = data.profile
|
||||
const resume = {
|
||||
basics: profile.basics,
|
||||
} satisfies ResumeSchema;
|
||||
basics: profile.basics
|
||||
} satisfies ResumeSchema
|
||||
|
||||
return resume;
|
||||
};
|
||||
return resume
|
||||
}
|
||||
|
||||
const getArticleJsonLD = async (data: Data, article: Article) => {
|
||||
const jsonld = {
|
||||
@@ -23,16 +23,16 @@ const getArticleJsonLD = async (data: Data, article: Article) => {
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: data.profile.basics.name,
|
||||
url: data.profile.basics.url,
|
||||
},
|
||||
};
|
||||
return jsonld;
|
||||
};
|
||||
url: data.profile.basics.url
|
||||
}
|
||||
}
|
||||
return jsonld
|
||||
}
|
||||
|
||||
const getJsonLDResume = async (data: Data) => {
|
||||
const work = await data.work.find();
|
||||
const currentWork = work.find((w) => !w.data.endDate);
|
||||
const otherWork = work.filter((w) => w !== currentWork);
|
||||
const work = await data.work.find()
|
||||
const currentWork = work.find((w) => !w.data.endDate)
|
||||
const otherWork = work.filter((w) => w !== currentWork)
|
||||
|
||||
const jsonld = {
|
||||
'@context': 'https://schema.org',
|
||||
@@ -47,24 +47,24 @@ const getJsonLDResume = async (data: Data) => {
|
||||
'@type': 'ContactPoint',
|
||||
contactType: profile.network.toLowerCase(),
|
||||
identifier: profile.username,
|
||||
url: profile.url,
|
||||
url: profile.url
|
||||
})),
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
addressLocality: data.profile.basics.location.city,
|
||||
addressRegion: data.profile.basics.location.region,
|
||||
addressCountry: data.profile.basics.location.countryCode,
|
||||
addressCountry: data.profile.basics.location.countryCode
|
||||
},
|
||||
sameAs: data.profile.basics.profiles.map((profile) => profile.url),
|
||||
hasOccupation: currentWork && {
|
||||
'@type': 'EmployeeRole',
|
||||
roleName: currentWork.data.position,
|
||||
startDate: currentWork.data.startDate.toISOString(),
|
||||
startDate: currentWork.data.startDate.toISOString()
|
||||
},
|
||||
worksFor: currentWork && {
|
||||
'@type': 'Organization',
|
||||
name: currentWork?.data.name,
|
||||
sameAs: currentWork?.data.url,
|
||||
sameAs: currentWork?.data.url
|
||||
},
|
||||
alumniOf: otherWork.map((w) => ({
|
||||
'@type': 'Organization',
|
||||
@@ -76,14 +76,14 @@ const getJsonLDResume = async (data: Data) => {
|
||||
'@type': 'EmployeeRole',
|
||||
roleName: w.data.position,
|
||||
startDate: w.data.startDate.toISOString(),
|
||||
endDate: w.data.endDate?.toISOString(),
|
||||
endDate: w.data.endDate?.toISOString()
|
||||
},
|
||||
sameAs: '#me',
|
||||
},
|
||||
})),
|
||||
};
|
||||
sameAs: '#me'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
return jsonld;
|
||||
};
|
||||
return jsonld
|
||||
}
|
||||
|
||||
export { getJsonResume, getJsonLDResume, getArticleJsonLD };
|
||||
export { getJsonResume, getJsonLDResume, getArticleJsonLD }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Work {
|
||||
public find = () => getCollection('work');
|
||||
public find = () => getCollection('work')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>;
|
||||
export { Work, type WorkItem };
|
||||
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>
|
||||
export { Work, type WorkItem }
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import { getArticleJsonLD } from '@/data/data.utils';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import { getArticleJsonLD } from '@/data/data.utils'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Html from '../html/html.astro'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
const { props } = Astro;
|
||||
const { article } = props;
|
||||
const { Content } = await article.render();
|
||||
article: Article
|
||||
}
|
||||
const { props } = Astro
|
||||
const { article } = props
|
||||
const { Content } = await article.render()
|
||||
---
|
||||
|
||||
<Html
|
||||
@@ -24,22 +24,22 @@ const { Content } = await article.render();
|
||||
<h1>
|
||||
{article.data.title.split(' ').map((word) => <span>{word}</span>)}
|
||||
</h1>
|
||||
<a href="/"><h2>By {data.profile.basics.name}</h2></a>
|
||||
<a href='/'><h2>By {data.profile.basics.name}</h2></a>
|
||||
</header>
|
||||
<Picture
|
||||
loading="eager"
|
||||
class="img"
|
||||
loading='eager'
|
||||
class='img'
|
||||
src={article.data.heroImage}
|
||||
widths={[320, 640, 1024, 1400]}
|
||||
formats={['avif', 'webp', 'png']}
|
||||
alt="Cover image"
|
||||
alt='Cover image'
|
||||
/>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
</Html>
|
||||
<style lang="less">
|
||||
<style lang='less'>
|
||||
article {
|
||||
--left-padding: 100px;
|
||||
display: grid;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
import type { Article } from '@/data/data.js';
|
||||
import { range } from '@/utils/data';
|
||||
import type { Article } from '@/data/data.js'
|
||||
import { range } from '@/utils/data'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Html from '../html/html.astro'
|
||||
|
||||
type Props = {
|
||||
pageNumber: number;
|
||||
pageCount: number;
|
||||
articles: Article[];
|
||||
};
|
||||
const { articles, pageNumber, pageCount } = Astro.props;
|
||||
const hasPrev = pageNumber > 1;
|
||||
const hasNext = pageNumber < pageCount;
|
||||
pageNumber: number
|
||||
pageCount: number
|
||||
articles: Article[]
|
||||
}
|
||||
const { articles, pageNumber, pageCount } = Astro.props
|
||||
const hasPrev = pageNumber > 1
|
||||
const hasNext = pageNumber < pageCount
|
||||
---
|
||||
|
||||
<Html title="Articles" description="A list of articles">
|
||||
<Html title='Articles' description='A list of articles'>
|
||||
<h1>Articles</h1>
|
||||
{
|
||||
articles.map((article) => (
|
||||
@@ -26,7 +26,9 @@ const hasNext = pageNumber < pageCount;
|
||||
}
|
||||
|
||||
<nav>
|
||||
<a aria-disabled={!hasPrev} href={`/articles/pages/${pageNumber - 1}`}>Previous</a>
|
||||
<a aria-disabled={!hasPrev} href={`/articles/pages/${pageNumber - 1}`}
|
||||
>Previous</a
|
||||
>
|
||||
{
|
||||
range(1, pageCount).map((page) => (
|
||||
<a
|
||||
@@ -37,11 +39,13 @@ const hasNext = pageNumber < pageCount;
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}> Next </a>
|
||||
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}>
|
||||
Next
|
||||
</a>
|
||||
</nav>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Article from './articles.item.astro';
|
||||
import Article from './articles.item.astro'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const articleCount = 6;
|
||||
const allArticles = await data.articles.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const articleCount = 6
|
||||
const allArticles = await data.articles.find()
|
||||
const sortedArticles = allArticles.sort(
|
||||
(a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(),
|
||||
);
|
||||
const hasMore = sortedArticles.length > articleCount;
|
||||
const articles = sortedArticles.slice(0, articleCount);
|
||||
(a, b) =>
|
||||
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
|
||||
)
|
||||
const hasMore = sortedArticles.length > articleCount
|
||||
const articles = sortedArticles.slice(0, articleCount)
|
||||
---
|
||||
|
||||
<div class:list={['articles', className]} {...rest}>
|
||||
<h2>Articles</h2>
|
||||
<div class="items">
|
||||
<div class='items'>
|
||||
{articles.map((article) => <Article article={article} />)}
|
||||
</div>
|
||||
{hasMore && <a href="/articles/pages/1">View all articles</a>}
|
||||
{hasMore && <a href='/articles/pages/1'>View all articles</a>}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.articles {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import type { Article } from '@/data/data.js';
|
||||
import { formatDate } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import type { Article } from '@/data/data.js'
|
||||
import { formatDate } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
article: Article
|
||||
}
|
||||
|
||||
const { article: item } = Astro.props;
|
||||
const { article: item } = Astro.props
|
||||
---
|
||||
|
||||
<a href={`/articles/${item.slug}`}>
|
||||
<article>
|
||||
<Picture
|
||||
class="thumb"
|
||||
alt="thumbnail image"
|
||||
class='thumb'
|
||||
alt='thumbnail image'
|
||||
src={item.data.heroImage}
|
||||
formats={['avif', 'webp', 'jpeg']}
|
||||
width={100}
|
||||
/>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<small>
|
||||
<Time format={formatDate} datetime={item.data.pubDate} />
|
||||
</small>
|
||||
@@ -30,7 +30,7 @@ const { article: item } = Astro.props;
|
||||
</article>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
a {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Profile from './description.profile.astro';
|
||||
import Profile from './description.profile.astro'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const { Content, basics, image } = data.profile;
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const { Content, basics, image } = data.profile
|
||||
---
|
||||
|
||||
<div class:list={['main', className]} {...rest}>
|
||||
<Picture
|
||||
class="picture"
|
||||
alt="Profile Picture"
|
||||
class='picture'
|
||||
alt='Profile Picture'
|
||||
src={image}
|
||||
formats={['avif', 'webp', 'jpeg']}
|
||||
width={230}
|
||||
/>
|
||||
<h1>{basics.name}</h1>
|
||||
<h2>{basics.tagline}</h2>
|
||||
<div class="description">
|
||||
<div class='description'>
|
||||
<Content />
|
||||
</div>
|
||||
<div class="profiles">
|
||||
<div class='profiles'>
|
||||
{basics.profiles.map((profile) => <Profile profile={profile} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
@media screen and (max-width: 768px) {
|
||||
.main {
|
||||
display: flex;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { Icon } from 'astro-icon/components'
|
||||
|
||||
import type { Profile } from '@/data/data';
|
||||
import type { Profile } from '@/data/data'
|
||||
|
||||
type Props = {
|
||||
profile: Profile['basics']['profiles'][number];
|
||||
};
|
||||
const { profile } = Astro.props;
|
||||
profile: Profile['basics']['profiles'][number]
|
||||
}
|
||||
const { profile } = Astro.props
|
||||
---
|
||||
|
||||
<a href={profile.url} target="_blank">
|
||||
<Icon class="icon" name={profile.icon} />
|
||||
<div class="network">{profile.network}</div>
|
||||
<div class="username">{profile.username}</div>
|
||||
<a href={profile.url} target='_blank'>
|
||||
<Icon class='icon' name={profile.icon} />
|
||||
<div class='network'>{profile.network}</div>
|
||||
<div class='username'>{profile.username}</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
a {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
---
|
||||
import { data } from '@/data/data';
|
||||
import { data } from '@/data/data'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Articles from './articles/articles.astro';
|
||||
import Description from './description/description.astro';
|
||||
import Info from './info/info.astro';
|
||||
import Skills from './skills/skills.astro';
|
||||
import Work from './work/work.astro';
|
||||
import Html from '../html/html.astro'
|
||||
import Articles from './articles/articles.astro'
|
||||
import Description from './description/description.astro'
|
||||
import Info from './info/info.astro'
|
||||
import Skills from './skills/skills.astro'
|
||||
import Work from './work/work.astro'
|
||||
|
||||
const jsonLd = await data.getJsonLDResume();
|
||||
const jsonLd = await data.getJsonLDResume()
|
||||
---
|
||||
|
||||
<Html title={data.profile.basics.name} description="Landing page" jsonLd={jsonLd}>
|
||||
<div class="wrapper">
|
||||
<div class="frontpage">
|
||||
<Description class="description" />
|
||||
<Info class="info" />
|
||||
<Articles class="articles" />
|
||||
<Skills class="skills" />
|
||||
<Work class="work" />
|
||||
<Html
|
||||
title={data.profile.basics.name}
|
||||
description='Landing page'
|
||||
jsonLd={jsonLd}
|
||||
>
|
||||
<div class='wrapper'>
|
||||
<div class='frontpage'>
|
||||
<Description class='description' />
|
||||
<Info class='info' />
|
||||
<Articles class='articles' />
|
||||
<Skills class='skills' />
|
||||
<Work class='work' />
|
||||
</div>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.wrapper {
|
||||
--gap: var(--space-xxl);
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const { basics } = data.profile;
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const { basics } = data.profile
|
||||
---
|
||||
|
||||
<div class:list={['sidebar', className]} {...rest}>
|
||||
@@ -28,7 +28,7 @@ const { basics } = data.profile;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const skills = await data.skills.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const skills = await data.skills.find()
|
||||
---
|
||||
|
||||
<div class:list={['skills', className]} {...rest}>
|
||||
<h2>Skills</h2>
|
||||
<div class="skill">
|
||||
<div class='skill'>
|
||||
{
|
||||
skills.map((item) => (
|
||||
<div class="item">
|
||||
<div class='item'>
|
||||
<h3>{item.data.name}</h3>
|
||||
<ul>
|
||||
{item.data.technologies.map((tech) => (
|
||||
@@ -27,7 +27,7 @@ const skills = await data.skills.find();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
h2 {
|
||||
font-size: var(--font-xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
---
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import { data } from '@/data/data.js';
|
||||
import { formatYearMonth } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import { data } from '@/data/data.js'
|
||||
import { formatYearMonth } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const allWork = await data.work.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const allWork = await data.work.find()
|
||||
const work = allWork.sort((a, b) => {
|
||||
return new Date(b.data.startDate).getTime() - new Date(a.data.startDate).getTime();
|
||||
});
|
||||
return (
|
||||
new Date(b.data.startDate).getTime() - new Date(a.data.startDate).getTime()
|
||||
)
|
||||
})
|
||||
---
|
||||
|
||||
<div class:list={[className]} {...rest}>
|
||||
<h2>Work</h2>
|
||||
<div class="list">
|
||||
<div class='list'>
|
||||
{
|
||||
work.map((item) => (
|
||||
<div class="item">
|
||||
<div class="time">
|
||||
<div class='item'>
|
||||
<div class='time'>
|
||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||
<br />
|
||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<h3>{item.data.position}</h3>
|
||||
<h4>@ {item.data.name}</h4>
|
||||
<p>{item.data.summary}</p>
|
||||
@@ -34,10 +36,10 @@ const work = allWork.sort((a, b) => {
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<a href="/work-history">See full work history</a>
|
||||
<a href='/work-history'>See full work history</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
h2 {
|
||||
font-size: var(--font-xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
|
||||
@@ -1,40 +1,53 @@
|
||||
---
|
||||
import '@/style/theme.css';
|
||||
import '@/style/theme.css'
|
||||
|
||||
import { Head } from 'astro-capo';
|
||||
import { Head } from 'astro-capo'
|
||||
|
||||
import { icons } from '@/assets/images/icons.js';
|
||||
import { data } from '@/data/data.js';
|
||||
import { icons } from '@/assets/images/icons.js'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
jsonLd?: unknown;
|
||||
};
|
||||
const { props } = Astro;
|
||||
const schema = JSON.stringify(props.jsonLd);
|
||||
title: string
|
||||
description: string
|
||||
image?: string
|
||||
jsonLd?: unknown
|
||||
}
|
||||
const { props } = Astro
|
||||
const schema = JSON.stringify(props.jsonLd)
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang='en'>
|
||||
<Head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta charset='UTF-8' />
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||
<meta name='HandheldFriendly' content='True' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||
<title>{props.title}</title>
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="theme-color" content={data.site.theme} />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/articles/rss.xml" />
|
||||
{props.description && <meta name="description" content={props.description} />}
|
||||
{props.image && <meta property="og:image" content={props.image} />}
|
||||
{props.jsonLd && <script type="application/ld+json" is:inline set:html={schema} />}
|
||||
<link rel='sitemap' href='/sitemap-index.xml' />
|
||||
<link rel='manifest' href='/manifest.webmanifest' />
|
||||
<meta name='generator' content={Astro.generator} />
|
||||
<meta name='theme-color' content={data.site.theme} />
|
||||
<link
|
||||
rel='alternate'
|
||||
type='application/rss+xml'
|
||||
title='RSS Feed'
|
||||
href='/articles/rss.xml'
|
||||
/>
|
||||
{
|
||||
props.description && (
|
||||
<meta name='description' content={props.description} />
|
||||
)
|
||||
}
|
||||
{props.image && <meta property='og:image' content={props.image} />}
|
||||
{
|
||||
props.jsonLd && (
|
||||
<script type='application/ld+json' is:inline set:html={schema} />
|
||||
)
|
||||
}
|
||||
{
|
||||
icons.pngs.map((icon) => (
|
||||
<link rel="icon" href={icon.src} type="image/png" sizes={icon.size} />
|
||||
<link rel='icon' href={icon.src} type='image/png' sizes={icon.size} />
|
||||
))
|
||||
}
|
||||
</Head>
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
---
|
||||
import Header from '@/components/header/header.astro';
|
||||
import { data } from '@/data/data.js';
|
||||
import Header from '@/components/header/header.astro'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Item from './work-history.item.astro';
|
||||
import Html from '../html/html.astro'
|
||||
import Item from './work-history.item.astro'
|
||||
|
||||
const allWork = await data.work.find();
|
||||
const work = allWork.sort((a, b) => b.data.startDate.getTime() - a.data.startDate.getTime());
|
||||
const allWork = await data.work.find()
|
||||
const work = allWork.sort(
|
||||
(a, b) => b.data.startDate.getTime() - a.data.startDate.getTime()
|
||||
)
|
||||
---
|
||||
|
||||
<Html title="Work" description="A list of work experiences">
|
||||
<Html title='Work' description='A list of work experiences'>
|
||||
<Header />
|
||||
<div class="wrapper">
|
||||
<div class='wrapper'>
|
||||
<h2>Work history</h2>
|
||||
<div class="list">
|
||||
<div class='list'>
|
||||
{work.map((item) => <Item item={item} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: var(--content-width);
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import type { WorkItem } from '@/data/data.js';
|
||||
import { formatYearMonth } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import type { WorkItem } from '@/data/data.js'
|
||||
import { formatYearMonth } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
item: WorkItem;
|
||||
};
|
||||
item: WorkItem
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
const { Content } = await item.render();
|
||||
const { item } = Astro.props
|
||||
const { Content } = await item.render()
|
||||
---
|
||||
|
||||
<div class="item">
|
||||
<div class="time">
|
||||
<div class='item'>
|
||||
<div class='time'>
|
||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||
-
|
||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class='main'>
|
||||
<h3>{item.data.position}</h3>
|
||||
<h4>{item.data.name}</h4>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.item {
|
||||
display: contents;
|
||||
break-inside: avoid;
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
---
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import ArticleView from '@/layouts/article/article.astro';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import ArticleView from '@/layouts/article/article.astro'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
article: Article
|
||||
}
|
||||
|
||||
const getStaticPaths = async () => {
|
||||
const articles = await data.articles.find();
|
||||
export const getStaticPaths = async () => {
|
||||
const articles = await data.articles.find()
|
||||
return articles.map((article) => ({
|
||||
params: { slug: article.slug },
|
||||
props: { article },
|
||||
}));
|
||||
};
|
||||
const { props } = Astro;
|
||||
const { article } = props;
|
||||
|
||||
export { getStaticPaths };
|
||||
props: { article }
|
||||
}))
|
||||
}
|
||||
const { props } = Astro
|
||||
const { article } = props
|
||||
---
|
||||
|
||||
<ArticleView article={article} />
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { type Article, data } from '@/data/data.ts';
|
||||
import type { APIContext } from 'astro';
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
|
||||
export async function GET(context: APIContext<Props>) {
|
||||
const { props } = context;
|
||||
const { article } = props;
|
||||
return new Response(JSON.stringify(article), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const articles = await data.articles.find();
|
||||
return articles.map((article) => ({
|
||||
params: { slug: article.slug },
|
||||
props: { article },
|
||||
}));
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
---
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import Articles from '@/layouts/articles/articles.astro';
|
||||
import { range } from '@/utils/data.js';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import Articles from '@/layouts/articles/articles.astro'
|
||||
import { range } from '@/utils/data.js'
|
||||
|
||||
type Props = {
|
||||
articles: Article[];
|
||||
pageNumber: number;
|
||||
pageCount: number;
|
||||
pageSize: number;
|
||||
};
|
||||
articles: Article[]
|
||||
pageNumber: number
|
||||
pageCount: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const pageSize = 2;
|
||||
const allArticles = await data.articles.find();
|
||||
const pageCount = Math.ceil(allArticles.length / pageSize);
|
||||
const pageSize = 2
|
||||
const allArticles = await data.articles.find()
|
||||
const pageCount = Math.ceil(allArticles.length / pageSize)
|
||||
const pages = range(0, pageCount).map((index) => {
|
||||
const start = index * pageSize;
|
||||
const end = start + pageSize;
|
||||
const start = index * pageSize
|
||||
const end = start + pageSize
|
||||
return {
|
||||
pageNumber: index + 1,
|
||||
pageCount,
|
||||
pageSize,
|
||||
articles: allArticles.slice(start, end),
|
||||
};
|
||||
});
|
||||
articles: allArticles.slice(start, end)
|
||||
}
|
||||
})
|
||||
return pages.map((page) => ({
|
||||
params: { page: String(page.pageNumber) },
|
||||
props: page,
|
||||
}));
|
||||
props: page
|
||||
}))
|
||||
}
|
||||
|
||||
const { props } = Astro;
|
||||
const { props } = Astro
|
||||
---
|
||||
|
||||
<Articles {...props} />
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { data } from '@/data/data.ts';
|
||||
import rss from '@astrojs/rss';
|
||||
import type { APIContext } from 'astro';
|
||||
import { data } from '@/data/data.ts'
|
||||
import rss from '@astrojs/rss'
|
||||
import type { APIContext } from 'astro'
|
||||
|
||||
export async function GET(context: APIContext) {
|
||||
const articles = await data.articles.find();
|
||||
const profile = data.profile;
|
||||
const articles = await data.articles.find()
|
||||
const profile = data.profile
|
||||
return rss({
|
||||
title: profile.basics.name,
|
||||
description: profile.basics.tagline,
|
||||
site: context.site || 'http://localhost:3000',
|
||||
items: articles.map((article) => ({
|
||||
...article.data,
|
||||
link: `/articles/${article.slug}/`,
|
||||
})),
|
||||
});
|
||||
link: `/articles/${article.slug}/`
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import {} from '@/data/data.js';
|
||||
import Frontpage from '@/layouts/frontpage/frontpage.astro';
|
||||
import {} from '@/data/data.js'
|
||||
import Frontpage from '@/layouts/frontpage/frontpage.astro'
|
||||
---
|
||||
|
||||
<Frontpage />
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { icons } from '@/assets/images/icons.js';
|
||||
import { data } from '@/data/data.js';
|
||||
import type { ManifestOptions } from 'vite-plugin-pwa';
|
||||
import { icons } from '@/assets/images/icons.js'
|
||||
import { data } from '@/data/data.js'
|
||||
import type { ManifestOptions } from 'vite-plugin-pwa'
|
||||
|
||||
export async function GET() {
|
||||
const [maskableIcon] = icons.pngs.filter(
|
||||
(icon) => icon.size === '512x512' && icon.src.includes('png'),
|
||||
);
|
||||
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon);
|
||||
const basics = data.profile.basics;
|
||||
(icon) => icon.size === '512x512' && icon.src.includes('png')
|
||||
)
|
||||
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon)
|
||||
const basics = data.profile.basics
|
||||
|
||||
const manifest: Partial<ManifestOptions> = {
|
||||
name: basics.name,
|
||||
@@ -21,7 +21,7 @@ export async function GET() {
|
||||
...nonMaskableIcons.map((png) => ({
|
||||
src: png.src,
|
||||
sizes: png.size,
|
||||
type: 'image/png',
|
||||
type: 'image/png'
|
||||
})),
|
||||
...(maskableIcon
|
||||
? [
|
||||
@@ -29,11 +29,11 @@ export async function GET() {
|
||||
src: maskableIcon.src,
|
||||
sizes: maskableIcon.size,
|
||||
type: 'image/png',
|
||||
purpose: 'maskable',
|
||||
},
|
||||
purpose: 'maskable'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
return new Response(JSON.stringify(manifest, null, 2));
|
||||
: [])
|
||||
]
|
||||
}
|
||||
return new Response(JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { data } from '@/data/data.ts';
|
||||
import { data } from '@/data/data.ts'
|
||||
|
||||
export async function GET() {
|
||||
const resume = await data.getJsonResume();
|
||||
return new Response(JSON.stringify(resume, null, 2));
|
||||
const resume = await data.getJsonResume()
|
||||
return new Response(JSON.stringify(resume, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import Work from '@/layouts/work-history/work-history.astro';
|
||||
import Work from '@/layouts/work-history/work-history.astro'
|
||||
---
|
||||
|
||||
<Work />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:root {
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
|
||||
Cantarell, 'Helvetica Neue', sans-serif;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
|
||||
--font-family-heading: var(--font-family);
|
||||
--font-size: 15px;
|
||||
--letter-spacing: 0.5px;
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
/**
|
||||
* Similar to the standard date type, but each section after the year is optional. e.g. 2014-06-29 or 2023-04
|
||||
*/
|
||||
export type Iso8601 = string;
|
||||
export type Iso8601 = string
|
||||
|
||||
export interface ResumeSchema {
|
||||
/**
|
||||
* link to the version of the schema that can validate the resume
|
||||
*/
|
||||
$schema?: string;
|
||||
$schema?: string
|
||||
basics?: {
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Web Developer
|
||||
*/
|
||||
label?: string;
|
||||
label?: string
|
||||
/**
|
||||
* URL (as per RFC 3986) to a image in JPEG or PNG format
|
||||
*/
|
||||
image?: string;
|
||||
image?: string
|
||||
/**
|
||||
* e.g. thomas@gmail.com
|
||||
*/
|
||||
email?: string;
|
||||
email?: string
|
||||
/**
|
||||
* Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923
|
||||
*/
|
||||
phone?: string;
|
||||
phone?: string
|
||||
/**
|
||||
* URL (as per RFC 3986) to your website, e.g. personal homepage
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Write a short 2-3 sentence biography about yourself
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
location?: {
|
||||
/**
|
||||
* To add multiple address lines, use
|
||||
* . For example, 1234 Glücklichkeit Straße
|
||||
* Hinterhaus 5. Etage li.
|
||||
*/
|
||||
address?: string;
|
||||
postalCode?: string;
|
||||
city?: string;
|
||||
address?: string
|
||||
postalCode?: string
|
||||
city?: string
|
||||
/**
|
||||
* code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN
|
||||
*/
|
||||
countryCode?: string;
|
||||
countryCode?: string
|
||||
/**
|
||||
* The general region where you live. Can be a US state, or a province, for instance.
|
||||
*/
|
||||
region?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
region?: string
|
||||
[k: string]: unknown
|
||||
}
|
||||
/**
|
||||
* Specify any number of social networks that you participate in
|
||||
*/
|
||||
@@ -60,106 +60,106 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Facebook or Twitter
|
||||
*/
|
||||
network?: string;
|
||||
network?: string
|
||||
/**
|
||||
* e.g. neutralthoughts
|
||||
*/
|
||||
username?: string;
|
||||
username?: string
|
||||
/**
|
||||
* e.g. http://twitter.example.com/neutralthoughts
|
||||
*/
|
||||
url?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
url?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
[k: string]: unknown
|
||||
}
|
||||
work?: {
|
||||
/**
|
||||
* e.g. Facebook
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Menlo Park, CA
|
||||
*/
|
||||
location?: string;
|
||||
location?: string
|
||||
/**
|
||||
* e.g. Social Media Company
|
||||
*/
|
||||
description?: string;
|
||||
description?: string
|
||||
/**
|
||||
* e.g. Software Engineer
|
||||
*/
|
||||
position?: string;
|
||||
position?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
url?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* Give an overview of your responsibilities at the company
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
/**
|
||||
* Specify multiple accomplishments
|
||||
*/
|
||||
highlights?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
highlights?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
volunteer?: {
|
||||
/**
|
||||
* e.g. Facebook
|
||||
*/
|
||||
organization?: string;
|
||||
organization?: string
|
||||
/**
|
||||
* e.g. Software Engineer
|
||||
*/
|
||||
position?: string;
|
||||
position?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
url?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* Give an overview of your responsibilities at the company
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
/**
|
||||
* Specify accomplishments and achievements
|
||||
*/
|
||||
highlights?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
highlights?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
education?: {
|
||||
/**
|
||||
* e.g. Massachusetts Institute of Technology
|
||||
*/
|
||||
institution?: string;
|
||||
institution?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* e.g. Arts
|
||||
*/
|
||||
area?: string;
|
||||
area?: string
|
||||
/**
|
||||
* e.g. Bachelor
|
||||
*/
|
||||
studyType?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
studyType?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* grade point average, e.g. 3.67/4.0
|
||||
*/
|
||||
score?: string;
|
||||
score?: string
|
||||
/**
|
||||
* List notable courses/subjects
|
||||
*/
|
||||
courses?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
courses?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify any awards you have received throughout your professional career
|
||||
*/
|
||||
@@ -167,18 +167,18 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. One of the 100 greatest minds of the century
|
||||
*/
|
||||
title?: string;
|
||||
date?: Iso8601;
|
||||
title?: string
|
||||
date?: Iso8601
|
||||
/**
|
||||
* e.g. Time Magazine
|
||||
*/
|
||||
awarder?: string;
|
||||
awarder?: string
|
||||
/**
|
||||
* e.g. Received for my work with Quantum Physics
|
||||
*/
|
||||
summary?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
summary?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify any certificates you have received throughout your professional career
|
||||
*/
|
||||
@@ -186,18 +186,18 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Certified Kubernetes Administrator
|
||||
*/
|
||||
name?: string;
|
||||
date?: Iso8601;
|
||||
name?: string
|
||||
date?: Iso8601
|
||||
/**
|
||||
* e.g. http://example.com
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* e.g. CNCF
|
||||
*/
|
||||
issuer?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
issuer?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify your publications through your career
|
||||
*/
|
||||
@@ -205,22 +205,22 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. The World Wide Web
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. IEEE, Computer Magazine
|
||||
*/
|
||||
publisher?: string;
|
||||
releaseDate?: Iso8601;
|
||||
publisher?: string
|
||||
releaseDate?: Iso8601
|
||||
/**
|
||||
* e.g. http://www.computer.org.example.com/csdl/mags/co/1996/10/rx069-abs.html
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML.
|
||||
*/
|
||||
summary?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
summary?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List out your professional skill-set
|
||||
*/
|
||||
@@ -228,17 +228,17 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Web Development
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Master
|
||||
*/
|
||||
level?: string;
|
||||
level?: string
|
||||
/**
|
||||
* List some keywords pertaining to this skill
|
||||
*/
|
||||
keywords?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
keywords?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List any other languages you speak
|
||||
*/
|
||||
@@ -246,21 +246,21 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. English, Spanish
|
||||
*/
|
||||
language?: string;
|
||||
language?: string
|
||||
/**
|
||||
* e.g. Fluent, Beginner
|
||||
*/
|
||||
fluency?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
fluency?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
interests?: {
|
||||
/**
|
||||
* e.g. Philosophy
|
||||
*/
|
||||
name?: string;
|
||||
keywords?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
name?: string
|
||||
keywords?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List references you have received
|
||||
*/
|
||||
@@ -268,13 +268,13 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Timothy Cook
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing.
|
||||
*/
|
||||
reference?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
reference?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify career projects
|
||||
*/
|
||||
@@ -282,39 +282,39 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. The World Wide Web
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* Short summary of project. e.g. Collated works of 2017.
|
||||
*/
|
||||
description?: string;
|
||||
description?: string
|
||||
/**
|
||||
* Specify multiple features
|
||||
*/
|
||||
highlights?: string[];
|
||||
highlights?: string[]
|
||||
/**
|
||||
* Specify special elements involved
|
||||
*/
|
||||
keywords?: string[];
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
keywords?: string[]
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Specify your role on this project or in company
|
||||
*/
|
||||
roles?: string[];
|
||||
roles?: string[]
|
||||
/**
|
||||
* Specify the relevant company/entity affiliations e.g. 'greenpeace', 'corporationXYZ'
|
||||
*/
|
||||
entity?: string;
|
||||
entity?: string
|
||||
/**
|
||||
* e.g. 'volunteering', 'presentation', 'talk', 'application', 'conference'
|
||||
*/
|
||||
type?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
type?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* The schema version and any other tooling configuration lives here
|
||||
*/
|
||||
@@ -322,15 +322,15 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* URL (as per RFC 3986) to latest version of this document
|
||||
*/
|
||||
canonical?: string;
|
||||
canonical?: string
|
||||
/**
|
||||
* A version field which follows semver - e.g. v1.0.0
|
||||
*/
|
||||
version?: string;
|
||||
version?: string
|
||||
/**
|
||||
* Using ISO 8601 with YYYY-MM-DDThh:mm:ss
|
||||
*/
|
||||
lastModified?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
lastModified?: string
|
||||
[k: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const range = (start: number, end: number) => {
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||||
};
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
}
|
||||
|
||||
export { range };
|
||||
export { range }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const formatDate: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
};
|
||||
day: 'numeric'
|
||||
}
|
||||
|
||||
const formatYearMonth: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
};
|
||||
month: 'short'
|
||||
}
|
||||
|
||||
export { formatDate, formatYearMonth };
|
||||
export { formatDate, formatYearMonth }
|
||||
|
||||
Reference in New Issue
Block a user