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/
|
/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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.github/workflows/release.yaml
vendored
3
.github/workflows/release.yaml
vendored
@@ -1,7 +1,8 @@
|
|||||||
name: Deploy Astro site to Pages
|
name: Deploy Astro site to Pages
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [$default-branch]
|
branches:
|
||||||
|
- main
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
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": []
|
"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 { defineConfig } from 'astro/config'
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx'
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap'
|
||||||
import icon from "astro-icon";
|
import icon from 'astro-icon'
|
||||||
import compress from "astro-compress";
|
import compress from 'astro-compress'
|
||||||
import robotsTxt from 'astro-robots-txt';
|
import robotsTxt from 'astro-robots-txt'
|
||||||
const getSiteInfo = () => {
|
const getSiteInfo = () => {
|
||||||
const siteUrl = process.env.SITE_URL;
|
const siteUrl = process.env.SITE_URL
|
||||||
if (!siteUrl) {
|
if (!siteUrl) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
const url = new URL(siteUrl);
|
const url = new URL(siteUrl)
|
||||||
return {
|
return {
|
||||||
site: `${url.protocol}//${url.host}`,
|
site: `${url.protocol}//${url.host}`,
|
||||||
base: url.pathname
|
base: url.pathname
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -26,4 +26,4 @@ export default defineConfig({
|
|||||||
assetsInlineLimit: 1024 * 10
|
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": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "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",
|
"build": "astro check && astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"serve": "SITE_URL=http://localhost:3000 pnpm build && serve dist",
|
"serve": "SITE_URL=http://localhost:3000 pnpm build && serve dist",
|
||||||
@@ -35,17 +36,14 @@
|
|||||||
"astro-icon": "^1.1.0",
|
"astro-icon": "^1.1.0",
|
||||||
"astro-robots-txt": "^1.0.0",
|
"astro-robots-txt": "^1.0.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-plugin-astro": "^0.33.1",
|
"eslint-plugin-astro": "^0.33.1",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
"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",
|
"husky": "^9.0.11",
|
||||||
"json-schema-to-typescript": "^13.1.2",
|
"json-schema-to-typescript": "^13.1.2",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"lint-staged": "^15.2.2",
|
"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",
|
"prettier-plugin-astro": "^0.13.0",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.72.0",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.1",
|
||||||
|
|||||||
209
pnpm-lock.yaml
generated
209
pnpm-lock.yaml
generated
@@ -58,24 +58,12 @@ devDependencies:
|
|||||||
eslint:
|
eslint:
|
||||||
specifier: ^8.57.0
|
specifier: ^8.57.0
|
||||||
version: 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:
|
eslint-plugin-astro:
|
||||||
specifier: ^0.33.1
|
specifier: ^0.33.1
|
||||||
version: 0.33.1(eslint@8.57.0)
|
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:
|
eslint-plugin-jsx-a11y:
|
||||||
specifier: ^6.8.0
|
specifier: ^6.8.0
|
||||||
version: 6.8.0(eslint@8.57.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:
|
husky:
|
||||||
specifier: ^9.0.11
|
specifier: ^9.0.11
|
||||||
version: 9.0.11
|
version: 9.0.11
|
||||||
@@ -89,8 +77,11 @@ devDependencies:
|
|||||||
specifier: ^15.2.2
|
specifier: ^15.2.2
|
||||||
version: 15.2.2
|
version: 15.2.2
|
||||||
prettier:
|
prettier:
|
||||||
specifier: 3.2.5
|
specifier: ^3.2.5
|
||||||
version: 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:
|
prettier-plugin-astro:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
@@ -2328,10 +2319,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/json5@0.0.29:
|
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/jsonld@1.5.13:
|
/@types/jsonld@1.5.13:
|
||||||
resolution: {integrity: sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A==}
|
resolution: {integrity: sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -2725,18 +2712,6 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
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:
|
/array.prototype.flat@1.3.2:
|
||||||
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3583,17 +3558,6 @@ packages:
|
|||||||
ms: 2.0.0
|
ms: 2.0.0
|
||||||
dev: true
|
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:
|
/debug@4.3.4:
|
||||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@@ -3715,13 +3679,6 @@ packages:
|
|||||||
/dlv@1.1.3:
|
/dlv@1.1.3:
|
||||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
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:
|
/doctrine@3.0.0:
|
||||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -4026,54 +3983,6 @@ packages:
|
|||||||
semver: 7.6.0
|
semver: 7.6.0
|
||||||
dev: true
|
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):
|
/eslint-plugin-astro@0.33.1(eslint@8.57.0):
|
||||||
resolution: {integrity: sha512-wVyxAf8Ulmljv5qJQLgspWe17LR4hLXcksIENtUlEC3W7rleBVEKXS+hIqzBfCbpkBLZpl1tsYes1AGpYHd13w==}
|
resolution: {integrity: sha512-wVyxAf8Ulmljv5qJQLgspWe17LR4hLXcksIENtUlEC3W7rleBVEKXS+hIqzBfCbpkBLZpl1tsYes1AGpYHd13w==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@@ -4093,41 +4002,6 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
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):
|
/eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0):
|
||||||
resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
|
resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -4153,35 +4027,6 @@ packages:
|
|||||||
object.fromentries: 2.0.8
|
object.fromentries: 2.0.8
|
||||||
dev: true
|
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:
|
/eslint-scope@7.2.2:
|
||||||
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -4411,10 +4256,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/fast-diff@1.3.0:
|
|
||||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/fast-fifo@1.3.2:
|
/fast-fifo@1.3.2:
|
||||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@@ -5461,13 +5302,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
dev: true
|
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:
|
/json5@2.2.3:
|
||||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -6620,15 +6454,6 @@ packages:
|
|||||||
es-object-atoms: 1.0.0
|
es-object-atoms: 1.0.0
|
||||||
dev: true
|
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:
|
/object.values@1.2.0:
|
||||||
resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
|
resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -6931,11 +6756,12 @@ packages:
|
|||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prettier-linter-helpers@1.0.0:
|
/prettier-config-standard@7.0.0(prettier@3.2.5):
|
||||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
resolution: {integrity: sha512-NgZy4TYupJR6aMMuV/Aqs0ONnVhlFT8PXVkYRskxREq8EUhJHOddVfBxPV6fWpgcASpJSgvvhVLk0CBO5M3Hvw==}
|
||||||
engines: {node: '>=6.0.0'}
|
peerDependencies:
|
||||||
|
prettier: ^2.6.0 || ^3.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
prettier: 3.2.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/prettier-plugin-astro@0.13.0:
|
/prettier-plugin-astro@0.13.0:
|
||||||
@@ -7890,14 +7716,6 @@ packages:
|
|||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
dev: true
|
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:
|
/synckit@0.9.0:
|
||||||
resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==}
|
resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@@ -8057,15 +7875,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.4.2
|
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:
|
/tslib@2.6.2:
|
||||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { fileURLToPath} from 'url';
|
import { fileURLToPath } from 'url'
|
||||||
import { resolve, dirname } from 'path';
|
import { resolve, dirname } from 'path'
|
||||||
import { writeFile, mkdir } from 'fs/promises';
|
import { writeFile, mkdir } from 'fs/promises'
|
||||||
import { compile } from 'json-schema-to-typescript';
|
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 response = await fetch(
|
||||||
const schema = await response.json();
|
'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');
|
const location = resolve(root, 'src/types/resume-schema.ts')
|
||||||
console.log(`Writing to ${location}`);
|
console.log(`Writing to ${location}`)
|
||||||
await mkdir(dirname(location), { recursive: true });
|
await mkdir(dirname(location), { recursive: true })
|
||||||
await writeFile(location, types);
|
await writeFile(location, types)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getImage } from 'astro:assets';
|
import { getImage } from 'astro:assets'
|
||||||
import { data } from '@/data/data.js';
|
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(
|
const pngs = await Promise.all(
|
||||||
imageSizes.map(async (size) => {
|
imageSizes.map(async (size) => {
|
||||||
@@ -10,14 +10,14 @@ const pngs = await Promise.all(
|
|||||||
src: data.profile.image,
|
src: data.profile.image,
|
||||||
format: 'png',
|
format: 'png',
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size
|
||||||
})),
|
})),
|
||||||
size: `${size}x${size}`,
|
size: `${size}x${size}`
|
||||||
};
|
}
|
||||||
}),
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
pngs,
|
pngs
|
||||||
};
|
}
|
||||||
export { icons };
|
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>
|
<nav>
|
||||||
<a href="/">{basics.name}</a>
|
<a href='/'>{basics.name}</a>
|
||||||
<div>{basics.tagline}</div>
|
<div>{basics.tagline}</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
nav {
|
nav {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
type Props = {
|
type Props = {
|
||||||
datetime: Date | undefined;
|
datetime: Date | undefined
|
||||||
format?: Intl.DateTimeFormatOptions;
|
format?: Intl.DateTimeFormatOptions
|
||||||
};
|
}
|
||||||
const { datetime, format, ...rest } = Astro.props;
|
const { datetime, format, ...rest } = Astro.props
|
||||||
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime);
|
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime)
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ color: '#e7d9ac'
|
|||||||
heroImage: ./assets/cover.png
|
heroImage: ./assets/cover.png
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets'
|
||||||
import TaskBounds from './assets/TaskBounds.png';
|
import TaskBounds from './assets/TaskBounds.png'
|
||||||
import Frame1 from './assets/Frame1.png';
|
import Frame1 from './assets/Frame1.png'
|
||||||
import Graph1 from './assets/Graph1.png';
|
import Graph1 from './assets/Graph1.png'
|
||||||
import Graph2 from './assets/Graph2.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.
|
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.
|
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.
|
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.
|
- If the task is required.
|
||||||
- A priority
|
- 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
|
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.
|
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.
|
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.
|
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.
|
Bob has four different strategies for finding a plan.
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ description: ''
|
|||||||
heroImage: ./assets/cover.png
|
heroImage: ./assets/cover.png
|
||||||
---
|
---
|
||||||
|
|
||||||
import graph from './assets/graph.png';
|
import graph from './assets/graph.png'
|
||||||
import { Image } from 'astro:assets';
|
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.
|
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
|
...sorry
|
||||||
|
|
||||||
<Image src={graph} alt="graph" />
|
<Image src={graph} alt='graph' />
|
||||||
|
|
||||||
So this shows our final setup.
|
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({
|
const articles = defineCollection({
|
||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
@@ -10,10 +10,10 @@ const articles = defineCollection({
|
|||||||
updatedDate: z.coerce.date().optional(),
|
updatedDate: z.coerce.date().optional(),
|
||||||
tags: z.array(z.string()).optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
heroImage: image().refine((img) => img.width >= 320, {
|
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({
|
const work = defineCollection({
|
||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
@@ -26,16 +26,16 @@ const work = defineCollection({
|
|||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
logo: image()
|
logo: image()
|
||||||
.refine((img) => img.width >= 200, {
|
.refine((img) => img.width >= 200, {
|
||||||
message: 'Logo must be at least 320 pixels wide!',
|
message: 'Logo must be at least 320 pixels wide!'
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
banner: image()
|
banner: image()
|
||||||
.refine((img) => img.height >= 50, {
|
.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({
|
const references = defineCollection({
|
||||||
schema: () =>
|
schema: () =>
|
||||||
@@ -45,16 +45,16 @@ const references = defineCollection({
|
|||||||
company: z.string(),
|
company: z.string(),
|
||||||
date: z.coerce.date(),
|
date: z.coerce.date(),
|
||||||
relation: z.string(),
|
relation: z.string(),
|
||||||
profile: z.string(),
|
profile: z.string()
|
||||||
}),
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
const skills = defineCollection({
|
const skills = defineCollection({
|
||||||
schema: () =>
|
schema: () =>
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
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 type { ResumeSchema } from '@/types/resume-schema.js'
|
||||||
import { Content } from './description.md';
|
import { Content } from './description.md'
|
||||||
import image from './profile.jpg';
|
import image from './profile.jpg'
|
||||||
|
|
||||||
const basics = {
|
const basics = {
|
||||||
name: 'Morten Olsen',
|
name: 'Morten Olsen',
|
||||||
@@ -11,38 +11,38 @@ const basics = {
|
|||||||
location: {
|
location: {
|
||||||
city: 'Copenhagen',
|
city: 'Copenhagen',
|
||||||
countryCode: 'DK',
|
countryCode: 'DK',
|
||||||
region: 'Capital Region of Denmark',
|
region: 'Capital Region of Denmark'
|
||||||
},
|
},
|
||||||
profiles: [
|
profiles: [
|
||||||
{
|
{
|
||||||
network: 'GitHub',
|
network: 'GitHub',
|
||||||
icon: 'mdi:github',
|
icon: 'mdi:github',
|
||||||
username: 'morten-olsen',
|
username: 'morten-olsen',
|
||||||
url: 'https://github.com/morten-olsen',
|
url: 'https://github.com/morten-olsen'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
network: 'LinkedIn',
|
network: 'LinkedIn',
|
||||||
icon: 'mdi:linkedin',
|
icon: 'mdi:linkedin',
|
||||||
username: 'mortenolsendk',
|
username: 'mortenolsendk',
|
||||||
url: 'https://www.linkedin.com/in/mortenolsendk',
|
url: 'https://www.linkedin.com/in/mortenolsendk'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
languages: [
|
languages: [
|
||||||
{
|
{
|
||||||
name: 'English',
|
name: 'English',
|
||||||
fluency: 'Conversational',
|
fluency: 'Conversational'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Danish',
|
name: 'Danish',
|
||||||
fluency: 'Native speaker',
|
fluency: 'Native speaker'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
} satisfies ResumeSchema['basics'];
|
} satisfies ResumeSchema['basics']
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
basics,
|
basics,
|
||||||
image,
|
image,
|
||||||
Content,
|
Content
|
||||||
};
|
}
|
||||||
|
|
||||||
export { profile };
|
export { profile }
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content'
|
||||||
|
|
||||||
class Articles {
|
class Articles {
|
||||||
public find = () => getCollection('articles');
|
public find = () => getCollection('articles')
|
||||||
public get = async (slug: string) => {
|
public get = async (slug: string) => {
|
||||||
const collection = await this.find();
|
const collection = await this.find()
|
||||||
return collection.find((entry) => entry.slug === slug);
|
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 {
|
class References {
|
||||||
public find = () => getCollection('references');
|
public find = () => getCollection('references')
|
||||||
public get = async (slug: string) => {
|
public get = async (slug: string) => {
|
||||||
const collection = await this.find();
|
const collection = await this.find()
|
||||||
return collection.find((entry) => entry.slug === slug);
|
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 = {
|
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 {
|
class Skills {
|
||||||
public find = () => getCollection('skills');
|
public find = () => getCollection('skills')
|
||||||
public get = async (slug: string) => {
|
public get = async (slug: string) => {
|
||||||
const collection = await this.find();
|
const collection = await this.find()
|
||||||
return collection.find((entry) => entry.slug === slug);
|
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 { profile } from '../content/profile/profile.js'
|
||||||
import { type Article, Articles } from './data.articles.js';
|
import { type Article, Articles } from './data.articles.js'
|
||||||
import { References } from './data.references.ts';
|
import { References } from './data.references.ts'
|
||||||
import { site } from './data.site.ts';
|
import { site } from './data.site.ts'
|
||||||
import { Skills } from './data.skills.ts';
|
import { Skills } from './data.skills.ts'
|
||||||
import { getJsonLDResume, getJsonResume } from './data.utils.js';
|
import { getJsonLDResume, getJsonResume } from './data.utils.js'
|
||||||
import { Work, type WorkItem } from './data.work.js';
|
import { Work, type WorkItem } from './data.work.js'
|
||||||
|
|
||||||
class Data {
|
class Data {
|
||||||
public articles = new Articles();
|
public articles = new Articles()
|
||||||
public work = new Work();
|
public work = new Work()
|
||||||
public references = new References();
|
public references = new References()
|
||||||
public skills = new Skills();
|
public skills = new Skills()
|
||||||
public profile = profile;
|
public profile = profile
|
||||||
public site = site;
|
public site = site
|
||||||
|
|
||||||
public getJsonResume = getJsonResume.bind(null, this);
|
public getJsonResume = getJsonResume.bind(null, this)
|
||||||
public getJsonLDResume = getJsonLDResume.bind(null, this);
|
public getJsonLDResume = getJsonLDResume.bind(null, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = new Data();
|
const data = new Data()
|
||||||
|
|
||||||
type Profile = typeof profile;
|
type Profile = typeof profile
|
||||||
export type { Article, Profile, WorkItem };
|
export type { Article, Profile, WorkItem }
|
||||||
export { data, Data };
|
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 getJsonResume = async (data: Data) => {
|
||||||
const profile = data.profile;
|
const profile = data.profile
|
||||||
const resume = {
|
const resume = {
|
||||||
basics: profile.basics,
|
basics: profile.basics
|
||||||
} satisfies ResumeSchema;
|
} satisfies ResumeSchema
|
||||||
|
|
||||||
return resume;
|
return resume
|
||||||
};
|
}
|
||||||
|
|
||||||
const getArticleJsonLD = async (data: Data, article: Article) => {
|
const getArticleJsonLD = async (data: Data, article: Article) => {
|
||||||
const jsonld = {
|
const jsonld = {
|
||||||
@@ -23,16 +23,16 @@ const getArticleJsonLD = async (data: Data, article: Article) => {
|
|||||||
author: {
|
author: {
|
||||||
'@type': 'Person',
|
'@type': 'Person',
|
||||||
name: data.profile.basics.name,
|
name: data.profile.basics.name,
|
||||||
url: data.profile.basics.url,
|
url: data.profile.basics.url
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
return jsonld;
|
return jsonld
|
||||||
};
|
}
|
||||||
|
|
||||||
const getJsonLDResume = async (data: Data) => {
|
const getJsonLDResume = async (data: Data) => {
|
||||||
const work = await data.work.find();
|
const work = await data.work.find()
|
||||||
const currentWork = work.find((w) => !w.data.endDate);
|
const currentWork = work.find((w) => !w.data.endDate)
|
||||||
const otherWork = work.filter((w) => w !== currentWork);
|
const otherWork = work.filter((w) => w !== currentWork)
|
||||||
|
|
||||||
const jsonld = {
|
const jsonld = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
@@ -47,24 +47,24 @@ const getJsonLDResume = async (data: Data) => {
|
|||||||
'@type': 'ContactPoint',
|
'@type': 'ContactPoint',
|
||||||
contactType: profile.network.toLowerCase(),
|
contactType: profile.network.toLowerCase(),
|
||||||
identifier: profile.username,
|
identifier: profile.username,
|
||||||
url: profile.url,
|
url: profile.url
|
||||||
})),
|
})),
|
||||||
address: {
|
address: {
|
||||||
'@type': 'PostalAddress',
|
'@type': 'PostalAddress',
|
||||||
addressLocality: data.profile.basics.location.city,
|
addressLocality: data.profile.basics.location.city,
|
||||||
addressRegion: data.profile.basics.location.region,
|
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),
|
sameAs: data.profile.basics.profiles.map((profile) => profile.url),
|
||||||
hasOccupation: currentWork && {
|
hasOccupation: currentWork && {
|
||||||
'@type': 'EmployeeRole',
|
'@type': 'EmployeeRole',
|
||||||
roleName: currentWork.data.position,
|
roleName: currentWork.data.position,
|
||||||
startDate: currentWork.data.startDate.toISOString(),
|
startDate: currentWork.data.startDate.toISOString()
|
||||||
},
|
},
|
||||||
worksFor: currentWork && {
|
worksFor: currentWork && {
|
||||||
'@type': 'Organization',
|
'@type': 'Organization',
|
||||||
name: currentWork?.data.name,
|
name: currentWork?.data.name,
|
||||||
sameAs: currentWork?.data.url,
|
sameAs: currentWork?.data.url
|
||||||
},
|
},
|
||||||
alumniOf: otherWork.map((w) => ({
|
alumniOf: otherWork.map((w) => ({
|
||||||
'@type': 'Organization',
|
'@type': 'Organization',
|
||||||
@@ -76,14 +76,14 @@ const getJsonLDResume = async (data: Data) => {
|
|||||||
'@type': 'EmployeeRole',
|
'@type': 'EmployeeRole',
|
||||||
roleName: w.data.position,
|
roleName: w.data.position,
|
||||||
startDate: w.data.startDate.toISOString(),
|
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 {
|
class Work {
|
||||||
public find = () => getCollection('work');
|
public find = () => getCollection('work')
|
||||||
public get = async (slug: string) => {
|
public get = async (slug: string) => {
|
||||||
const collection = await this.find();
|
const collection = await this.find()
|
||||||
return collection.find((entry) => entry.slug === slug);
|
return collection.find((entry) => entry.slug === slug)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>;
|
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>
|
||||||
export { Work, type WorkItem };
|
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 { type Article, data } from '@/data/data.js'
|
||||||
import { getArticleJsonLD } from '@/data/data.utils';
|
import { getArticleJsonLD } from '@/data/data.utils'
|
||||||
|
|
||||||
import Html from '../html/html.astro';
|
import Html from '../html/html.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Article;
|
article: Article
|
||||||
};
|
}
|
||||||
const { props } = Astro;
|
const { props } = Astro
|
||||||
const { article } = props;
|
const { article } = props
|
||||||
const { Content } = await article.render();
|
const { Content } = await article.render()
|
||||||
---
|
---
|
||||||
|
|
||||||
<Html
|
<Html
|
||||||
@@ -24,22 +24,22 @@ const { Content } = await article.render();
|
|||||||
<h1>
|
<h1>
|
||||||
{article.data.title.split(' ').map((word) => <span>{word}</span>)}
|
{article.data.title.split(' ').map((word) => <span>{word}</span>)}
|
||||||
</h1>
|
</h1>
|
||||||
<a href="/"><h2>By {data.profile.basics.name}</h2></a>
|
<a href='/'><h2>By {data.profile.basics.name}</h2></a>
|
||||||
</header>
|
</header>
|
||||||
<Picture
|
<Picture
|
||||||
loading="eager"
|
loading='eager'
|
||||||
class="img"
|
class='img'
|
||||||
src={article.data.heroImage}
|
src={article.data.heroImage}
|
||||||
widths={[320, 640, 1024, 1400]}
|
widths={[320, 640, 1024, 1400]}
|
||||||
formats={['avif', 'webp', 'png']}
|
formats={['avif', 'webp', 'png']}
|
||||||
alt="Cover image"
|
alt='Cover image'
|
||||||
/>
|
/>
|
||||||
<div class="content">
|
<div class='content'>
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</Html>
|
</Html>
|
||||||
<style lang="less">
|
<style lang='less'>
|
||||||
article {
|
article {
|
||||||
--left-padding: 100px;
|
--left-padding: 100px;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
---
|
---
|
||||||
import type { Article } from '@/data/data.js';
|
import type { Article } from '@/data/data.js'
|
||||||
import { range } from '@/utils/data';
|
import { range } from '@/utils/data'
|
||||||
|
|
||||||
import Html from '../html/html.astro';
|
import Html from '../html/html.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pageNumber: number;
|
pageNumber: number
|
||||||
pageCount: number;
|
pageCount: number
|
||||||
articles: Article[];
|
articles: Article[]
|
||||||
};
|
}
|
||||||
const { articles, pageNumber, pageCount } = Astro.props;
|
const { articles, pageNumber, pageCount } = Astro.props
|
||||||
const hasPrev = pageNumber > 1;
|
const hasPrev = pageNumber > 1
|
||||||
const hasNext = pageNumber < pageCount;
|
const hasNext = pageNumber < pageCount
|
||||||
---
|
---
|
||||||
|
|
||||||
<Html title="Articles" description="A list of articles">
|
<Html title='Articles' description='A list of articles'>
|
||||||
<h1>Articles</h1>
|
<h1>Articles</h1>
|
||||||
{
|
{
|
||||||
articles.map((article) => (
|
articles.map((article) => (
|
||||||
@@ -26,7 +26,9 @@ const hasNext = pageNumber < pageCount;
|
|||||||
}
|
}
|
||||||
|
|
||||||
<nav>
|
<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) => (
|
range(1, pageCount).map((page) => (
|
||||||
<a
|
<a
|
||||||
@@ -37,11 +39,13 @@ const hasNext = pageNumber < pageCount;
|
|||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}> Next </a>
|
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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 = {
|
type Props = {
|
||||||
class?: string;
|
class?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const { class: className, ...rest } = Astro.props;
|
const { class: className, ...rest } = Astro.props
|
||||||
const articleCount = 6;
|
const articleCount = 6
|
||||||
const allArticles = await data.articles.find();
|
const allArticles = await data.articles.find()
|
||||||
const sortedArticles = allArticles.sort(
|
const sortedArticles = allArticles.sort(
|
||||||
(a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(),
|
(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);
|
const hasMore = sortedArticles.length > articleCount
|
||||||
|
const articles = sortedArticles.slice(0, articleCount)
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class:list={['articles', className]} {...rest}>
|
<div class:list={['articles', className]} {...rest}>
|
||||||
<h2>Articles</h2>
|
<h2>Articles</h2>
|
||||||
<div class="items">
|
<div class='items'>
|
||||||
{articles.map((article) => <Article article={article} />)}
|
{articles.map((article) => <Article article={article} />)}
|
||||||
</div>
|
</div>
|
||||||
{hasMore && <a href="/articles/pages/1">View all articles</a>}
|
{hasMore && <a href='/articles/pages/1'>View all articles</a>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
.articles {
|
.articles {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-lg);
|
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 Time from '@/components/time/absolute.astro'
|
||||||
import type { Article } from '@/data/data.js';
|
import type { Article } from '@/data/data.js'
|
||||||
import { formatDate } from '@/utils/time.js';
|
import { formatDate } from '@/utils/time.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Article;
|
article: Article
|
||||||
};
|
}
|
||||||
|
|
||||||
const { article: item } = Astro.props;
|
const { article: item } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={`/articles/${item.slug}`}>
|
<a href={`/articles/${item.slug}`}>
|
||||||
<article>
|
<article>
|
||||||
<Picture
|
<Picture
|
||||||
class="thumb"
|
class='thumb'
|
||||||
alt="thumbnail image"
|
alt='thumbnail image'
|
||||||
src={item.data.heroImage}
|
src={item.data.heroImage}
|
||||||
formats={['avif', 'webp', 'jpeg']}
|
formats={['avif', 'webp', 'jpeg']}
|
||||||
width={100}
|
width={100}
|
||||||
/>
|
/>
|
||||||
<div class="content">
|
<div class='content'>
|
||||||
<small>
|
<small>
|
||||||
<Time format={formatDate} datetime={item.data.pubDate} />
|
<Time format={formatDate} datetime={item.data.pubDate} />
|
||||||
</small>
|
</small>
|
||||||
@@ -30,7 +30,7 @@ const { article: item } = Astro.props;
|
|||||||
</article>
|
</article>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
a {
|
a {
|
||||||
width: 45%;
|
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 = {
|
type Props = {
|
||||||
class?: string;
|
class?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const { class: className, ...rest } = Astro.props;
|
const { class: className, ...rest } = Astro.props
|
||||||
const { Content, basics, image } = data.profile;
|
const { Content, basics, image } = data.profile
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class:list={['main', className]} {...rest}>
|
<div class:list={['main', className]} {...rest}>
|
||||||
<Picture
|
<Picture
|
||||||
class="picture"
|
class='picture'
|
||||||
alt="Profile Picture"
|
alt='Profile Picture'
|
||||||
src={image}
|
src={image}
|
||||||
formats={['avif', 'webp', 'jpeg']}
|
formats={['avif', 'webp', 'jpeg']}
|
||||||
width={230}
|
width={230}
|
||||||
/>
|
/>
|
||||||
<h1>{basics.name}</h1>
|
<h1>{basics.name}</h1>
|
||||||
<h2>{basics.tagline}</h2>
|
<h2>{basics.tagline}</h2>
|
||||||
<div class="description">
|
<div class='description'>
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
<div class="profiles">
|
<div class='profiles'>
|
||||||
{basics.profiles.map((profile) => <Profile profile={profile} />)}
|
{basics.profiles.map((profile) => <Profile profile={profile} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.main {
|
.main {
|
||||||
display: flex;
|
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 = {
|
type Props = {
|
||||||
profile: Profile['basics']['profiles'][number];
|
profile: Profile['basics']['profiles'][number]
|
||||||
};
|
}
|
||||||
const { profile } = Astro.props;
|
const { profile } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={profile.url} target="_blank">
|
<a href={profile.url} target='_blank'>
|
||||||
<Icon class="icon" name={profile.icon} />
|
<Icon class='icon' name={profile.icon} />
|
||||||
<div class="network">{profile.network}</div>
|
<div class='network'>{profile.network}</div>
|
||||||
<div class="username">{profile.username}</div>
|
<div class='username'>{profile.username}</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
a {
|
a {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
---
|
---
|
||||||
import { data } from '@/data/data';
|
import { data } from '@/data/data'
|
||||||
|
|
||||||
import Html from '../html/html.astro';
|
import Html from '../html/html.astro'
|
||||||
import Articles from './articles/articles.astro';
|
import Articles from './articles/articles.astro'
|
||||||
import Description from './description/description.astro';
|
import Description from './description/description.astro'
|
||||||
import Info from './info/info.astro';
|
import Info from './info/info.astro'
|
||||||
import Skills from './skills/skills.astro';
|
import Skills from './skills/skills.astro'
|
||||||
import Work from './work/work.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}>
|
<Html
|
||||||
<div class="wrapper">
|
title={data.profile.basics.name}
|
||||||
<div class="frontpage">
|
description='Landing page'
|
||||||
<Description class="description" />
|
jsonLd={jsonLd}
|
||||||
<Info class="info" />
|
>
|
||||||
<Articles class="articles" />
|
<div class='wrapper'>
|
||||||
<Skills class="skills" />
|
<div class='frontpage'>
|
||||||
<Work class="work" />
|
<Description class='description' />
|
||||||
|
<Info class='info' />
|
||||||
|
<Articles class='articles' />
|
||||||
|
<Skills class='skills' />
|
||||||
|
<Work class='work' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
--gap: var(--space-xxl);
|
--gap: var(--space-xxl);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string;
|
class?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const { class: className, ...rest } = Astro.props;
|
const { class: className, ...rest } = Astro.props
|
||||||
const { basics } = data.profile;
|
const { basics } = data.profile
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class:list={['sidebar', className]} {...rest}>
|
<div class:list={['sidebar', className]} {...rest}>
|
||||||
@@ -28,7 +28,7 @@ const { basics } = data.profile;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
.sidebar {
|
.sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
---
|
---
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string;
|
class?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const { class: className, ...rest } = Astro.props;
|
const { class: className, ...rest } = Astro.props
|
||||||
const skills = await data.skills.find();
|
const skills = await data.skills.find()
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class:list={['skills', className]} {...rest}>
|
<div class:list={['skills', className]} {...rest}>
|
||||||
<h2>Skills</h2>
|
<h2>Skills</h2>
|
||||||
<div class="skill">
|
<div class='skill'>
|
||||||
{
|
{
|
||||||
skills.map((item) => (
|
skills.map((item) => (
|
||||||
<div class="item">
|
<div class='item'>
|
||||||
<h3>{item.data.name}</h3>
|
<h3>{item.data.name}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{item.data.technologies.map((tech) => (
|
{item.data.technologies.map((tech) => (
|
||||||
@@ -27,7 +27,7 @@ const skills = await data.skills.find();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
h2 {
|
h2 {
|
||||||
font-size: var(--font-xl);
|
font-size: var(--font-xl);
|
||||||
margin-bottom: var(--space-lg);
|
margin-bottom: var(--space-lg);
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
---
|
---
|
||||||
import Time from '@/components/time/absolute.astro';
|
import Time from '@/components/time/absolute.astro'
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
import { formatYearMonth } from '@/utils/time.js';
|
import { formatYearMonth } from '@/utils/time.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string;
|
class?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const { class: className, ...rest } = Astro.props;
|
const { class: className, ...rest } = Astro.props
|
||||||
const allWork = await data.work.find();
|
const allWork = await data.work.find()
|
||||||
const work = allWork.sort((a, b) => {
|
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}>
|
<div class:list={[className]} {...rest}>
|
||||||
<h2>Work</h2>
|
<h2>Work</h2>
|
||||||
<div class="list">
|
<div class='list'>
|
||||||
{
|
{
|
||||||
work.map((item) => (
|
work.map((item) => (
|
||||||
<div class="item">
|
<div class='item'>
|
||||||
<div class="time">
|
<div class='time'>
|
||||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||||
<br />
|
<br />
|
||||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class='content'>
|
||||||
<h3>{item.data.position}</h3>
|
<h3>{item.data.position}</h3>
|
||||||
<h4>@ {item.data.name}</h4>
|
<h4>@ {item.data.name}</h4>
|
||||||
<p>{item.data.summary}</p>
|
<p>{item.data.summary}</p>
|
||||||
@@ -34,10 +36,10 @@ const work = allWork.sort((a, b) => {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a href="/work-history">See full work history</a>
|
<a href='/work-history'>See full work history</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
h2 {
|
h2 {
|
||||||
font-size: var(--font-xl);
|
font-size: var(--font-xl);
|
||||||
margin-bottom: var(--space-lg);
|
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 { icons } from '@/assets/images/icons.js'
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string
|
||||||
description: string;
|
description: string
|
||||||
image?: string;
|
image?: string
|
||||||
jsonLd?: unknown;
|
jsonLd?: unknown
|
||||||
};
|
}
|
||||||
const { props } = Astro;
|
const { props } = Astro
|
||||||
const schema = JSON.stringify(props.jsonLd);
|
const schema = JSON.stringify(props.jsonLd)
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang='en'>
|
||||||
<Head>
|
<Head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset='UTF-8' />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||||
<meta name="HandheldFriendly" content="True" />
|
<meta name='HandheldFriendly' content='True' />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||||
<title>{props.title}</title>
|
<title>{props.title}</title>
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel='sitemap' href='/sitemap-index.xml' />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel='manifest' href='/manifest.webmanifest' />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name='generator' content={Astro.generator} />
|
||||||
<meta name="theme-color" content={data.site.theme} />
|
<meta name='theme-color' content={data.site.theme} />
|
||||||
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/articles/rss.xml" />
|
<link
|
||||||
{props.description && <meta name="description" content={props.description} />}
|
rel='alternate'
|
||||||
{props.image && <meta property="og:image" content={props.image} />}
|
type='application/rss+xml'
|
||||||
{props.jsonLd && <script type="application/ld+json" is:inline set:html={schema} />}
|
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) => (
|
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>
|
</Head>
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
---
|
---
|
||||||
import Header from '@/components/header/header.astro';
|
import Header from '@/components/header/header.astro'
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
|
|
||||||
import Html from '../html/html.astro';
|
import Html from '../html/html.astro'
|
||||||
import Item from './work-history.item.astro';
|
import Item from './work-history.item.astro'
|
||||||
|
|
||||||
const allWork = await data.work.find();
|
const allWork = await data.work.find()
|
||||||
const work = allWork.sort((a, b) => b.data.startDate.getTime() - a.data.startDate.getTime());
|
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 />
|
<Header />
|
||||||
<div class="wrapper">
|
<div class='wrapper'>
|
||||||
<h2>Work history</h2>
|
<h2>Work history</h2>
|
||||||
<div class="list">
|
<div class='list'>
|
||||||
{work.map((item) => <Item item={item} />)}
|
{work.map((item) => <Item item={item} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: var(--content-width);
|
max-width: var(--content-width);
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
---
|
---
|
||||||
import Time from '@/components/time/absolute.astro';
|
import Time from '@/components/time/absolute.astro'
|
||||||
import type { WorkItem } from '@/data/data.js';
|
import type { WorkItem } from '@/data/data.js'
|
||||||
import { formatYearMonth } from '@/utils/time.js';
|
import { formatYearMonth } from '@/utils/time.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: WorkItem;
|
item: WorkItem
|
||||||
};
|
}
|
||||||
|
|
||||||
const { item } = Astro.props;
|
const { item } = Astro.props
|
||||||
const { Content } = await item.render();
|
const { Content } = await item.render()
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="item">
|
<div class='item'>
|
||||||
<div class="time">
|
<div class='time'>
|
||||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||||
-
|
-
|
||||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class='main'>
|
||||||
<h3>{item.data.position}</h3>
|
<h3>{item.data.position}</h3>
|
||||||
<h4>{item.data.name}</h4>
|
<h4>{item.data.name}</h4>
|
||||||
<div class="content">
|
<div class='content'>
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang='less'>
|
||||||
.item {
|
.item {
|
||||||
display: contents;
|
display: contents;
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
---
|
---
|
||||||
import { type Article, data } from '@/data/data.js';
|
import { type Article, data } from '@/data/data.js'
|
||||||
import ArticleView from '@/layouts/article/article.astro';
|
import ArticleView from '@/layouts/article/article.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Article;
|
article: Article
|
||||||
};
|
}
|
||||||
|
|
||||||
const getStaticPaths = async () => {
|
export const getStaticPaths = async () => {
|
||||||
const articles = await data.articles.find();
|
const articles = await data.articles.find()
|
||||||
return articles.map((article) => ({
|
return articles.map((article) => ({
|
||||||
params: { slug: article.slug },
|
params: { slug: article.slug },
|
||||||
props: { article },
|
props: { article }
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
const { props } = Astro;
|
const { props } = Astro
|
||||||
const { article } = props;
|
const { article } = props
|
||||||
|
|
||||||
export { getStaticPaths };
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<ArticleView article={article} />
|
<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 { type Article, data } from '@/data/data.js'
|
||||||
import Articles from '@/layouts/articles/articles.astro';
|
import Articles from '@/layouts/articles/articles.astro'
|
||||||
import { range } from '@/utils/data.js';
|
import { range } from '@/utils/data.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
articles: Article[];
|
articles: Article[]
|
||||||
pageNumber: number;
|
pageNumber: number
|
||||||
pageCount: number;
|
pageCount: number
|
||||||
pageSize: number;
|
pageSize: number
|
||||||
};
|
}
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const pageSize = 2;
|
const pageSize = 2
|
||||||
const allArticles = await data.articles.find();
|
const allArticles = await data.articles.find()
|
||||||
const pageCount = Math.ceil(allArticles.length / pageSize);
|
const pageCount = Math.ceil(allArticles.length / pageSize)
|
||||||
const pages = range(0, pageCount).map((index) => {
|
const pages = range(0, pageCount).map((index) => {
|
||||||
const start = index * pageSize;
|
const start = index * pageSize
|
||||||
const end = start + pageSize;
|
const end = start + pageSize
|
||||||
return {
|
return {
|
||||||
pageNumber: index + 1,
|
pageNumber: index + 1,
|
||||||
pageCount,
|
pageCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
articles: allArticles.slice(start, end),
|
articles: allArticles.slice(start, end)
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
return pages.map((page) => ({
|
return pages.map((page) => ({
|
||||||
params: { page: String(page.pageNumber) },
|
params: { page: String(page.pageNumber) },
|
||||||
props: page,
|
props: page
|
||||||
}));
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const { props } = Astro;
|
const { props } = Astro
|
||||||
---
|
---
|
||||||
|
|
||||||
<Articles {...props} />
|
<Articles {...props} />
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { data } from '@/data/data.ts';
|
import { data } from '@/data/data.ts'
|
||||||
import rss from '@astrojs/rss';
|
import rss from '@astrojs/rss'
|
||||||
import type { APIContext } from 'astro';
|
import type { APIContext } from 'astro'
|
||||||
|
|
||||||
export async function GET(context: APIContext) {
|
export async function GET(context: APIContext) {
|
||||||
const articles = await data.articles.find();
|
const articles = await data.articles.find()
|
||||||
const profile = data.profile;
|
const profile = data.profile
|
||||||
return rss({
|
return rss({
|
||||||
title: profile.basics.name,
|
title: profile.basics.name,
|
||||||
description: profile.basics.tagline,
|
description: profile.basics.tagline,
|
||||||
site: context.site || 'http://localhost:3000',
|
site: context.site || 'http://localhost:3000',
|
||||||
items: articles.map((article) => ({
|
items: articles.map((article) => ({
|
||||||
...article.data,
|
...article.data,
|
||||||
link: `/articles/${article.slug}/`,
|
link: `/articles/${article.slug}/`
|
||||||
})),
|
}))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import {} from '@/data/data.js';
|
import {} from '@/data/data.js'
|
||||||
import Frontpage from '@/layouts/frontpage/frontpage.astro';
|
import Frontpage from '@/layouts/frontpage/frontpage.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Frontpage />
|
<Frontpage />
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { icons } from '@/assets/images/icons.js';
|
import { icons } from '@/assets/images/icons.js'
|
||||||
import { data } from '@/data/data.js';
|
import { data } from '@/data/data.js'
|
||||||
import type { ManifestOptions } from 'vite-plugin-pwa';
|
import type { ManifestOptions } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const [maskableIcon] = icons.pngs.filter(
|
const [maskableIcon] = icons.pngs.filter(
|
||||||
(icon) => icon.size === '512x512' && icon.src.includes('png'),
|
(icon) => icon.size === '512x512' && icon.src.includes('png')
|
||||||
);
|
)
|
||||||
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon);
|
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon)
|
||||||
const basics = data.profile.basics;
|
const basics = data.profile.basics
|
||||||
|
|
||||||
const manifest: Partial<ManifestOptions> = {
|
const manifest: Partial<ManifestOptions> = {
|
||||||
name: basics.name,
|
name: basics.name,
|
||||||
@@ -21,7 +21,7 @@ export async function GET() {
|
|||||||
...nonMaskableIcons.map((png) => ({
|
...nonMaskableIcons.map((png) => ({
|
||||||
src: png.src,
|
src: png.src,
|
||||||
sizes: png.size,
|
sizes: png.size,
|
||||||
type: 'image/png',
|
type: 'image/png'
|
||||||
})),
|
})),
|
||||||
...(maskableIcon
|
...(maskableIcon
|
||||||
? [
|
? [
|
||||||
@@ -29,11 +29,11 @@ export async function GET() {
|
|||||||
src: maskableIcon.src,
|
src: maskableIcon.src,
|
||||||
sizes: maskableIcon.size,
|
sizes: maskableIcon.size,
|
||||||
type: 'image/png',
|
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() {
|
export async function GET() {
|
||||||
const resume = await data.getJsonResume();
|
const resume = await data.getJsonResume()
|
||||||
return new Response(JSON.stringify(resume, null, 2));
|
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 />
|
<Work />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
|
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||||
Cantarell, 'Helvetica Neue', sans-serif;
|
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
|
||||||
--font-family-heading: var(--font-family);
|
--font-family-heading: var(--font-family);
|
||||||
--font-size: 15px;
|
--font-size: 15px;
|
||||||
--letter-spacing: 0.5px;
|
--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
|
* 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 {
|
export interface ResumeSchema {
|
||||||
/**
|
/**
|
||||||
* link to the version of the schema that can validate the resume
|
* link to the version of the schema that can validate the resume
|
||||||
*/
|
*/
|
||||||
$schema?: string;
|
$schema?: string
|
||||||
basics?: {
|
basics?: {
|
||||||
name?: string;
|
name?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Web Developer
|
* e.g. Web Developer
|
||||||
*/
|
*/
|
||||||
label?: string;
|
label?: string
|
||||||
/**
|
/**
|
||||||
* URL (as per RFC 3986) to a image in JPEG or PNG format
|
* URL (as per RFC 3986) to a image in JPEG or PNG format
|
||||||
*/
|
*/
|
||||||
image?: string;
|
image?: string
|
||||||
/**
|
/**
|
||||||
* e.g. thomas@gmail.com
|
* 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 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 (as per RFC 3986) to your website, e.g. personal homepage
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
/**
|
/**
|
||||||
* Write a short 2-3 sentence biography about yourself
|
* Write a short 2-3 sentence biography about yourself
|
||||||
*/
|
*/
|
||||||
summary?: string;
|
summary?: string
|
||||||
location?: {
|
location?: {
|
||||||
/**
|
/**
|
||||||
* To add multiple address lines, use
|
* To add multiple address lines, use
|
||||||
* . For example, 1234 Glücklichkeit Straße
|
* . For example, 1234 Glücklichkeit Straße
|
||||||
* Hinterhaus 5. Etage li.
|
* Hinterhaus 5. Etage li.
|
||||||
*/
|
*/
|
||||||
address?: string;
|
address?: string
|
||||||
postalCode?: string;
|
postalCode?: string
|
||||||
city?: string;
|
city?: string
|
||||||
/**
|
/**
|
||||||
* code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN
|
* 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.
|
* The general region where you live. Can be a US state, or a province, for instance.
|
||||||
*/
|
*/
|
||||||
region?: string;
|
region?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
};
|
}
|
||||||
/**
|
/**
|
||||||
* Specify any number of social networks that you participate in
|
* Specify any number of social networks that you participate in
|
||||||
*/
|
*/
|
||||||
@@ -60,106 +60,106 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. Facebook or Twitter
|
* e.g. Facebook or Twitter
|
||||||
*/
|
*/
|
||||||
network?: string;
|
network?: string
|
||||||
/**
|
/**
|
||||||
* e.g. neutralthoughts
|
* e.g. neutralthoughts
|
||||||
*/
|
*/
|
||||||
username?: string;
|
username?: string
|
||||||
/**
|
/**
|
||||||
* e.g. http://twitter.example.com/neutralthoughts
|
* e.g. http://twitter.example.com/neutralthoughts
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
};
|
}
|
||||||
work?: {
|
work?: {
|
||||||
/**
|
/**
|
||||||
* e.g. Facebook
|
* e.g. Facebook
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Menlo Park, CA
|
* e.g. Menlo Park, CA
|
||||||
*/
|
*/
|
||||||
location?: string;
|
location?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Social Media Company
|
* e.g. Social Media Company
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Software Engineer
|
* e.g. Software Engineer
|
||||||
*/
|
*/
|
||||||
position?: string;
|
position?: string
|
||||||
/**
|
/**
|
||||||
* e.g. http://facebook.example.com
|
* e.g. http://facebook.example.com
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
startDate?: Iso8601;
|
startDate?: Iso8601
|
||||||
endDate?: Iso8601;
|
endDate?: Iso8601
|
||||||
/**
|
/**
|
||||||
* Give an overview of your responsibilities at the company
|
* Give an overview of your responsibilities at the company
|
||||||
*/
|
*/
|
||||||
summary?: string;
|
summary?: string
|
||||||
/**
|
/**
|
||||||
* Specify multiple accomplishments
|
* Specify multiple accomplishments
|
||||||
*/
|
*/
|
||||||
highlights?: string[];
|
highlights?: string[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
volunteer?: {
|
volunteer?: {
|
||||||
/**
|
/**
|
||||||
* e.g. Facebook
|
* e.g. Facebook
|
||||||
*/
|
*/
|
||||||
organization?: string;
|
organization?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Software Engineer
|
* e.g. Software Engineer
|
||||||
*/
|
*/
|
||||||
position?: string;
|
position?: string
|
||||||
/**
|
/**
|
||||||
* e.g. http://facebook.example.com
|
* e.g. http://facebook.example.com
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
startDate?: Iso8601;
|
startDate?: Iso8601
|
||||||
endDate?: Iso8601;
|
endDate?: Iso8601
|
||||||
/**
|
/**
|
||||||
* Give an overview of your responsibilities at the company
|
* Give an overview of your responsibilities at the company
|
||||||
*/
|
*/
|
||||||
summary?: string;
|
summary?: string
|
||||||
/**
|
/**
|
||||||
* Specify accomplishments and achievements
|
* Specify accomplishments and achievements
|
||||||
*/
|
*/
|
||||||
highlights?: string[];
|
highlights?: string[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
education?: {
|
education?: {
|
||||||
/**
|
/**
|
||||||
* e.g. Massachusetts Institute of Technology
|
* e.g. Massachusetts Institute of Technology
|
||||||
*/
|
*/
|
||||||
institution?: string;
|
institution?: string
|
||||||
/**
|
/**
|
||||||
* e.g. http://facebook.example.com
|
* e.g. http://facebook.example.com
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Arts
|
* e.g. Arts
|
||||||
*/
|
*/
|
||||||
area?: string;
|
area?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Bachelor
|
* e.g. Bachelor
|
||||||
*/
|
*/
|
||||||
studyType?: string;
|
studyType?: string
|
||||||
startDate?: Iso8601;
|
startDate?: Iso8601
|
||||||
endDate?: Iso8601;
|
endDate?: Iso8601
|
||||||
/**
|
/**
|
||||||
* grade point average, e.g. 3.67/4.0
|
* grade point average, e.g. 3.67/4.0
|
||||||
*/
|
*/
|
||||||
score?: string;
|
score?: string
|
||||||
/**
|
/**
|
||||||
* List notable courses/subjects
|
* List notable courses/subjects
|
||||||
*/
|
*/
|
||||||
courses?: string[];
|
courses?: string[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* Specify any awards you have received throughout your professional career
|
* 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
|
* e.g. One of the 100 greatest minds of the century
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string
|
||||||
date?: Iso8601;
|
date?: Iso8601
|
||||||
/**
|
/**
|
||||||
* e.g. Time Magazine
|
* e.g. Time Magazine
|
||||||
*/
|
*/
|
||||||
awarder?: string;
|
awarder?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Received for my work with Quantum Physics
|
* e.g. Received for my work with Quantum Physics
|
||||||
*/
|
*/
|
||||||
summary?: string;
|
summary?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* Specify any certificates you have received throughout your professional career
|
* Specify any certificates you have received throughout your professional career
|
||||||
*/
|
*/
|
||||||
@@ -186,18 +186,18 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. Certified Kubernetes Administrator
|
* e.g. Certified Kubernetes Administrator
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
date?: Iso8601;
|
date?: Iso8601
|
||||||
/**
|
/**
|
||||||
* e.g. http://example.com
|
* e.g. http://example.com
|
||||||
*/
|
*/
|
||||||
url?: string;
|
url?: string
|
||||||
/**
|
/**
|
||||||
* e.g. CNCF
|
* e.g. CNCF
|
||||||
*/
|
*/
|
||||||
issuer?: string;
|
issuer?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* Specify your publications through your career
|
* Specify your publications through your career
|
||||||
*/
|
*/
|
||||||
@@ -205,22 +205,22 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. The World Wide Web
|
* e.g. The World Wide Web
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
/**
|
/**
|
||||||
* e.g. IEEE, Computer Magazine
|
* e.g. IEEE, Computer Magazine
|
||||||
*/
|
*/
|
||||||
publisher?: string;
|
publisher?: string
|
||||||
releaseDate?: Iso8601;
|
releaseDate?: Iso8601
|
||||||
/**
|
/**
|
||||||
* e.g. http://www.computer.org.example.com/csdl/mags/co/1996/10/rx069-abs.html
|
* 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.
|
* Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML.
|
||||||
*/
|
*/
|
||||||
summary?: string;
|
summary?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* List out your professional skill-set
|
* List out your professional skill-set
|
||||||
*/
|
*/
|
||||||
@@ -228,17 +228,17 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. Web Development
|
* e.g. Web Development
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Master
|
* e.g. Master
|
||||||
*/
|
*/
|
||||||
level?: string;
|
level?: string
|
||||||
/**
|
/**
|
||||||
* List some keywords pertaining to this skill
|
* List some keywords pertaining to this skill
|
||||||
*/
|
*/
|
||||||
keywords?: string[];
|
keywords?: string[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* List any other languages you speak
|
* List any other languages you speak
|
||||||
*/
|
*/
|
||||||
@@ -246,21 +246,21 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. English, Spanish
|
* e.g. English, Spanish
|
||||||
*/
|
*/
|
||||||
language?: string;
|
language?: string
|
||||||
/**
|
/**
|
||||||
* e.g. Fluent, Beginner
|
* e.g. Fluent, Beginner
|
||||||
*/
|
*/
|
||||||
fluency?: string;
|
fluency?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
interests?: {
|
interests?: {
|
||||||
/**
|
/**
|
||||||
* e.g. Philosophy
|
* e.g. Philosophy
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
keywords?: string[];
|
keywords?: string[]
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* List references you have received
|
* List references you have received
|
||||||
*/
|
*/
|
||||||
@@ -268,13 +268,13 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. Timothy Cook
|
* 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.
|
* 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;
|
reference?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* Specify career projects
|
* Specify career projects
|
||||||
*/
|
*/
|
||||||
@@ -282,39 +282,39 @@ export interface ResumeSchema {
|
|||||||
/**
|
/**
|
||||||
* e.g. The World Wide Web
|
* e.g. The World Wide Web
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string
|
||||||
/**
|
/**
|
||||||
* Short summary of project. e.g. Collated works of 2017.
|
* Short summary of project. e.g. Collated works of 2017.
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description?: string
|
||||||
/**
|
/**
|
||||||
* Specify multiple features
|
* Specify multiple features
|
||||||
*/
|
*/
|
||||||
highlights?: string[];
|
highlights?: string[]
|
||||||
/**
|
/**
|
||||||
* Specify special elements involved
|
* Specify special elements involved
|
||||||
*/
|
*/
|
||||||
keywords?: string[];
|
keywords?: string[]
|
||||||
startDate?: Iso8601;
|
startDate?: Iso8601
|
||||||
endDate?: Iso8601;
|
endDate?: Iso8601
|
||||||
/**
|
/**
|
||||||
* e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html
|
* 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
|
* Specify your role on this project or in company
|
||||||
*/
|
*/
|
||||||
roles?: string[];
|
roles?: string[]
|
||||||
/**
|
/**
|
||||||
* Specify the relevant company/entity affiliations e.g. 'greenpeace', 'corporationXYZ'
|
* Specify the relevant company/entity affiliations e.g. 'greenpeace', 'corporationXYZ'
|
||||||
*/
|
*/
|
||||||
entity?: string;
|
entity?: string
|
||||||
/**
|
/**
|
||||||
* e.g. 'volunteering', 'presentation', 'talk', 'application', 'conference'
|
* e.g. 'volunteering', 'presentation', 'talk', 'application', 'conference'
|
||||||
*/
|
*/
|
||||||
type?: string;
|
type?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
}[];
|
}[]
|
||||||
/**
|
/**
|
||||||
* The schema version and any other tooling configuration lives here
|
* 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
|
* 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
|
* A version field which follows semver - e.g. v1.0.0
|
||||||
*/
|
*/
|
||||||
version?: string;
|
version?: string
|
||||||
/**
|
/**
|
||||||
* Using ISO 8601 with YYYY-MM-DDThh:mm:ss
|
* Using ISO 8601 with YYYY-MM-DDThh:mm:ss
|
||||||
*/
|
*/
|
||||||
lastModified?: string;
|
lastModified?: string
|
||||||
[k: string]: unknown;
|
[k: string]: unknown
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const range = (start: number, end: number) => {
|
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 = {
|
const formatDate: Intl.DateTimeFormatOptions = {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric'
|
||||||
};
|
}
|
||||||
|
|
||||||
const formatYearMonth: Intl.DateTimeFormatOptions = {
|
const formatYearMonth: Intl.DateTimeFormatOptions = {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short'
|
||||||
};
|
}
|
||||||
|
|
||||||
export { formatDate, formatYearMonth };
|
export { formatDate, formatYearMonth }
|
||||||
|
|||||||
Reference in New Issue
Block a user