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,7 +1,7 @@
|
||||
import { getImage } from 'astro:assets';
|
||||
import { data } from '@/data/data.js';
|
||||
import { getImage } from 'astro:assets'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
const imageSizes = [16, 32, 48, 64, 96, 128, 256, 512];
|
||||
const imageSizes = [16, 32, 48, 64, 96, 128, 256, 512]
|
||||
|
||||
const pngs = await Promise.all(
|
||||
imageSizes.map(async (size) => {
|
||||
@@ -10,14 +10,14 @@ const pngs = await Promise.all(
|
||||
src: data.profile.image,
|
||||
format: 'png',
|
||||
width: size,
|
||||
height: size,
|
||||
height: size
|
||||
})),
|
||||
size: `${size}x${size}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
size: `${size}x${size}`
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const icons = {
|
||||
pngs,
|
||||
};
|
||||
export { icons };
|
||||
pngs
|
||||
}
|
||||
export { icons }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
import { data } from '@/data/data';
|
||||
import { data } from '@/data/data'
|
||||
|
||||
const { basics } = data.profile;
|
||||
const { basics } = data.profile
|
||||
---
|
||||
|
||||
<nav>
|
||||
<a href="/">{basics.name}</a>
|
||||
<a href='/'>{basics.name}</a>
|
||||
<div>{basics.tagline}</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
nav {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
type Props = {
|
||||
datetime: Date | undefined;
|
||||
format?: Intl.DateTimeFormatOptions;
|
||||
};
|
||||
const { datetime, format, ...rest } = Astro.props;
|
||||
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime);
|
||||
datetime: Date | undefined
|
||||
format?: Intl.DateTimeFormatOptions
|
||||
}
|
||||
const { datetime, format, ...rest } = Astro.props
|
||||
const formatted = Intl.DateTimeFormat('en-US', format).format(datetime)
|
||||
---
|
||||
|
||||
{
|
||||
|
||||
@@ -6,11 +6,11 @@ color: '#e7d9ac'
|
||||
heroImage: ./assets/cover.png
|
||||
---
|
||||
|
||||
import { Image } from 'astro:assets';
|
||||
import TaskBounds from './assets/TaskBounds.png';
|
||||
import Frame1 from './assets/Frame1.png';
|
||||
import Graph1 from './assets/Graph1.png';
|
||||
import Graph2 from './assets/Graph2.png';
|
||||
import { Image } from 'astro:assets'
|
||||
import TaskBounds from './assets/TaskBounds.png'
|
||||
import Frame1 from './assets/Frame1.png'
|
||||
import Graph1 from './assets/Graph1.png'
|
||||
import Graph2 from './assets/Graph2.png'
|
||||
|
||||
Allow me to introduce Bob. Bob is an algorithm, and he has just accepted a role as my assistant.
|
||||
|
||||
@@ -26,7 +26,7 @@ Also, I wanted a planning algorithm that was not only for productivity. I did no
|
||||
|
||||
Bob is still pretty young and still learning new things, but he has gotten to the point where I believe he is good enough to start to use on a day to day basis.
|
||||
|
||||
<Image src={Frame1} alt="Frame1" />
|
||||
<Image src={Frame1} alt='Frame1' />
|
||||
|
||||
How does Bob work? Bob gets a list of tasks, some from my calendar (both my work and my personal calendar), some from "routines" (which are daily tasks that I want to do most days, such as eating breakfast or picking up the kid), and some tasks come from "goals" which are a list of completable items. These tasks go into Bob, and he tries to create a plan for the next couple of days where I get everything done that I set out to do.
|
||||
|
||||
@@ -38,7 +38,7 @@ An "earliest start time" and a "latest start time". These define when the task c
|
||||
- If the task is required.
|
||||
- A priority
|
||||
|
||||
<Image src={TaskBounds} alt="Task bounds" />
|
||||
<Image src={TaskBounds} alt='Task bounds' />
|
||||
|
||||
Bob uses a graph walk to create the optimal plan, where each node contains a few different things
|
||||
|
||||
@@ -53,11 +53,11 @@ Bob starts by figuring out which locations I can go to complete the remaining ta
|
||||
He then gets a list of all the remaining tasks for the current node which can be completed at the current location, again figuring out when I would be done with the task, updating the list of impossible tasks and scoring the node.
|
||||
If any node adds a required task to the impossible list, that node is considered dead, and Bob will not analyze it further.
|
||||
|
||||
<Image src={Graph1} alt="Graph1" />
|
||||
<Image src={Graph1} alt='Graph1' />
|
||||
|
||||
Now we have a list of active leaves, and from that list, we find the node with the highest score and redo the process from above.
|
||||
|
||||
<Image src={Graph2} alt="Graph2" />
|
||||
<Image src={Graph2} alt='Graph2' />
|
||||
|
||||
Bob has four different strategies for finding a plan.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ description: ''
|
||||
heroImage: ./assets/cover.png
|
||||
---
|
||||
|
||||
import graph from './assets/graph.png';
|
||||
import { Image } from 'astro:assets';
|
||||
import graph from './assets/graph.png'
|
||||
import { Image } from 'astro:assets'
|
||||
|
||||
I have been playing around with smart homes for a long time; I have used most of the platforms out there, I have developed quite a few myself, and one thing I keep coming back to is Redux.
|
||||
|
||||
@@ -66,7 +66,7 @@ Now comes the part I have feared, where I need to draw a diagram.
|
||||
|
||||
...sorry
|
||||
|
||||
<Image src={graph} alt="graph" />
|
||||
<Image src={graph} alt='graph' />
|
||||
|
||||
So this shows our final setup.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { defineCollection, z } from 'astro:content'
|
||||
|
||||
const articles = defineCollection({
|
||||
schema: ({ image }) =>
|
||||
@@ -10,10 +10,10 @@ const articles = defineCollection({
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
heroImage: image().refine((img) => img.width >= 320, {
|
||||
message: 'Cover image must be at least 1080 pixels wide!',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
message: 'Cover image must be at least 1080 pixels wide!'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const work = defineCollection({
|
||||
schema: ({ image }) =>
|
||||
@@ -26,16 +26,16 @@ const work = defineCollection({
|
||||
url: z.string().optional(),
|
||||
logo: image()
|
||||
.refine((img) => img.width >= 200, {
|
||||
message: 'Logo must be at least 320 pixels wide!',
|
||||
message: 'Logo must be at least 320 pixels wide!'
|
||||
})
|
||||
.optional(),
|
||||
banner: image()
|
||||
.refine((img) => img.height >= 50, {
|
||||
message: 'Logo must be at least 320 pixels wide!',
|
||||
message: 'Logo must be at least 320 pixels wide!'
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
.optional()
|
||||
})
|
||||
})
|
||||
|
||||
const references = defineCollection({
|
||||
schema: () =>
|
||||
@@ -45,16 +45,16 @@ const references = defineCollection({
|
||||
company: z.string(),
|
||||
date: z.coerce.date(),
|
||||
relation: z.string(),
|
||||
profile: z.string(),
|
||||
}),
|
||||
});
|
||||
profile: z.string()
|
||||
})
|
||||
})
|
||||
|
||||
const skills = defineCollection({
|
||||
schema: () =>
|
||||
z.object({
|
||||
name: z.string(),
|
||||
technologies: z.array(z.string()),
|
||||
}),
|
||||
});
|
||||
technologies: z.array(z.string())
|
||||
})
|
||||
})
|
||||
|
||||
export const collections = { articles, work, references, skills };
|
||||
export const collections = { articles, work, references, skills }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js';
|
||||
import { Content } from './description.md';
|
||||
import image from './profile.jpg';
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js'
|
||||
import { Content } from './description.md'
|
||||
import image from './profile.jpg'
|
||||
|
||||
const basics = {
|
||||
name: 'Morten Olsen',
|
||||
@@ -11,38 +11,38 @@ const basics = {
|
||||
location: {
|
||||
city: 'Copenhagen',
|
||||
countryCode: 'DK',
|
||||
region: 'Capital Region of Denmark',
|
||||
region: 'Capital Region of Denmark'
|
||||
},
|
||||
profiles: [
|
||||
{
|
||||
network: 'GitHub',
|
||||
icon: 'mdi:github',
|
||||
username: 'morten-olsen',
|
||||
url: 'https://github.com/morten-olsen',
|
||||
url: 'https://github.com/morten-olsen'
|
||||
},
|
||||
{
|
||||
network: 'LinkedIn',
|
||||
icon: 'mdi:linkedin',
|
||||
username: 'mortenolsendk',
|
||||
url: 'https://www.linkedin.com/in/mortenolsendk',
|
||||
},
|
||||
url: 'https://www.linkedin.com/in/mortenolsendk'
|
||||
}
|
||||
],
|
||||
languages: [
|
||||
{
|
||||
name: 'English',
|
||||
fluency: 'Conversational',
|
||||
fluency: 'Conversational'
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
fluency: 'Native speaker',
|
||||
},
|
||||
],
|
||||
} satisfies ResumeSchema['basics'];
|
||||
fluency: 'Native speaker'
|
||||
}
|
||||
]
|
||||
} satisfies ResumeSchema['basics']
|
||||
|
||||
const profile = {
|
||||
basics,
|
||||
image,
|
||||
Content,
|
||||
};
|
||||
Content
|
||||
}
|
||||
|
||||
export { profile };
|
||||
export { profile }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Articles {
|
||||
public find = () => getCollection('articles');
|
||||
public find = () => getCollection('articles')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Article = Exclude<Awaited<ReturnType<Articles['get']>>, undefined>;
|
||||
type Article = Exclude<Awaited<ReturnType<Articles['get']>>, undefined>
|
||||
|
||||
export { Articles, type Article };
|
||||
export { Articles, type Article }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class References {
|
||||
public find = () => getCollection('references');
|
||||
public find = () => getCollection('references')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Reference = Exclude<Awaited<ReturnType<References['get']>>, undefined>;
|
||||
type Reference = Exclude<Awaited<ReturnType<References['get']>>, undefined>
|
||||
|
||||
export { References, type Reference };
|
||||
export { References, type Reference }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const site = {
|
||||
theme: '#30E130',
|
||||
};
|
||||
theme: '#30E130'
|
||||
}
|
||||
|
||||
export { site };
|
||||
export { site }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Skills {
|
||||
public find = () => getCollection('skills');
|
||||
public find = () => getCollection('skills')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type Skill = Exclude<Awaited<ReturnType<Skills['get']>>, undefined>;
|
||||
type Skill = Exclude<Awaited<ReturnType<Skills['get']>>, undefined>
|
||||
|
||||
export { Skills, type Skill };
|
||||
export { Skills, type Skill }
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { profile } from '../content/profile/profile.js';
|
||||
import { type Article, Articles } from './data.articles.js';
|
||||
import { References } from './data.references.ts';
|
||||
import { site } from './data.site.ts';
|
||||
import { Skills } from './data.skills.ts';
|
||||
import { getJsonLDResume, getJsonResume } from './data.utils.js';
|
||||
import { Work, type WorkItem } from './data.work.js';
|
||||
import { profile } from '../content/profile/profile.js'
|
||||
import { type Article, Articles } from './data.articles.js'
|
||||
import { References } from './data.references.ts'
|
||||
import { site } from './data.site.ts'
|
||||
import { Skills } from './data.skills.ts'
|
||||
import { getJsonLDResume, getJsonResume } from './data.utils.js'
|
||||
import { Work, type WorkItem } from './data.work.js'
|
||||
|
||||
class Data {
|
||||
public articles = new Articles();
|
||||
public work = new Work();
|
||||
public references = new References();
|
||||
public skills = new Skills();
|
||||
public profile = profile;
|
||||
public site = site;
|
||||
public articles = new Articles()
|
||||
public work = new Work()
|
||||
public references = new References()
|
||||
public skills = new Skills()
|
||||
public profile = profile
|
||||
public site = site
|
||||
|
||||
public getJsonResume = getJsonResume.bind(null, this);
|
||||
public getJsonLDResume = getJsonLDResume.bind(null, this);
|
||||
public getJsonResume = getJsonResume.bind(null, this)
|
||||
public getJsonLDResume = getJsonLDResume.bind(null, this)
|
||||
}
|
||||
|
||||
const data = new Data();
|
||||
const data = new Data()
|
||||
|
||||
type Profile = typeof profile;
|
||||
export type { Article, Profile, WorkItem };
|
||||
export { data, Data };
|
||||
type Profile = typeof profile
|
||||
export type { Article, Profile, WorkItem }
|
||||
export { data, Data }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js';
|
||||
import type { ResumeSchema } from '@/types/resume-schema.js'
|
||||
|
||||
import type { Article, Data } from './data';
|
||||
import type { Article, Data } from './data'
|
||||
|
||||
const getJsonResume = async (data: Data) => {
|
||||
const profile = data.profile;
|
||||
const profile = data.profile
|
||||
const resume = {
|
||||
basics: profile.basics,
|
||||
} satisfies ResumeSchema;
|
||||
basics: profile.basics
|
||||
} satisfies ResumeSchema
|
||||
|
||||
return resume;
|
||||
};
|
||||
return resume
|
||||
}
|
||||
|
||||
const getArticleJsonLD = async (data: Data, article: Article) => {
|
||||
const jsonld = {
|
||||
@@ -23,16 +23,16 @@ const getArticleJsonLD = async (data: Data, article: Article) => {
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: data.profile.basics.name,
|
||||
url: data.profile.basics.url,
|
||||
},
|
||||
};
|
||||
return jsonld;
|
||||
};
|
||||
url: data.profile.basics.url
|
||||
}
|
||||
}
|
||||
return jsonld
|
||||
}
|
||||
|
||||
const getJsonLDResume = async (data: Data) => {
|
||||
const work = await data.work.find();
|
||||
const currentWork = work.find((w) => !w.data.endDate);
|
||||
const otherWork = work.filter((w) => w !== currentWork);
|
||||
const work = await data.work.find()
|
||||
const currentWork = work.find((w) => !w.data.endDate)
|
||||
const otherWork = work.filter((w) => w !== currentWork)
|
||||
|
||||
const jsonld = {
|
||||
'@context': 'https://schema.org',
|
||||
@@ -47,24 +47,24 @@ const getJsonLDResume = async (data: Data) => {
|
||||
'@type': 'ContactPoint',
|
||||
contactType: profile.network.toLowerCase(),
|
||||
identifier: profile.username,
|
||||
url: profile.url,
|
||||
url: profile.url
|
||||
})),
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
addressLocality: data.profile.basics.location.city,
|
||||
addressRegion: data.profile.basics.location.region,
|
||||
addressCountry: data.profile.basics.location.countryCode,
|
||||
addressCountry: data.profile.basics.location.countryCode
|
||||
},
|
||||
sameAs: data.profile.basics.profiles.map((profile) => profile.url),
|
||||
hasOccupation: currentWork && {
|
||||
'@type': 'EmployeeRole',
|
||||
roleName: currentWork.data.position,
|
||||
startDate: currentWork.data.startDate.toISOString(),
|
||||
startDate: currentWork.data.startDate.toISOString()
|
||||
},
|
||||
worksFor: currentWork && {
|
||||
'@type': 'Organization',
|
||||
name: currentWork?.data.name,
|
||||
sameAs: currentWork?.data.url,
|
||||
sameAs: currentWork?.data.url
|
||||
},
|
||||
alumniOf: otherWork.map((w) => ({
|
||||
'@type': 'Organization',
|
||||
@@ -76,14 +76,14 @@ const getJsonLDResume = async (data: Data) => {
|
||||
'@type': 'EmployeeRole',
|
||||
roleName: w.data.position,
|
||||
startDate: w.data.startDate.toISOString(),
|
||||
endDate: w.data.endDate?.toISOString(),
|
||||
endDate: w.data.endDate?.toISOString()
|
||||
},
|
||||
sameAs: '#me',
|
||||
},
|
||||
})),
|
||||
};
|
||||
sameAs: '#me'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
return jsonld;
|
||||
};
|
||||
return jsonld
|
||||
}
|
||||
|
||||
export { getJsonResume, getJsonLDResume, getArticleJsonLD };
|
||||
export { getJsonResume, getJsonLDResume, getArticleJsonLD }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
class Work {
|
||||
public find = () => getCollection('work');
|
||||
public find = () => getCollection('work')
|
||||
public get = async (slug: string) => {
|
||||
const collection = await this.find();
|
||||
return collection.find((entry) => entry.slug === slug);
|
||||
};
|
||||
const collection = await this.find()
|
||||
return collection.find((entry) => entry.slug === slug)
|
||||
}
|
||||
}
|
||||
|
||||
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>;
|
||||
export { Work, type WorkItem };
|
||||
type WorkItem = Exclude<Awaited<ReturnType<Work['get']>>, undefined>
|
||||
export { Work, type WorkItem }
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import { getArticleJsonLD } from '@/data/data.utils';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import { getArticleJsonLD } from '@/data/data.utils'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Html from '../html/html.astro'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
const { props } = Astro;
|
||||
const { article } = props;
|
||||
const { Content } = await article.render();
|
||||
article: Article
|
||||
}
|
||||
const { props } = Astro
|
||||
const { article } = props
|
||||
const { Content } = await article.render()
|
||||
---
|
||||
|
||||
<Html
|
||||
@@ -24,22 +24,22 @@ const { Content } = await article.render();
|
||||
<h1>
|
||||
{article.data.title.split(' ').map((word) => <span>{word}</span>)}
|
||||
</h1>
|
||||
<a href="/"><h2>By {data.profile.basics.name}</h2></a>
|
||||
<a href='/'><h2>By {data.profile.basics.name}</h2></a>
|
||||
</header>
|
||||
<Picture
|
||||
loading="eager"
|
||||
class="img"
|
||||
loading='eager'
|
||||
class='img'
|
||||
src={article.data.heroImage}
|
||||
widths={[320, 640, 1024, 1400]}
|
||||
formats={['avif', 'webp', 'png']}
|
||||
alt="Cover image"
|
||||
alt='Cover image'
|
||||
/>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
</Html>
|
||||
<style lang="less">
|
||||
<style lang='less'>
|
||||
article {
|
||||
--left-padding: 100px;
|
||||
display: grid;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
import type { Article } from '@/data/data.js';
|
||||
import { range } from '@/utils/data';
|
||||
import type { Article } from '@/data/data.js'
|
||||
import { range } from '@/utils/data'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Html from '../html/html.astro'
|
||||
|
||||
type Props = {
|
||||
pageNumber: number;
|
||||
pageCount: number;
|
||||
articles: Article[];
|
||||
};
|
||||
const { articles, pageNumber, pageCount } = Astro.props;
|
||||
const hasPrev = pageNumber > 1;
|
||||
const hasNext = pageNumber < pageCount;
|
||||
pageNumber: number
|
||||
pageCount: number
|
||||
articles: Article[]
|
||||
}
|
||||
const { articles, pageNumber, pageCount } = Astro.props
|
||||
const hasPrev = pageNumber > 1
|
||||
const hasNext = pageNumber < pageCount
|
||||
---
|
||||
|
||||
<Html title="Articles" description="A list of articles">
|
||||
<Html title='Articles' description='A list of articles'>
|
||||
<h1>Articles</h1>
|
||||
{
|
||||
articles.map((article) => (
|
||||
@@ -26,7 +26,9 @@ const hasNext = pageNumber < pageCount;
|
||||
}
|
||||
|
||||
<nav>
|
||||
<a aria-disabled={!hasPrev} href={`/articles/pages/${pageNumber - 1}`}>Previous</a>
|
||||
<a aria-disabled={!hasPrev} href={`/articles/pages/${pageNumber - 1}`}
|
||||
>Previous</a
|
||||
>
|
||||
{
|
||||
range(1, pageCount).map((page) => (
|
||||
<a
|
||||
@@ -37,11 +39,13 @@ const hasNext = pageNumber < pageCount;
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}> Next </a>
|
||||
<a aria-disabled={!hasNext} href={`/articles/pages/${pageNumber + 1}`}>
|
||||
Next
|
||||
</a>
|
||||
</nav>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Article from './articles.item.astro';
|
||||
import Article from './articles.item.astro'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const articleCount = 6;
|
||||
const allArticles = await data.articles.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const articleCount = 6
|
||||
const allArticles = await data.articles.find()
|
||||
const sortedArticles = allArticles.sort(
|
||||
(a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(),
|
||||
);
|
||||
const hasMore = sortedArticles.length > articleCount;
|
||||
const articles = sortedArticles.slice(0, articleCount);
|
||||
(a, b) =>
|
||||
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
|
||||
)
|
||||
const hasMore = sortedArticles.length > articleCount
|
||||
const articles = sortedArticles.slice(0, articleCount)
|
||||
---
|
||||
|
||||
<div class:list={['articles', className]} {...rest}>
|
||||
<h2>Articles</h2>
|
||||
<div class="items">
|
||||
<div class='items'>
|
||||
{articles.map((article) => <Article article={article} />)}
|
||||
</div>
|
||||
{hasMore && <a href="/articles/pages/1">View all articles</a>}
|
||||
{hasMore && <a href='/articles/pages/1'>View all articles</a>}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.articles {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import type { Article } from '@/data/data.js';
|
||||
import { formatDate } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import type { Article } from '@/data/data.js'
|
||||
import { formatDate } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
article: Article
|
||||
}
|
||||
|
||||
const { article: item } = Astro.props;
|
||||
const { article: item } = Astro.props
|
||||
---
|
||||
|
||||
<a href={`/articles/${item.slug}`}>
|
||||
<article>
|
||||
<Picture
|
||||
class="thumb"
|
||||
alt="thumbnail image"
|
||||
class='thumb'
|
||||
alt='thumbnail image'
|
||||
src={item.data.heroImage}
|
||||
formats={['avif', 'webp', 'jpeg']}
|
||||
width={100}
|
||||
/>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<small>
|
||||
<Time format={formatDate} datetime={item.data.pubDate} />
|
||||
</small>
|
||||
@@ -30,7 +30,7 @@ const { article: item } = Astro.props;
|
||||
</article>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
a {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import { Picture } from 'astro:assets'
|
||||
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Profile from './description.profile.astro';
|
||||
import Profile from './description.profile.astro'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const { Content, basics, image } = data.profile;
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const { Content, basics, image } = data.profile
|
||||
---
|
||||
|
||||
<div class:list={['main', className]} {...rest}>
|
||||
<Picture
|
||||
class="picture"
|
||||
alt="Profile Picture"
|
||||
class='picture'
|
||||
alt='Profile Picture'
|
||||
src={image}
|
||||
formats={['avif', 'webp', 'jpeg']}
|
||||
width={230}
|
||||
/>
|
||||
<h1>{basics.name}</h1>
|
||||
<h2>{basics.tagline}</h2>
|
||||
<div class="description">
|
||||
<div class='description'>
|
||||
<Content />
|
||||
</div>
|
||||
<div class="profiles">
|
||||
<div class='profiles'>
|
||||
{basics.profiles.map((profile) => <Profile profile={profile} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
@media screen and (max-width: 768px) {
|
||||
.main {
|
||||
display: flex;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { Icon } from 'astro-icon/components'
|
||||
|
||||
import type { Profile } from '@/data/data';
|
||||
import type { Profile } from '@/data/data'
|
||||
|
||||
type Props = {
|
||||
profile: Profile['basics']['profiles'][number];
|
||||
};
|
||||
const { profile } = Astro.props;
|
||||
profile: Profile['basics']['profiles'][number]
|
||||
}
|
||||
const { profile } = Astro.props
|
||||
---
|
||||
|
||||
<a href={profile.url} target="_blank">
|
||||
<Icon class="icon" name={profile.icon} />
|
||||
<div class="network">{profile.network}</div>
|
||||
<div class="username">{profile.username}</div>
|
||||
<a href={profile.url} target='_blank'>
|
||||
<Icon class='icon' name={profile.icon} />
|
||||
<div class='network'>{profile.network}</div>
|
||||
<div class='username'>{profile.username}</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
a {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
---
|
||||
import { data } from '@/data/data';
|
||||
import { data } from '@/data/data'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Articles from './articles/articles.astro';
|
||||
import Description from './description/description.astro';
|
||||
import Info from './info/info.astro';
|
||||
import Skills from './skills/skills.astro';
|
||||
import Work from './work/work.astro';
|
||||
import Html from '../html/html.astro'
|
||||
import Articles from './articles/articles.astro'
|
||||
import Description from './description/description.astro'
|
||||
import Info from './info/info.astro'
|
||||
import Skills from './skills/skills.astro'
|
||||
import Work from './work/work.astro'
|
||||
|
||||
const jsonLd = await data.getJsonLDResume();
|
||||
const jsonLd = await data.getJsonLDResume()
|
||||
---
|
||||
|
||||
<Html title={data.profile.basics.name} description="Landing page" jsonLd={jsonLd}>
|
||||
<div class="wrapper">
|
||||
<div class="frontpage">
|
||||
<Description class="description" />
|
||||
<Info class="info" />
|
||||
<Articles class="articles" />
|
||||
<Skills class="skills" />
|
||||
<Work class="work" />
|
||||
<Html
|
||||
title={data.profile.basics.name}
|
||||
description='Landing page'
|
||||
jsonLd={jsonLd}
|
||||
>
|
||||
<div class='wrapper'>
|
||||
<div class='frontpage'>
|
||||
<Description class='description' />
|
||||
<Info class='info' />
|
||||
<Articles class='articles' />
|
||||
<Skills class='skills' />
|
||||
<Work class='work' />
|
||||
</div>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.wrapper {
|
||||
--gap: var(--space-xxl);
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const { basics } = data.profile;
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const { basics } = data.profile
|
||||
---
|
||||
|
||||
<div class:list={['sidebar', className]} {...rest}>
|
||||
@@ -28,7 +28,7 @@ const { basics } = data.profile;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
import { data } from '@/data/data.js';
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const skills = await data.skills.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const skills = await data.skills.find()
|
||||
---
|
||||
|
||||
<div class:list={['skills', className]} {...rest}>
|
||||
<h2>Skills</h2>
|
||||
<div class="skill">
|
||||
<div class='skill'>
|
||||
{
|
||||
skills.map((item) => (
|
||||
<div class="item">
|
||||
<div class='item'>
|
||||
<h3>{item.data.name}</h3>
|
||||
<ul>
|
||||
{item.data.technologies.map((tech) => (
|
||||
@@ -27,7 +27,7 @@ const skills = await data.skills.find();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
h2 {
|
||||
font-size: var(--font-xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
---
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import { data } from '@/data/data.js';
|
||||
import { formatYearMonth } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import { data } from '@/data/data.js'
|
||||
import { formatYearMonth } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
class?: string;
|
||||
};
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { class: className, ...rest } = Astro.props;
|
||||
const allWork = await data.work.find();
|
||||
const { class: className, ...rest } = Astro.props
|
||||
const allWork = await data.work.find()
|
||||
const work = allWork.sort((a, b) => {
|
||||
return new Date(b.data.startDate).getTime() - new Date(a.data.startDate).getTime();
|
||||
});
|
||||
return (
|
||||
new Date(b.data.startDate).getTime() - new Date(a.data.startDate).getTime()
|
||||
)
|
||||
})
|
||||
---
|
||||
|
||||
<div class:list={[className]} {...rest}>
|
||||
<h2>Work</h2>
|
||||
<div class="list">
|
||||
<div class='list'>
|
||||
{
|
||||
work.map((item) => (
|
||||
<div class="item">
|
||||
<div class="time">
|
||||
<div class='item'>
|
||||
<div class='time'>
|
||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||
<br />
|
||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<h3>{item.data.position}</h3>
|
||||
<h4>@ {item.data.name}</h4>
|
||||
<p>{item.data.summary}</p>
|
||||
@@ -34,10 +36,10 @@ const work = allWork.sort((a, b) => {
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<a href="/work-history">See full work history</a>
|
||||
<a href='/work-history'>See full work history</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
h2 {
|
||||
font-size: var(--font-xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
|
||||
@@ -1,40 +1,53 @@
|
||||
---
|
||||
import '@/style/theme.css';
|
||||
import '@/style/theme.css'
|
||||
|
||||
import { Head } from 'astro-capo';
|
||||
import { Head } from 'astro-capo'
|
||||
|
||||
import { icons } from '@/assets/images/icons.js';
|
||||
import { data } from '@/data/data.js';
|
||||
import { icons } from '@/assets/images/icons.js'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
jsonLd?: unknown;
|
||||
};
|
||||
const { props } = Astro;
|
||||
const schema = JSON.stringify(props.jsonLd);
|
||||
title: string
|
||||
description: string
|
||||
image?: string
|
||||
jsonLd?: unknown
|
||||
}
|
||||
const { props } = Astro
|
||||
const schema = JSON.stringify(props.jsonLd)
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang='en'>
|
||||
<Head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta charset='UTF-8' />
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||
<meta name='HandheldFriendly' content='True' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||
<title>{props.title}</title>
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="theme-color" content={data.site.theme} />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/articles/rss.xml" />
|
||||
{props.description && <meta name="description" content={props.description} />}
|
||||
{props.image && <meta property="og:image" content={props.image} />}
|
||||
{props.jsonLd && <script type="application/ld+json" is:inline set:html={schema} />}
|
||||
<link rel='sitemap' href='/sitemap-index.xml' />
|
||||
<link rel='manifest' href='/manifest.webmanifest' />
|
||||
<meta name='generator' content={Astro.generator} />
|
||||
<meta name='theme-color' content={data.site.theme} />
|
||||
<link
|
||||
rel='alternate'
|
||||
type='application/rss+xml'
|
||||
title='RSS Feed'
|
||||
href='/articles/rss.xml'
|
||||
/>
|
||||
{
|
||||
props.description && (
|
||||
<meta name='description' content={props.description} />
|
||||
)
|
||||
}
|
||||
{props.image && <meta property='og:image' content={props.image} />}
|
||||
{
|
||||
props.jsonLd && (
|
||||
<script type='application/ld+json' is:inline set:html={schema} />
|
||||
)
|
||||
}
|
||||
{
|
||||
icons.pngs.map((icon) => (
|
||||
<link rel="icon" href={icon.src} type="image/png" sizes={icon.size} />
|
||||
<link rel='icon' href={icon.src} type='image/png' sizes={icon.size} />
|
||||
))
|
||||
}
|
||||
</Head>
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
---
|
||||
import Header from '@/components/header/header.astro';
|
||||
import { data } from '@/data/data.js';
|
||||
import Header from '@/components/header/header.astro'
|
||||
import { data } from '@/data/data.js'
|
||||
|
||||
import Html from '../html/html.astro';
|
||||
import Item from './work-history.item.astro';
|
||||
import Html from '../html/html.astro'
|
||||
import Item from './work-history.item.astro'
|
||||
|
||||
const allWork = await data.work.find();
|
||||
const work = allWork.sort((a, b) => b.data.startDate.getTime() - a.data.startDate.getTime());
|
||||
const allWork = await data.work.find()
|
||||
const work = allWork.sort(
|
||||
(a, b) => b.data.startDate.getTime() - a.data.startDate.getTime()
|
||||
)
|
||||
---
|
||||
|
||||
<Html title="Work" description="A list of work experiences">
|
||||
<Html title='Work' description='A list of work experiences'>
|
||||
<Header />
|
||||
<div class="wrapper">
|
||||
<div class='wrapper'>
|
||||
<h2>Work history</h2>
|
||||
<div class="list">
|
||||
<div class='list'>
|
||||
{work.map((item) => <Item item={item} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: var(--content-width);
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
import Time from '@/components/time/absolute.astro';
|
||||
import type { WorkItem } from '@/data/data.js';
|
||||
import { formatYearMonth } from '@/utils/time.js';
|
||||
import Time from '@/components/time/absolute.astro'
|
||||
import type { WorkItem } from '@/data/data.js'
|
||||
import { formatYearMonth } from '@/utils/time.js'
|
||||
|
||||
type Props = {
|
||||
item: WorkItem;
|
||||
};
|
||||
item: WorkItem
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
const { Content } = await item.render();
|
||||
const { item } = Astro.props
|
||||
const { Content } = await item.render()
|
||||
---
|
||||
|
||||
<div class="item">
|
||||
<div class="time">
|
||||
<div class='item'>
|
||||
<div class='time'>
|
||||
<Time format={formatYearMonth} datetime={item.data.endDate} />
|
||||
-
|
||||
<Time format={formatYearMonth} datetime={item.data.startDate} />
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class='main'>
|
||||
<h3>{item.data.position}</h3>
|
||||
<h4>{item.data.name}</h4>
|
||||
<div class="content">
|
||||
<div class='content'>
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang='less'>
|
||||
.item {
|
||||
display: contents;
|
||||
break-inside: avoid;
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
---
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import ArticleView from '@/layouts/article/article.astro';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import ArticleView from '@/layouts/article/article.astro'
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
article: Article
|
||||
}
|
||||
|
||||
const getStaticPaths = async () => {
|
||||
const articles = await data.articles.find();
|
||||
export const getStaticPaths = async () => {
|
||||
const articles = await data.articles.find()
|
||||
return articles.map((article) => ({
|
||||
params: { slug: article.slug },
|
||||
props: { article },
|
||||
}));
|
||||
};
|
||||
const { props } = Astro;
|
||||
const { article } = props;
|
||||
|
||||
export { getStaticPaths };
|
||||
props: { article }
|
||||
}))
|
||||
}
|
||||
const { props } = Astro
|
||||
const { article } = props
|
||||
---
|
||||
|
||||
<ArticleView article={article} />
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { type Article, data } from '@/data/data.ts';
|
||||
import type { APIContext } from 'astro';
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
};
|
||||
|
||||
export async function GET(context: APIContext<Props>) {
|
||||
const { props } = context;
|
||||
const { article } = props;
|
||||
return new Response(JSON.stringify(article), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const articles = await data.articles.find();
|
||||
return articles.map((article) => ({
|
||||
params: { slug: article.slug },
|
||||
props: { article },
|
||||
}));
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
---
|
||||
import { type Article, data } from '@/data/data.js';
|
||||
import Articles from '@/layouts/articles/articles.astro';
|
||||
import { range } from '@/utils/data.js';
|
||||
import { type Article, data } from '@/data/data.js'
|
||||
import Articles from '@/layouts/articles/articles.astro'
|
||||
import { range } from '@/utils/data.js'
|
||||
|
||||
type Props = {
|
||||
articles: Article[];
|
||||
pageNumber: number;
|
||||
pageCount: number;
|
||||
pageSize: number;
|
||||
};
|
||||
articles: Article[]
|
||||
pageNumber: number
|
||||
pageCount: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const pageSize = 2;
|
||||
const allArticles = await data.articles.find();
|
||||
const pageCount = Math.ceil(allArticles.length / pageSize);
|
||||
const pageSize = 2
|
||||
const allArticles = await data.articles.find()
|
||||
const pageCount = Math.ceil(allArticles.length / pageSize)
|
||||
const pages = range(0, pageCount).map((index) => {
|
||||
const start = index * pageSize;
|
||||
const end = start + pageSize;
|
||||
const start = index * pageSize
|
||||
const end = start + pageSize
|
||||
return {
|
||||
pageNumber: index + 1,
|
||||
pageCount,
|
||||
pageSize,
|
||||
articles: allArticles.slice(start, end),
|
||||
};
|
||||
});
|
||||
articles: allArticles.slice(start, end)
|
||||
}
|
||||
})
|
||||
return pages.map((page) => ({
|
||||
params: { page: String(page.pageNumber) },
|
||||
props: page,
|
||||
}));
|
||||
props: page
|
||||
}))
|
||||
}
|
||||
|
||||
const { props } = Astro;
|
||||
const { props } = Astro
|
||||
---
|
||||
|
||||
<Articles {...props} />
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { data } from '@/data/data.ts';
|
||||
import rss from '@astrojs/rss';
|
||||
import type { APIContext } from 'astro';
|
||||
import { data } from '@/data/data.ts'
|
||||
import rss from '@astrojs/rss'
|
||||
import type { APIContext } from 'astro'
|
||||
|
||||
export async function GET(context: APIContext) {
|
||||
const articles = await data.articles.find();
|
||||
const profile = data.profile;
|
||||
const articles = await data.articles.find()
|
||||
const profile = data.profile
|
||||
return rss({
|
||||
title: profile.basics.name,
|
||||
description: profile.basics.tagline,
|
||||
site: context.site || 'http://localhost:3000',
|
||||
items: articles.map((article) => ({
|
||||
...article.data,
|
||||
link: `/articles/${article.slug}/`,
|
||||
})),
|
||||
});
|
||||
link: `/articles/${article.slug}/`
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import {} from '@/data/data.js';
|
||||
import Frontpage from '@/layouts/frontpage/frontpage.astro';
|
||||
import {} from '@/data/data.js'
|
||||
import Frontpage from '@/layouts/frontpage/frontpage.astro'
|
||||
---
|
||||
|
||||
<Frontpage />
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { icons } from '@/assets/images/icons.js';
|
||||
import { data } from '@/data/data.js';
|
||||
import type { ManifestOptions } from 'vite-plugin-pwa';
|
||||
import { icons } from '@/assets/images/icons.js'
|
||||
import { data } from '@/data/data.js'
|
||||
import type { ManifestOptions } from 'vite-plugin-pwa'
|
||||
|
||||
export async function GET() {
|
||||
const [maskableIcon] = icons.pngs.filter(
|
||||
(icon) => icon.size === '512x512' && icon.src.includes('png'),
|
||||
);
|
||||
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon);
|
||||
const basics = data.profile.basics;
|
||||
(icon) => icon.size === '512x512' && icon.src.includes('png')
|
||||
)
|
||||
const nonMaskableIcons = icons.pngs.filter((icon) => icon !== maskableIcon)
|
||||
const basics = data.profile.basics
|
||||
|
||||
const manifest: Partial<ManifestOptions> = {
|
||||
name: basics.name,
|
||||
@@ -21,7 +21,7 @@ export async function GET() {
|
||||
...nonMaskableIcons.map((png) => ({
|
||||
src: png.src,
|
||||
sizes: png.size,
|
||||
type: 'image/png',
|
||||
type: 'image/png'
|
||||
})),
|
||||
...(maskableIcon
|
||||
? [
|
||||
@@ -29,11 +29,11 @@ export async function GET() {
|
||||
src: maskableIcon.src,
|
||||
sizes: maskableIcon.size,
|
||||
type: 'image/png',
|
||||
purpose: 'maskable',
|
||||
},
|
||||
purpose: 'maskable'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
return new Response(JSON.stringify(manifest, null, 2));
|
||||
: [])
|
||||
]
|
||||
}
|
||||
return new Response(JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { data } from '@/data/data.ts';
|
||||
import { data } from '@/data/data.ts'
|
||||
|
||||
export async function GET() {
|
||||
const resume = await data.getJsonResume();
|
||||
return new Response(JSON.stringify(resume, null, 2));
|
||||
const resume = await data.getJsonResume()
|
||||
return new Response(JSON.stringify(resume, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import Work from '@/layouts/work-history/work-history.astro';
|
||||
import Work from '@/layouts/work-history/work-history.astro'
|
||||
---
|
||||
|
||||
<Work />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:root {
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
|
||||
Cantarell, 'Helvetica Neue', sans-serif;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
|
||||
--font-family-heading: var(--font-family);
|
||||
--font-size: 15px;
|
||||
--letter-spacing: 0.5px;
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
/**
|
||||
* Similar to the standard date type, but each section after the year is optional. e.g. 2014-06-29 or 2023-04
|
||||
*/
|
||||
export type Iso8601 = string;
|
||||
export type Iso8601 = string
|
||||
|
||||
export interface ResumeSchema {
|
||||
/**
|
||||
* link to the version of the schema that can validate the resume
|
||||
*/
|
||||
$schema?: string;
|
||||
$schema?: string
|
||||
basics?: {
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Web Developer
|
||||
*/
|
||||
label?: string;
|
||||
label?: string
|
||||
/**
|
||||
* URL (as per RFC 3986) to a image in JPEG or PNG format
|
||||
*/
|
||||
image?: string;
|
||||
image?: string
|
||||
/**
|
||||
* e.g. thomas@gmail.com
|
||||
*/
|
||||
email?: string;
|
||||
email?: string
|
||||
/**
|
||||
* Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923
|
||||
*/
|
||||
phone?: string;
|
||||
phone?: string
|
||||
/**
|
||||
* URL (as per RFC 3986) to your website, e.g. personal homepage
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Write a short 2-3 sentence biography about yourself
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
location?: {
|
||||
/**
|
||||
* To add multiple address lines, use
|
||||
* . For example, 1234 Glücklichkeit Straße
|
||||
* Hinterhaus 5. Etage li.
|
||||
*/
|
||||
address?: string;
|
||||
postalCode?: string;
|
||||
city?: string;
|
||||
address?: string
|
||||
postalCode?: string
|
||||
city?: string
|
||||
/**
|
||||
* code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN
|
||||
*/
|
||||
countryCode?: string;
|
||||
countryCode?: string
|
||||
/**
|
||||
* The general region where you live. Can be a US state, or a province, for instance.
|
||||
*/
|
||||
region?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
region?: string
|
||||
[k: string]: unknown
|
||||
}
|
||||
/**
|
||||
* Specify any number of social networks that you participate in
|
||||
*/
|
||||
@@ -60,106 +60,106 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Facebook or Twitter
|
||||
*/
|
||||
network?: string;
|
||||
network?: string
|
||||
/**
|
||||
* e.g. neutralthoughts
|
||||
*/
|
||||
username?: string;
|
||||
username?: string
|
||||
/**
|
||||
* e.g. http://twitter.example.com/neutralthoughts
|
||||
*/
|
||||
url?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
url?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
[k: string]: unknown
|
||||
}
|
||||
work?: {
|
||||
/**
|
||||
* e.g. Facebook
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Menlo Park, CA
|
||||
*/
|
||||
location?: string;
|
||||
location?: string
|
||||
/**
|
||||
* e.g. Social Media Company
|
||||
*/
|
||||
description?: string;
|
||||
description?: string
|
||||
/**
|
||||
* e.g. Software Engineer
|
||||
*/
|
||||
position?: string;
|
||||
position?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
url?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* Give an overview of your responsibilities at the company
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
/**
|
||||
* Specify multiple accomplishments
|
||||
*/
|
||||
highlights?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
highlights?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
volunteer?: {
|
||||
/**
|
||||
* e.g. Facebook
|
||||
*/
|
||||
organization?: string;
|
||||
organization?: string
|
||||
/**
|
||||
* e.g. Software Engineer
|
||||
*/
|
||||
position?: string;
|
||||
position?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
url?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* Give an overview of your responsibilities at the company
|
||||
*/
|
||||
summary?: string;
|
||||
summary?: string
|
||||
/**
|
||||
* Specify accomplishments and achievements
|
||||
*/
|
||||
highlights?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
highlights?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
education?: {
|
||||
/**
|
||||
* e.g. Massachusetts Institute of Technology
|
||||
*/
|
||||
institution?: string;
|
||||
institution?: string
|
||||
/**
|
||||
* e.g. http://facebook.example.com
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* e.g. Arts
|
||||
*/
|
||||
area?: string;
|
||||
area?: string
|
||||
/**
|
||||
* e.g. Bachelor
|
||||
*/
|
||||
studyType?: string;
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
studyType?: string
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* grade point average, e.g. 3.67/4.0
|
||||
*/
|
||||
score?: string;
|
||||
score?: string
|
||||
/**
|
||||
* List notable courses/subjects
|
||||
*/
|
||||
courses?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
courses?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify any awards you have received throughout your professional career
|
||||
*/
|
||||
@@ -167,18 +167,18 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. One of the 100 greatest minds of the century
|
||||
*/
|
||||
title?: string;
|
||||
date?: Iso8601;
|
||||
title?: string
|
||||
date?: Iso8601
|
||||
/**
|
||||
* e.g. Time Magazine
|
||||
*/
|
||||
awarder?: string;
|
||||
awarder?: string
|
||||
/**
|
||||
* e.g. Received for my work with Quantum Physics
|
||||
*/
|
||||
summary?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
summary?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify any certificates you have received throughout your professional career
|
||||
*/
|
||||
@@ -186,18 +186,18 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Certified Kubernetes Administrator
|
||||
*/
|
||||
name?: string;
|
||||
date?: Iso8601;
|
||||
name?: string
|
||||
date?: Iso8601
|
||||
/**
|
||||
* e.g. http://example.com
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* e.g. CNCF
|
||||
*/
|
||||
issuer?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
issuer?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify your publications through your career
|
||||
*/
|
||||
@@ -205,22 +205,22 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. The World Wide Web
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. IEEE, Computer Magazine
|
||||
*/
|
||||
publisher?: string;
|
||||
releaseDate?: Iso8601;
|
||||
publisher?: string
|
||||
releaseDate?: Iso8601
|
||||
/**
|
||||
* e.g. http://www.computer.org.example.com/csdl/mags/co/1996/10/rx069-abs.html
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML.
|
||||
*/
|
||||
summary?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
summary?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List out your professional skill-set
|
||||
*/
|
||||
@@ -228,17 +228,17 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Web Development
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Master
|
||||
*/
|
||||
level?: string;
|
||||
level?: string
|
||||
/**
|
||||
* List some keywords pertaining to this skill
|
||||
*/
|
||||
keywords?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
keywords?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List any other languages you speak
|
||||
*/
|
||||
@@ -246,21 +246,21 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. English, Spanish
|
||||
*/
|
||||
language?: string;
|
||||
language?: string
|
||||
/**
|
||||
* e.g. Fluent, Beginner
|
||||
*/
|
||||
fluency?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
fluency?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
interests?: {
|
||||
/**
|
||||
* e.g. Philosophy
|
||||
*/
|
||||
name?: string;
|
||||
keywords?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
name?: string
|
||||
keywords?: string[]
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* List references you have received
|
||||
*/
|
||||
@@ -268,13 +268,13 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. Timothy Cook
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing.
|
||||
*/
|
||||
reference?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
reference?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* Specify career projects
|
||||
*/
|
||||
@@ -282,39 +282,39 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* e.g. The World Wide Web
|
||||
*/
|
||||
name?: string;
|
||||
name?: string
|
||||
/**
|
||||
* Short summary of project. e.g. Collated works of 2017.
|
||||
*/
|
||||
description?: string;
|
||||
description?: string
|
||||
/**
|
||||
* Specify multiple features
|
||||
*/
|
||||
highlights?: string[];
|
||||
highlights?: string[]
|
||||
/**
|
||||
* Specify special elements involved
|
||||
*/
|
||||
keywords?: string[];
|
||||
startDate?: Iso8601;
|
||||
endDate?: Iso8601;
|
||||
keywords?: string[]
|
||||
startDate?: Iso8601
|
||||
endDate?: Iso8601
|
||||
/**
|
||||
* e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html
|
||||
*/
|
||||
url?: string;
|
||||
url?: string
|
||||
/**
|
||||
* Specify your role on this project or in company
|
||||
*/
|
||||
roles?: string[];
|
||||
roles?: string[]
|
||||
/**
|
||||
* Specify the relevant company/entity affiliations e.g. 'greenpeace', 'corporationXYZ'
|
||||
*/
|
||||
entity?: string;
|
||||
entity?: string
|
||||
/**
|
||||
* e.g. 'volunteering', 'presentation', 'talk', 'application', 'conference'
|
||||
*/
|
||||
type?: string;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
type?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
/**
|
||||
* The schema version and any other tooling configuration lives here
|
||||
*/
|
||||
@@ -322,15 +322,15 @@ export interface ResumeSchema {
|
||||
/**
|
||||
* URL (as per RFC 3986) to latest version of this document
|
||||
*/
|
||||
canonical?: string;
|
||||
canonical?: string
|
||||
/**
|
||||
* A version field which follows semver - e.g. v1.0.0
|
||||
*/
|
||||
version?: string;
|
||||
version?: string
|
||||
/**
|
||||
* Using ISO 8601 with YYYY-MM-DDThh:mm:ss
|
||||
*/
|
||||
lastModified?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
lastModified?: string
|
||||
[k: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const range = (start: number, end: number) => {
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||||
};
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
}
|
||||
|
||||
export { range };
|
||||
export { range }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const formatDate: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
};
|
||||
day: 'numeric'
|
||||
}
|
||||
|
||||
const formatYearMonth: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
};
|
||||
month: 'short'
|
||||
}
|
||||
|
||||
export { formatDate, formatYearMonth };
|
||||
export { formatDate, formatYearMonth }
|
||||
|
||||
Reference in New Issue
Block a user