Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a8473b964 | |||
| 18211ad485 | |||
| 429cf94023 | |||
| 0497731c45 | |||
| 6c2c6da91d | |||
| 19e17ea947 | |||
| 3d9120c570 | |||
| 875b8a7f47 | |||
| 1ddc76ae69 | |||
| 6423ffba63 |
@@ -27,7 +27,7 @@ ENV SITE_URL=https://www.alexlebens.dev
|
|||||||
ENV DIRECTUS_URL=https://directus.alexlebens.net
|
ENV DIRECTUS_URL=https://directus.alexlebens.net
|
||||||
ENV PORT=4321
|
ENV PORT=4321
|
||||||
|
|
||||||
LABEL version="2.11.0"
|
LABEL version="2.12.0"
|
||||||
LABEL description="Astro based personal website"
|
LABEL description="Astro based personal website"
|
||||||
|
|
||||||
EXPOSE $PORT
|
EXPOSE $PORT
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "site-profile",
|
"name": "site-profile",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.11.0",
|
"version": "2.12.0",
|
||||||
"homepage": "https://www.alexlebens.dev",
|
"homepage": "https://www.alexlebens.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
|
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { getImage } from 'astro:assets';
|
|||||||
import { readSingleton } from '@directus/sdk';
|
import { readSingleton } from '@directus/sdk';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
|
import { SEO } from '@/config';
|
||||||
|
|
||||||
import brandSrc from '@images/brand_logo.png';
|
import brandSrc from '@images/brand_logo.png';
|
||||||
import faviconSvgSrc from '@images/favicon_icon.svg';
|
import faviconSvgSrc from '@images/favicon_icon.svg';
|
||||||
import faviconSrc from '@images/favicon_icon.png';
|
import faviconSrc from '@images/favicon_icon.png';
|
||||||
import { SEO } from '@/config';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -18,6 +19,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canonicalURL = Astro.url.href;
|
const canonicalURL = Astro.url.href;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@@ -27,14 +29,14 @@ let {
|
|||||||
structuredData = SEO.structuredData,
|
structuredData = SEO.structuredData,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
|
|
||||||
let card = 'summary_large_image';
|
let card = 'summary_large_image';
|
||||||
if (!ogImage) {
|
if (!ogImage) {
|
||||||
ogImage = brandSrc;
|
ogImage = brandSrc;
|
||||||
card = 'summary';
|
card = 'summary';
|
||||||
}
|
}
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
|
||||||
|
|
||||||
const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' });
|
const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' });
|
||||||
const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' });
|
const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' });
|
||||||
const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 });
|
const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 });
|
||||||
@@ -62,12 +64,12 @@ if (!socialImage.startsWith('http')) {
|
|||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="theme-color" content="#facc15" />
|
<meta name="theme-color" content="#facc15" />
|
||||||
|
<meta name="robots" content="index, follow" />
|
||||||
|
|
||||||
<!-- Open Graph -->
|
<!-- Open Graph -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta property="og:url" content={Astro.url} />
|
<meta property="og:url" content={Astro.url} />
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:title" content={ogTitle} />
|
<meta property="og:title" content={ogTitle} />
|
||||||
<meta property="og:site_name" content={global.name} />
|
<meta property="og:site_name" content={global.name} />
|
||||||
<meta property="og:description" content={ogDescription} />
|
<meta property="og:description" content={ogDescription} />
|
||||||
@@ -76,17 +78,10 @@ if (!socialImage.startsWith('http')) {
|
|||||||
<meta content="600" property="og:image:height" />
|
<meta content="600" property="og:image:height" />
|
||||||
<meta content="image/png" property="og:image:type" />
|
<meta content="image/png" property="og:image:type" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
|
||||||
<meta property="twitter:card" content={card} />
|
|
||||||
<meta property="twitter:url" content={Astro.url} />
|
|
||||||
<meta property="twitter:domain" content={Astro.url} />
|
|
||||||
<meta property="twitter:title" content={ogTitle} />
|
|
||||||
<meta property="twitter:description" content={ogDescription} />
|
|
||||||
<meta property="twitter:image" content={socialImage} />
|
|
||||||
|
|
||||||
<!-- Links -->
|
<!-- Links -->
|
||||||
<link href={canonicalURL} rel="canonical" />
|
<link href={canonicalURL} rel="canonical" />
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
|
<link rel="alternate" type="application/rss+xml" title={title} href="/rss.xml" />
|
||||||
<!--<link href="/manifest.json" rel="manifest" />-->
|
<!--<link href="/manifest.json" rel="manifest" />-->
|
||||||
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
|
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
|
||||||
<link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" />
|
<link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" />
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
import { readSingleton } from '@directus/sdk';
|
import { readSingleton } from '@directus/sdk';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
|
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
import { NavigationLinks, FooterLinks } from '@/config';
|
import { NavigationLinks, FooterLinks } from '@/config';
|
||||||
|
|
||||||
import footerImg from '@images/flowers.png';
|
import footerImg from '@images/flowers.png';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
---
|
|
||||||
import { Icon } from 'astro-icon/components';
|
|
||||||
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
|
||||||
|
|
||||||
import { getDirectusImageURL } from '@lib/directusFunctions';
|
|
||||||
import Image from '@components/ui/images/Image.astro';
|
|
||||||
import { formatDate } from '@support/time';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
post: Post;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { post } = Astro.props;
|
|
||||||
|
|
||||||
const baseClasses = 'group group-hover smooth-reveal-cards rounded-xl flex flex-col';
|
|
||||||
const borderClasses = 'border border-stone-200/50 dark:border-stone-700/50';
|
|
||||||
const bgColorClasses =
|
|
||||||
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
|
|
||||||
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class={`${baseClasses}`}>
|
|
||||||
<a
|
|
||||||
class={`rounded-xl duration-300 transition-all ${borderClasses} ${shadowClasses} ${bgColorClasses}`}
|
|
||||||
href={`/blog/${post.slug}/`}
|
|
||||||
data-astro-prefetch
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="relative w-full flex-shrink-0 overflow-hidden rounded-t-xl before:absolute before:inset-x-0 before:z-[1] before:size-full"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
class="h-auto w-full rounded-t-xl"
|
|
||||||
src={getDirectusImageURL(post.image)}
|
|
||||||
alt={post.image_alt}
|
|
||||||
draggable="false"
|
|
||||||
loading="eager"
|
|
||||||
format="webp"
|
|
||||||
width="800"
|
|
||||||
height="460"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl p-4 md:p-5">
|
|
||||||
<h3 class="text-xl font-bold text-neutral-600 dark:text-neutral-200">
|
|
||||||
{post.title}
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text relative z-10 mx-auto flex min-h-[44px] items-center font-medium text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-400"
|
|
||||||
>
|
|
||||||
<span class="relative inline-block overflow-hidden"> Read more </span>
|
|
||||||
<Icon
|
|
||||||
name="mdi:keyboard-arrow-right"
|
|
||||||
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
|
|
||||||
/>
|
|
||||||
<p class="ml-auto text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
{formatDate(post.published_date)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
|
||||||
import Image from '@components/ui/images/Image.astro';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
subTitle: string;
|
|
||||||
btnExists?: boolean;
|
|
||||||
btnTitle?: string;
|
|
||||||
btnURL?: string;
|
|
||||||
img: any;
|
|
||||||
imgAlt: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { title, subTitle, btnExists, btnTitle, btnURL, img, imgAlt } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<section
|
|
||||||
class="mx-auto max-w-[85rem] items-center gap-8 px-4 py-10 sm:px-6 sm:py-16 md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 xl:gap-16 2xl:max-w-full"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
class="h-full w-full rounded-xl object-cover sm:max-h-[320px] md:max-h-[360px]"
|
|
||||||
src={img}
|
|
||||||
alt={imgAlt}
|
|
||||||
draggable="false"
|
|
||||||
loading="lazy"
|
|
||||||
width="850"
|
|
||||||
height="420"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="mt-4 md:mt-0">
|
|
||||||
<h2
|
|
||||||
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-300"
|
|
||||||
>
|
|
||||||
{subTitle}
|
|
||||||
</p>
|
|
||||||
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
posts: Post[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { posts } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<section class="mx-auto mb-10 max-w-[85rem] px-4 py-8 sm:px-6 lg:px-8 2xl:max-w-full">
|
|
||||||
<div class="text-left">
|
|
||||||
<h2
|
|
||||||
id="recent-articles"
|
|
||||||
class="smooth-reveal-2 mb-10 text-5xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
Recent Posts
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col md:flex-row md:space-x-12 lg:space-x-16">
|
|
||||||
<div class="w-full">
|
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{posts.map((b) => <BlogCard post={b} />)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
---
|
|
||||||
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
|
||||||
import Image from '@components/ui/images/Image.astro';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
subTitle: string;
|
|
||||||
btnExists?: boolean;
|
|
||||||
btnTitle?: string;
|
|
||||||
btnURL?: string;
|
|
||||||
single?: boolean;
|
|
||||||
imgOne?: any;
|
|
||||||
imgOneAlt?: any;
|
|
||||||
imgTwo?: any;
|
|
||||||
imgTwoAlt?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
subTitle,
|
|
||||||
btnExists,
|
|
||||||
btnTitle,
|
|
||||||
btnURL,
|
|
||||||
single,
|
|
||||||
imgOne,
|
|
||||||
imgOneAlt,
|
|
||||||
imgTwo,
|
|
||||||
imgTwoAlt,
|
|
||||||
} = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<section
|
|
||||||
class="mx-auto max-w-[85rem] items-center gap-16 px-4 py-10 sm:px-6 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 2xl:max-w-full"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h2
|
|
||||||
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-400"
|
|
||||||
>
|
|
||||||
{subTitle}
|
|
||||||
</p>
|
|
||||||
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
single ? (
|
|
||||||
<div class="mt-8">
|
|
||||||
<Image
|
|
||||||
class="w-full rounded-lg"
|
|
||||||
src={imgOne}
|
|
||||||
alt={imgOneAlt}
|
|
||||||
format="webp"
|
|
||||||
loading="lazy"
|
|
||||||
width="850"
|
|
||||||
height="420"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div class="mt-8 grid grid-cols-2 gap-4">
|
|
||||||
<Image
|
|
||||||
class="w-full rounded-xl"
|
|
||||||
src={imgOne}
|
|
||||||
alt={imgOneAlt}
|
|
||||||
draggable="false"
|
|
||||||
format="webp"
|
|
||||||
loading="lazy"
|
|
||||||
width="400"
|
|
||||||
height="230"
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
class="mt-4 w-full rounded-xl lg:mt-10"
|
|
||||||
src={imgTwo}
|
|
||||||
alt={imgTwoAlt}
|
|
||||||
draggable="false"
|
|
||||||
format="webp"
|
|
||||||
loading="lazy"
|
|
||||||
width="400"
|
|
||||||
height="230"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
|
||||||
|
|
||||||
import { getDirectusImageURL } from '@lib/directusFunctions';
|
|
||||||
import BlogLeftSection from '@components/blog/BlogLeftSection.astro';
|
|
||||||
import BlogRightSection from '@components/blog/BlogRightSection.astro';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
posts: Post[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { posts } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<section class="smooth-reveal">
|
|
||||||
{
|
|
||||||
posts.map((b, index) =>
|
|
||||||
index % 2 === 0 ? (
|
|
||||||
<BlogLeftSection
|
|
||||||
title={b.title}
|
|
||||||
subTitle={b.description}
|
|
||||||
btnExists={true}
|
|
||||||
btnTitle="Read More"
|
|
||||||
btnURL={`/blog/${b.slug}`}
|
|
||||||
img={getDirectusImageURL(b.image)}
|
|
||||||
imgAlt={b.image_alt}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BlogRightSection
|
|
||||||
title={b.title}
|
|
||||||
subTitle={b.description}
|
|
||||||
btnExists={true}
|
|
||||||
btnTitle="Read More"
|
|
||||||
btnURL={`/blog/${b.slug}`}
|
|
||||||
single={!b.image_second}
|
|
||||||
imgOne={getDirectusImageURL(b.image)}
|
|
||||||
imgOneAlt={b.image_alt}
|
|
||||||
imgTwo={getDirectusImageURL(b?.image_second)}
|
|
||||||
imgTwoAlt={b?.image_second_alt}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
|
||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, url } = Astro.props;
|
const { url } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
---
|
---
|
||||||
import Icon from '@components/ui/icons/icon.astro';
|
import Icon from '@components/ui/icons/icon.astro';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
noArrow?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { noArrow } = Astro.props;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -14,7 +8,7 @@ const { noArrow } = Astro.props;
|
|||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
>
|
>
|
||||||
<div class="button-text-title flex relative items-center text-center">
|
<div class="button-text-title flex relative items-center text-center">
|
||||||
{noArrow ? null : <Icon name="arrowLeft" />}
|
<Icon name="arrowLeft" />
|
||||||
<span class="ml-2">
|
<span class="ml-2">
|
||||||
Go Back
|
Go Back
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ const socialPlatforms: SocialPlatform[] = [
|
|||||||
---
|
---
|
||||||
|
|
||||||
<div class="inline-flex items-center gap-x-2">
|
<div class="inline-flex items-center gap-x-2">
|
||||||
{
|
{socialPlatforms.map((platform) => (
|
||||||
socialPlatforms.map((platform) => (
|
|
||||||
<a
|
<a
|
||||||
class="button-base-hidden group inline-flex rounded-lg gap-x-2"
|
class="button-base-hidden group inline-flex rounded-lg gap-x-2"
|
||||||
href={platform.url}
|
href={platform.url}
|
||||||
@@ -43,9 +42,11 @@ const socialPlatforms: SocialPlatform[] = [
|
|||||||
title={`Share on ${platform.name}`}
|
title={`Share on ${platform.name}`}
|
||||||
>
|
>
|
||||||
<div class="button-text-title-hidden flex relative items-center text-center">
|
<div class="button-text-title-hidden flex relative items-center text-center">
|
||||||
<Icon name={platform.svg} class="h-5 w-5" />
|
<Icon
|
||||||
|
name={platform.svg}
|
||||||
|
class="h-5 w-5"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
58
src/components/cards/BlogCard.astro
Normal file
58
src/components/cards/BlogCard.astro
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="smooth-reveal-cards group flex flex-col">
|
||||||
|
<a
|
||||||
|
class="card-base border-none!"
|
||||||
|
href={`/blog/${post.slug}/`}
|
||||||
|
data-astro-prefetch
|
||||||
|
>
|
||||||
|
<div class="relative shrink-0 rounded-t-xl w-full overflow-hidden before:absolute before:inset-x-0 before:z-1 before:size-full">
|
||||||
|
<Image
|
||||||
|
class="rounded-t-xl h-auto w-full"
|
||||||
|
src={getDirectusImageURL(post.image)}
|
||||||
|
alt={post.image_alt}
|
||||||
|
draggable="false"
|
||||||
|
loading="eager"
|
||||||
|
format="webp"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl p-4 md:p-5">
|
||||||
|
<h3 class="card-text-title text-xl">
|
||||||
|
{post.title}
|
||||||
|
</h3>
|
||||||
|
<div class="ml-6 flex">
|
||||||
|
<div class="relative inline-block w-full">
|
||||||
|
<div class="card-text-title card-hover-text-title flex relative items-center mx-auto min-h-11 sm:mx-0 sm:mt-4">
|
||||||
|
<span class="relative inline-block overflow-hidden ml-2">
|
||||||
|
Read more
|
||||||
|
</span>
|
||||||
|
<Icon
|
||||||
|
name="mdi:keyboard-arrow-right"
|
||||||
|
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
|
||||||
|
/>
|
||||||
|
<p class="card-text-description text-sm ml-auto">
|
||||||
|
{new Date(post.published_date).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
@@ -8,37 +8,30 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { slug, title, description, count, publishDate } = Astro.props;
|
const { slug, title, description, count, publishDate } = Astro.props;
|
||||||
|
|
||||||
const baseClasses =
|
|
||||||
'group group-hover rounded-xl flex h-full min-h-[220px] cursor-pointer flex-col overflow-hidden';
|
|
||||||
const bgColorClasses =
|
|
||||||
'bg-neutral-100/60 dark:bg-neutral-800/60 hover:bg-neutral-100 dark:hover:bg-neutral-800/90 ';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<a class={`rounded-xl`} href={`/categories/${slug}/`} data-astro-prefetch="false">
|
<div class="smooth-reveal-cards group h-full">
|
||||||
<div class={`${baseClasses}`}>
|
<a
|
||||||
<div
|
class="card-base flex flex-col h-full min-h-55"
|
||||||
class={`relative min-h-0 flex-grow overflow-hidden transition-all duration-300 ${bgColorClasses}`}
|
href={`/categories/${slug}/`}
|
||||||
|
data-astro-prefetch
|
||||||
>
|
>
|
||||||
|
<div class="relative grow overflow-hidden">
|
||||||
<div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5">
|
<div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5">
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<h2
|
<h3 class="card-text-title-major card-hover-text-title whitespace-nowrap mb-4">
|
||||||
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text mb-4 text-4xl font-extrabold tracking-tight text-balance whitespace-nowrap text-neutral-800 duration-300 dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h3>
|
||||||
<p class="mb-4 font-light text-neutral-600 sm:text-lg dark:text-neutral-400">
|
<p class="card-text-description mb-4">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="card-text-description flex items-center justify-between text-xs mt-auto pt-1 md:pt-2">
|
||||||
class="mt-auto flex items-center justify-between pt-1 text-xs text-neutral-600 md:pt-2 dark:text-neutral-300"
|
|
||||||
>
|
|
||||||
<span class="inline-flex items-center">
|
<span class="inline-flex items-center">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="12"
|
width="16"
|
||||||
height="12"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -51,8 +44,8 @@ const bgColorClasses =
|
|||||||
<span class="inline-flex items-center">
|
<span class="inline-flex items-center">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="12"
|
width="16"
|
||||||
height="12"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -66,5 +59,5 @@ const bgColorClasses =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
import Logo from '@components/ui/logos/Logo.astro';
|
import Logo from '@components/ui/logos/Logo.astro';
|
||||||
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
topic: string;
|
topic: string;
|
||||||
@@ -12,7 +14,8 @@ interface Props {
|
|||||||
logoIcon?: string;
|
logoIcon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { topic, area, date, url, logoUrlLight, logoUrlDark, logoIcon } = Astro.props;
|
const { topic, area, date, url, logoUrlLight, logoIcon } = Astro.props;
|
||||||
|
const logoUrlDark = Astro.props.logoUrlDark || logoUrlLight;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="smooth-reveal group flex flex-col">
|
<div class="smooth-reveal group flex flex-col">
|
||||||
@@ -25,8 +28,8 @@ const { topic, area, date, url, logoUrlLight, logoUrlDark, logoIcon } = Astro.pr
|
|||||||
{logoUrlLight ? (
|
{logoUrlLight ? (
|
||||||
<div class="card-hover-icon-scale mr-5">
|
<div class="card-hover-icon-scale mr-5">
|
||||||
<Logo
|
<Logo
|
||||||
srcLight={logoUrlLight}
|
srcLight={getDirectusImageURL(logoUrlLight)}
|
||||||
srcDark={logoUrlDark}
|
srcDark={getDirectusImageURL(logoUrlDark!)}
|
||||||
alt={`Logo of ${topic}`}
|
alt={`Logo of ${topic}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { title, description, url, icon } = Astro.props;
|
const { title, description, url, icon } = Astro.props;
|
||||||
|
|
||||||
const sizeClasses = 'h-30 w-100 md:w-[300px]';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="smooth-reveal-2 group flex flex-col">
|
<div class="smooth-reveal-2 group flex flex-col">
|
||||||
<a
|
<a
|
||||||
class={`card-base flex items-center ${sizeClasses}`}
|
class="card-base flex items-center h-30 w-100 md:w-75"
|
||||||
href={url}
|
href={url}
|
||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
>
|
>
|
||||||
@@ -29,9 +27,9 @@ const sizeClasses = 'h-30 w-100 md:w-[300px]';
|
|||||||
<span class="card-text-title card-hover-text-title block text-lg">
|
<span class="card-text-title card-hover-text-title block text-lg">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
<span class="card-text-description block mt-1">
|
<p class="card-text-description block mt-1">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-titl
|
|||||||
<span class="card-text-title block text-lg">
|
<span class="card-text-title block text-lg">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
<span class="card-text-description block mt-1">
|
<p class="card-text-description block mt-1">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{highlights && (
|
{highlights && (
|
||||||
@@ -55,7 +55,7 @@ const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-titl
|
|||||||
)}
|
)}
|
||||||
<div class="ml-6 flex">
|
<div class="ml-6 flex">
|
||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
<div class={`card-text-title ${visitClass} flex relative items-center mx-auto min-h-11 font-semibold text-md sm:mx-0 sm:mt-4`}>
|
<div class={`card-text-title ${visitClass} flex relative items-center font-semibold text-md min-h-11 mx-auto sm:mx-0 sm:mt-4`}>
|
||||||
{visitSource && <Icon name="pajamas:gitea" />}
|
{visitSource && <Icon name="pajamas:gitea" />}
|
||||||
<span class="relative inline-block overflow-hidden ml-2">
|
<span class="relative inline-block overflow-hidden ml-2">
|
||||||
{visitText}
|
{visitText}
|
||||||
|
|||||||
55
src/components/cards/LargeBlogLeftCard.astro
Normal file
55
src/components/cards/LargeBlogLeftCard.astro
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
subTitle: string;
|
||||||
|
url: string;
|
||||||
|
img: string;
|
||||||
|
imgAlt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, subTitle, url, img, imgAlt } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="smooth-reveal group">
|
||||||
|
<a
|
||||||
|
class="card-base-hidden md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 items-center gap-8 xl:gap-16 max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 sm:py-16 lg:py-14 mx-auto"
|
||||||
|
href={url}
|
||||||
|
data-astro-prefetch
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
class="rounded-xl w-full h-full sm:max-h-80 md:max-h-90 object-cover"
|
||||||
|
src={getDirectusImageURL(img)}
|
||||||
|
alt={imgAlt}
|
||||||
|
draggable="false"
|
||||||
|
loading="lazy"
|
||||||
|
width="850"
|
||||||
|
height="420"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="card-text-header mb-4">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose mb-4">
|
||||||
|
{subTitle}
|
||||||
|
</p>
|
||||||
|
<div class="button-base button-bg-teal inline-flex rounded-lg gap-x-2">
|
||||||
|
<div class="button-text-title flex relative items-center text-center">
|
||||||
|
<span class="mr-2">
|
||||||
|
Read More
|
||||||
|
</span>
|
||||||
|
<Icon
|
||||||
|
name="mdi:keyboard-arrow-right"
|
||||||
|
class="button-hover-arrow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
83
src/components/cards/LargeBlogRightCard.astro
Normal file
83
src/components/cards/LargeBlogRightCard.astro
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
|
||||||
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
subTitle: string;
|
||||||
|
url: string;
|
||||||
|
single?: boolean;
|
||||||
|
imgOne: any;
|
||||||
|
imgOneAlt: any;
|
||||||
|
imgTwo?: any;
|
||||||
|
imgTwoAlt?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, subTitle, url, single, imgOne, imgOneAlt, imgTwo, imgTwoAlt } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="smooth-reveal group">
|
||||||
|
<a
|
||||||
|
class="card-base-hidden items-center lg:grid lg:grid-cols-2 gap-16 max-w-340 2xl:max-w-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto"
|
||||||
|
href={url}
|
||||||
|
data-astro-prefetch
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2 class="card-text-header mb-4">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose mb-4">
|
||||||
|
{subTitle}
|
||||||
|
</p>
|
||||||
|
<div class="button-base button-bg-teal inline-flex rounded-lg gap-x-2">
|
||||||
|
<div class="button-text-title flex relative items-center text-center">
|
||||||
|
<span class="mr-2">
|
||||||
|
Read More
|
||||||
|
</span>
|
||||||
|
<Icon
|
||||||
|
name="mdi:keyboard-arrow-right"
|
||||||
|
class="button-hover-arrow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{single ? (
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
class="rounded-xl w-full"
|
||||||
|
src={getDirectusImageURL(imgOne)}
|
||||||
|
alt={imgOneAlt}
|
||||||
|
format="webp"
|
||||||
|
loading="lazy"
|
||||||
|
width="850"
|
||||||
|
height="420"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<Image
|
||||||
|
class="rounded-xl w-full"
|
||||||
|
src={getDirectusImageURL(imgOne)}
|
||||||
|
alt={imgOneAlt}
|
||||||
|
draggable="false"
|
||||||
|
format="webp"
|
||||||
|
loading="lazy"
|
||||||
|
width="400"
|
||||||
|
height="230"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
class="rounded-xl w-full mt-4 lg:mt-10"
|
||||||
|
src={getDirectusImageURL(imgTwo)}
|
||||||
|
alt={imgTwoAlt}
|
||||||
|
draggable="false"
|
||||||
|
format="webp"
|
||||||
|
loading="lazy"
|
||||||
|
width="400"
|
||||||
|
height="230"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
@@ -7,12 +7,10 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { dayName, label, icon, temp } = Astro.props;
|
const { dayName, label, icon, temp } = Astro.props;
|
||||||
|
|
||||||
const sizeClasses = 'w-32 md:w-40';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="smooth-reveal-2 group flex flex-col">
|
<div class="smooth-reveal-2 group flex flex-col">
|
||||||
<div class={`card-base ${sizeClasses}`}>
|
<div class="card-base w-32 md:w-40">
|
||||||
<div class="p-5 text-center">
|
<div class="p-5 text-center">
|
||||||
<span class="card-text-description block font-bold text-xs uppercase tracking-widest">
|
<span class="card-text-description block font-bold text-xs uppercase tracking-widest">
|
||||||
{dayName}
|
{dayName}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { readItems } from '@directus/sdk';
|
|||||||
|
|
||||||
import type { Application } from '@lib/directusTypes';
|
import type { Application } from '@lib/directusTypes';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import HighlightsCard from '@components/cards/HighlightsCard.astro';
|
import HighlightsCard from '@components/cards/HighlightsCard.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
const applications = ((await directus.request(
|
const applications = ((await directus.request(
|
||||||
readItems('site_applications' as any, {
|
readItems('site_applications' as any, {
|
||||||
|
|||||||
93
src/components/sections/CategorySection.astro
Normal file
93
src/components/sections/CategorySection.astro
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
import { readItems } from '@directus/sdk';
|
||||||
|
|
||||||
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import CategoryCard from '@components/cards/CategoryCard.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
import { timeago } from '@support/time';
|
||||||
|
|
||||||
|
const posts = await directus.request(
|
||||||
|
readItems('posts', {
|
||||||
|
filter: { published: { _eq: true } },
|
||||||
|
fields: ['*'],
|
||||||
|
sort: ['-published_date'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const layoutPattern = [
|
||||||
|
{ col: 2, row: 2 },
|
||||||
|
{ col: 2, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
{ col: 1, row: 2 },
|
||||||
|
{ col: 2, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
{ col: 1, row: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const postMap: Map<string, Post[]> = posts
|
||||||
|
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
|
||||||
|
.reduce((acc, obj) => {
|
||||||
|
let posts = acc.get(obj.category);
|
||||||
|
if (!posts) {
|
||||||
|
posts = [];
|
||||||
|
}
|
||||||
|
posts.push(obj);
|
||||||
|
|
||||||
|
acc.set(obj.category, posts);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, new Map<string, Post[]>());
|
||||||
|
|
||||||
|
const categories = (await getCollection('categories'))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aCount = postMap.get(a.slug)?.length ?? 0;
|
||||||
|
const bCount = postMap.get(b.slug)?.length ?? 0;
|
||||||
|
return bCount - aCount;
|
||||||
|
})
|
||||||
|
.map((c, index) => {
|
||||||
|
const posts = postMap.get(c.slug);
|
||||||
|
const pattern = layoutPattern[index % layoutPattern.length];
|
||||||
|
const smColSpan = Math.min(pattern.col, 2);
|
||||||
|
const mdColSpan = Math.min(pattern.col, 4);
|
||||||
|
const rowSpan = pattern.row;
|
||||||
|
const rowSpanClass = rowSpan > 1 ? `row-span-${rowSpan}` : 'row-span-1';
|
||||||
|
const gridItemClass = `col-span-${smColSpan} md:col-span-${mdColSpan} ${rowSpanClass}`;
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
posts,
|
||||||
|
gridItemClass,
|
||||||
|
layoutPattern: {
|
||||||
|
smCol: smColSpan,
|
||||||
|
mdCol: mdColSpan,
|
||||||
|
row: rowSpan,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="mx-auto px-4 py-10 sm:px-6 lg:px-8 lg:py-14 lg:pt-10 2xl:max-w-full">
|
||||||
|
<div class="grid grid-flow-row-dense grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{categories.map((category) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={category.gridItemClass}
|
||||||
|
style={category.layoutPattern.row > 1 ? 'grid-row: span 2 / span 2;' : ''}
|
||||||
|
>
|
||||||
|
<CategoryCard
|
||||||
|
slug={category.slug}
|
||||||
|
title={category.data.title}
|
||||||
|
description={category.data.description}
|
||||||
|
count={postMap.get(category.slug)?.length ?? 0}
|
||||||
|
publishDate={timeago(postMap.get(category.slug)?.[0]?.published_date)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -3,9 +3,8 @@ import { readItems } from '@directus/sdk';
|
|||||||
|
|
||||||
import type { Education, Certificate} from '@lib/directusTypes';
|
import type { Education, Certificate} from '@lib/directusTypes';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import { getDirectusImageURL } from '@lib/directusFunctions';
|
|
||||||
import EducationCard from '@components/cards/EducationCard.astro';
|
import EducationCard from '@components/cards/EducationCard.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
const educations = ((await directus.request(
|
const educations = ((await directus.request(
|
||||||
readItems('site_education' as any, {
|
readItems('site_education' as any, {
|
||||||
@@ -37,8 +36,8 @@ const certificates = ((await directus.request(
|
|||||||
area={education.area}
|
area={education.area}
|
||||||
date={education.graduationDate}
|
date={education.graduationDate}
|
||||||
url={education.url}
|
url={education.url}
|
||||||
logoUrlLight={getDirectusImageURL(education.logo)}
|
logoUrlLight={education.logo}
|
||||||
logoUrlDark={getDirectusImageURL(education.logoDark)}
|
logoUrlDark={education.logoDark}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const experiences = ((await directus.request(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(experience.responsibilities || experience.achievements) && (
|
{(experience.responsibilities || experience.achievements) && (
|
||||||
<div class="relative flex flex-col gap-4 max-sm:h-auto! md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-gradient-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
|
<div class="relative flex flex-col gap-4 max-sm:h-auto! md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-linear-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
|
||||||
{experience.responsibilities && (
|
{experience.responsibilities && (
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h4 class="text-header font-semibold">
|
<h4 class="text-header font-semibold">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { readSingleton } from '@directus/sdk';
|
import { readSingleton } from '@directus/sdk';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import FeaturesCard from '@components/cards/FeaturesCard.astro';
|
import FeaturesCard from '@components/cards/FeaturesCard.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
---
|
---
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
import { readItems } from '@directus/sdk';
|
|
||||||
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
|
||||||
|
|
||||||
const posts = await directus.request(
|
|
||||||
readItems('posts', {
|
|
||||||
filter: { published: { _eq: true } },
|
|
||||||
fields: ['*'],
|
|
||||||
sort: ['-published_date'],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const recentPosts = posts
|
|
||||||
.sort((a: Post, b: Post) => (new Date(b.published_date).getTime()) - (new Date(a.published_date).getTime()))
|
|
||||||
.slice(0, 3);
|
|
||||||
---
|
|
||||||
|
|
||||||
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
|
|
||||||
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
|
|
||||||
<h1 class="smooth-reveal card-text-header block">
|
|
||||||
Latest Posts
|
|
||||||
</h1>
|
|
||||||
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
|
|
||||||
<span class="card-text-header-description">
|
|
||||||
Checkout my most recent thoughts here
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{recentPosts.map((b) => <BlogCard post={b} />)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -3,8 +3,8 @@ import { readItems } from '@directus/sdk';
|
|||||||
|
|
||||||
import type { Project } from '@lib/directusTypes';
|
import type { Project } from '@lib/directusTypes';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import HighlightsCard from '@components/cards/HighlightsCard.astro';
|
import HighlightsCard from '@components/cards/HighlightsCard.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
const projects = ((await directus.request(
|
const projects = ((await directus.request(
|
||||||
readItems('site_projects' as any, {
|
readItems('site_projects' as any, {
|
||||||
|
|||||||
29
src/components/sections/RecentPostsSection.astro
Normal file
29
src/components/sections/RecentPostsSection.astro
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import BlogCard from '@components/cards/BlogCard.astro';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
posts: Post[];
|
||||||
|
title: string;
|
||||||
|
subTitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { posts, title, subTitle } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
|
||||||
|
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
|
||||||
|
<h1 class="smooth-reveal card-text-header block">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
|
||||||
|
<span class="card-text-header-description">
|
||||||
|
{subTitle}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{posts.map((b) => <BlogCard post={b} />)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
35
src/components/sections/SelectedPostsSection.astro
Normal file
35
src/components/sections/SelectedPostsSection.astro
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import LargeBlogLeftCard from '@components/cards/LargeBlogLeftCard.astro';
|
||||||
|
import LargeBlogRightCard from '@components/cards/LargeBlogRightCard.astro';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
posts: Post[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { posts } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="smooth-reveal">
|
||||||
|
{posts.map((post, index) => index % 2 === 0 ? (
|
||||||
|
<LargeBlogLeftCard
|
||||||
|
title={post.title}
|
||||||
|
subTitle={post.description}
|
||||||
|
url={`/blog/${post.slug}`}
|
||||||
|
img={post.image}
|
||||||
|
imgAlt={post.image_alt}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LargeBlogRightCard
|
||||||
|
title={post.title}
|
||||||
|
subTitle={post.description}
|
||||||
|
url={`/blog/${post.slug}`}
|
||||||
|
single={!post.image_second}
|
||||||
|
imgOne={post.image}
|
||||||
|
imgOneAlt={post.image_alt}
|
||||||
|
imgTwo={post?.image_second}
|
||||||
|
imgTwoAlt={post?.image_second_alt}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
@@ -3,6 +3,7 @@ import WeatherCard from '@components/cards/WeatherCard.astro';
|
|||||||
import { getFiveDayForecast } from '@support/weather';
|
import { getFiveDayForecast } from '@support/weather';
|
||||||
|
|
||||||
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
|
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
|
||||||
|
|
||||||
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone);
|
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const global = await directus.request(readSingleton('site_global'));
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<section class="mt-20 grid place-content-center">
|
<section class="mt-20 grid place-content-center">
|
||||||
<div class="mx-auto max-w-7xl px-4 py-8 lg:px-6 lg:py-16">
|
<div class="mx-auto max-w-7xl px-4 py-8 lg:px-6 lg:py-16">
|
||||||
<div class="mx-auto max-w-screen-sm text-center">
|
<div class="mx-auto max-w-screen-sm text-center">
|
||||||
@@ -67,6 +68,7 @@ const global = await directus.request(readSingleton('site_global'));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
---
|
---
|
||||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||||
import getReadingTime from 'reading-time';
|
import getReadingTime from 'reading-time';
|
||||||
import { readItems, readSingleton } from '@directus/sdk';
|
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import markedShiki from 'marked-shiki';
|
import markedShiki from 'marked-shiki';
|
||||||
import { createHighlighter } from 'shiki';
|
import { createHighlighter } from 'shiki';
|
||||||
import { getDirectusImageURL } from '@lib/directusFunctions';
|
import { readItems, readSingleton } from '@directus/sdk';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
import SocialShareButton from '@components/buttons/SocialShareButton.astro';
|
import SocialShareButton from '@components/buttons/SocialShareButton.astro';
|
||||||
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
import { formatDateTime } from '@support/time';
|
import { formatDateTime } from '@support/time';
|
||||||
|
|
||||||
|
const post = Astro.props;
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await directus.request(readItems('posts'));
|
const posts = await directus.request(readItems('posts'));
|
||||||
return posts.map((post) => ({
|
return posts.map((post) => ({
|
||||||
@@ -20,18 +22,19 @@ export async function getStaticPaths() {
|
|||||||
props: post,
|
props: post,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const post = Astro.props;
|
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
const category: CollectionEntry<'categories'> = (await getCollection('categories'))
|
const category: CollectionEntry<'categories'> = (await getCollection('categories'))
|
||||||
.filter((c) => c.slug === post.category)
|
.filter((c) => c.slug === post.category)
|
||||||
.pop() as CollectionEntry<'categories'>;
|
.pop() as CollectionEntry<'categories'>;
|
||||||
|
|
||||||
const readingTime = getReadingTime(post.content);
|
const readingTime = getReadingTime(post.content);
|
||||||
|
|
||||||
const highlighter = await createHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ['github-light', 'github-dark', 'monokai'],
|
themes: ['github-light', 'github-dark', 'monokai'],
|
||||||
langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'],
|
langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'],
|
||||||
});
|
});
|
||||||
|
|
||||||
marked.use(markedShiki({
|
marked.use(markedShiki({
|
||||||
highlight(code, lang) {
|
highlight(code, lang) {
|
||||||
return highlighter.codeToHtml(code, {
|
return highlighter.codeToHtml(code, {
|
||||||
@@ -44,6 +47,7 @@ marked.use(markedShiki({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const content = marked.parse(post.content);
|
const content = marked.parse(post.content);
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,6 +83,7 @@ const content = marked.parse(post.content);
|
|||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<section class="mx-auto max-w-6xl px-4 pt-8 pb-12 sm:px-6 lg:px-8 lg:pt-12">
|
<section class="mx-auto max-w-6xl px-4 pt-8 pb-12 sm:px-6 lg:px-8 lg:pt-12">
|
||||||
<div class="smooth-reveal relative w-full">
|
<div class="smooth-reveal relative w-full">
|
||||||
<div class="mt-4 rounded-2xl shadow-none sm:mt-0 sm:shadow-sm">
|
<div class="mt-4 rounded-2xl shadow-none sm:mt-0 sm:shadow-sm">
|
||||||
@@ -171,6 +176,7 @@ const content = marked.parse(post.content);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style is:inline>
|
<style is:inline>
|
||||||
code[data-theme*=' '],
|
code[data-theme*=' '],
|
||||||
code[data-theme*=' '] span {
|
code[data-theme*=' '] span {
|
||||||
@@ -184,6 +190,7 @@ const content = marked.parse(post.content);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { readItems, readSingleton } from '@directus/sdk';
|
|||||||
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
|
||||||
import BlogSelectedArticles from '@components/blog/BlogSelectedArticles.astro';
|
|
||||||
import BlogRecentArticles from '@components/blog/BlogRecentArticles.astro';
|
|
||||||
import HeroSection from '@components/sections/HeroSection.astro';
|
import HeroSection from '@components/sections/HeroSection.astro';
|
||||||
|
import SelectedPostsSection from '@components/sections/SelectedPostsSection.astro';
|
||||||
|
import RecentPostsSection from '@components/sections/RecentPostsSection.astro';
|
||||||
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
import blogImg from '@images/autumn_tree.png';
|
import blogImg from '@images/autumn_tree.png';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
@@ -18,10 +19,11 @@ const posts = await directus.request(
|
|||||||
sort: ['-published_date'],
|
sort: ['-published_date'],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const selectedPosts: Post[] = posts.filter((p) => p.selected).slice(0, 4);
|
|
||||||
|
const selectedPosts: Post[] = posts.filter((p) => p.selected).slice(0, 3);
|
||||||
const recentPosts: Post[] = posts.filter(
|
const recentPosts: Post[] = posts.filter(
|
||||||
(p) => !selectedPosts.some((selected) => selected.slug === p.slug)
|
(p) => !selectedPosts.some((selected) => selected.slug === p.slug)
|
||||||
).slice(0, 6);
|
).slice(0, 9);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
@@ -43,10 +45,21 @@ const recentPosts: Post[] = posts.filter(
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HeroSection title="Blog" subTitle={global.about_blog} src={blogImg} alt={global.blog_image_alt} />
|
|
||||||
|
|
||||||
<BlogSelectedArticles posts={selectedPosts} />
|
<HeroSection
|
||||||
<BlogRecentArticles posts={recentPosts} />
|
title="Blog"
|
||||||
|
subTitle={global.about_blog}
|
||||||
|
src={blogImg}
|
||||||
|
alt={global.blog_image_alt}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectedPostsSection posts={selectedPosts} />
|
||||||
|
|
||||||
|
<RecentPostsSection
|
||||||
|
posts={recentPosts}
|
||||||
|
title="Recent Posts"
|
||||||
|
/>
|
||||||
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import { readItems, readSingleton } from '@directus/sdk';
|
import { readItems, readSingleton } from '@directus/sdk';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
import type { Post } from '@lib/directusTypes';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
import BlogCard from '@components/cards/BlogCard.astro';
|
||||||
import HeaderSection from '@components/sections/HeaderSection.astro';
|
import HeaderSection from '@components/sections/HeaderSection.astro';
|
||||||
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const categories = await getCollection('categories');
|
const categories = await getCollection('categories');
|
||||||
@@ -26,6 +27,7 @@ const posts = await directus.request(
|
|||||||
sort: ['-published_date'],
|
sort: ['-published_date'],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const categoriesPosts = posts
|
const categoriesPosts = posts
|
||||||
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
|
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
|
||||||
.filter((b) => {
|
.filter((b) => {
|
||||||
@@ -51,6 +53,7 @@ const categoriesPosts = posts
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<HeaderSection
|
<HeaderSection
|
||||||
title={category.data.title}
|
title={category.data.title}
|
||||||
subTitle={category.data.description}
|
subTitle={category.data.description}
|
||||||
@@ -59,9 +62,12 @@ const categoriesPosts = posts
|
|||||||
btnURL="/categories"
|
btnURL="/categories"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="mx-auto mt-10 mb-10 max-w-[85rem] px-4 py-8 sm:px-6 lg:px-8 2xl:max-w-full">
|
<section class="max-w-340 2xl:max-w-full mx-auto mt-10 mb-10 px-4 py-8 sm:px-6 lg:px-8">
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{categoriesPosts.map((b) => <BlogCard post={b} />)}
|
{categoriesPosts.map((b) =>
|
||||||
|
<BlogCard post={b} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -1,78 +1,14 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { readSingleton } from '@directus/sdk';
|
||||||
import { readItems, readSingleton } from '@directus/sdk';
|
|
||||||
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import BlogCategoryCard from '@components/blog/BlogCategoryCard.astro';
|
|
||||||
import HeroSection from '@components/sections/HeroSection.astro';
|
import HeroSection from '@components/sections/HeroSection.astro';
|
||||||
import { timeago } from '@support/time';
|
import CategorySection from '@components/sections/CategorySection.astro';
|
||||||
|
|
||||||
import categoryImg from '@images/autumn_bench.png';
|
import categoryImg from '@images/autumn_bench.png';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
const posts = await directus.request(
|
|
||||||
readItems('posts', {
|
|
||||||
filter: { published: { _eq: true } },
|
|
||||||
fields: ['*'],
|
|
||||||
sort: ['-published_date'],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const postMap: Map<string, Post[]> = posts
|
|
||||||
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
|
|
||||||
.reduce((acc, obj) => {
|
|
||||||
let posts = acc.get(obj.category);
|
|
||||||
if (!posts) {
|
|
||||||
posts = [];
|
|
||||||
}
|
|
||||||
posts.push(obj);
|
|
||||||
|
|
||||||
acc.set(obj.category, posts);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, new Map<string, Post[]>());
|
|
||||||
|
|
||||||
const layoutPattern = [
|
|
||||||
{ col: 2, row: 2 },
|
|
||||||
{ col: 2, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
{ col: 1, row: 2 },
|
|
||||||
{ col: 2, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
{ col: 1, row: 1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const categories = (await getCollection('categories'))
|
|
||||||
.sort((a, b) => {
|
|
||||||
const aCount = postMap.get(a.slug)?.length ?? 0;
|
|
||||||
const bCount = postMap.get(b.slug)?.length ?? 0;
|
|
||||||
return bCount - aCount;
|
|
||||||
})
|
|
||||||
.map((c, index) => {
|
|
||||||
const posts = postMap.get(c.slug);
|
|
||||||
const pattern = layoutPattern[index % layoutPattern.length];
|
|
||||||
const smColSpan = Math.min(pattern.col, 2);
|
|
||||||
const mdColSpan = Math.min(pattern.col, 4);
|
|
||||||
const rowSpan = pattern.row;
|
|
||||||
const rowSpanClass = rowSpan > 1 ? `row-span-${rowSpan}` : 'row-span-1';
|
|
||||||
const gridItemClass = `col-span-${smColSpan} md:col-span-${mdColSpan} ${rowSpanClass} smooth-reveal-cards rounded-xl transition-all duration-300 shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg border border-stone-200/50 dark:border-stone-700/50`;
|
|
||||||
return {
|
|
||||||
...c,
|
|
||||||
posts,
|
|
||||||
gridItemClass,
|
|
||||||
layoutPattern: {
|
|
||||||
smCol: smColSpan,
|
|
||||||
mdCol: mdColSpan,
|
|
||||||
row: rowSpan,
|
|
||||||
index,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
@@ -94,6 +30,7 @@ const categories = (await getCollection('categories'))
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
title="Categories"
|
title="Categories"
|
||||||
subTitle={global.about_categories}
|
subTitle={global.about_categories}
|
||||||
@@ -101,28 +38,8 @@ const categories = (await getCollection('categories'))
|
|||||||
alt={global.categories_image_alt}
|
alt={global.categories_image_alt}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="mx-auto px-4 py-10 sm:px-6 lg:px-8 lg:py-14 lg:pt-10 2xl:max-w-full">
|
<CategorySection />
|
||||||
<div class="grid grid-flow-row-dense grid-cols-2 gap-4 md:grid-cols-4">
|
|
||||||
{
|
|
||||||
categories.map((category) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={category.gridItemClass}
|
|
||||||
style={category.layoutPattern.row > 1 ? 'grid-row: span 2 / span 2;' : ''}
|
|
||||||
>
|
|
||||||
<BlogCategoryCard
|
|
||||||
slug={category.slug}
|
|
||||||
title={category.data.title}
|
|
||||||
description={category.data.description}
|
|
||||||
count={postMap.get(category.slug)?.length ?? 0}
|
|
||||||
publishDate={timeago(postMap.get(category.slug)?.[0]?.published_date)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
---
|
---
|
||||||
import { readSingleton } from '@directus/sdk';
|
import { readSingleton, readItems } from '@directus/sdk';
|
||||||
|
|
||||||
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import HeroSection from '@components/sections/HeroSection.astro';
|
import HeroSection from '@components/sections/HeroSection.astro';
|
||||||
import FeaturesSection from '@components/sections/FeaturesSection.astro';
|
import FeatureSection from '@components/sections/FeatureSection.astro';
|
||||||
import WeatherSection from '@components/sections/WeatherSection.astro';
|
import WeatherSection from '@components/sections/WeatherSection.astro';
|
||||||
import LatestPostsSection from '@components/sections/LatestPostsSection.astro';
|
import RecentPostsSection from '@components/sections/RecentPostsSection.astro';
|
||||||
import GiteaSection from '@components/sections/GiteaSection.astro';
|
import GiteaSection from '@components/sections/GiteaSection.astro';
|
||||||
|
|
||||||
import homeImg from '@images/autumn_mountain.png';
|
import homeImg from '@images/autumn_mountain.png';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
const weather = await directus.request(readSingleton('site_weather'));
|
const weather = await directus.request(readSingleton('site_weather'));
|
||||||
|
const posts = await directus.request(
|
||||||
|
readItems('posts', {
|
||||||
|
filter: { published: { _eq: true } },
|
||||||
|
fields: ['*'],
|
||||||
|
sort: ['-published_date'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const recentPosts = posts
|
||||||
|
.sort((a: Post, b: Post) => (new Date(b.published_date).getTime()) - (new Date(a.published_date).getTime()))
|
||||||
|
.slice(0, 3);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
@@ -34,6 +47,7 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
title={`Hello, I'm <span class="text-steel dark:text-steel">Alex Lebens</span>`}
|
title={`Hello, I'm <span class="text-steel dark:text-steel">Alex Lebens</span>`}
|
||||||
subTitle={global.about_description}
|
subTitle={global.about_description}
|
||||||
@@ -43,7 +57,7 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
alt={global.home_image_alt}
|
alt={global.home_image_alt}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FeaturesSection />
|
<FeatureSection />
|
||||||
|
|
||||||
<WeatherSection
|
<WeatherSection
|
||||||
server:defer
|
server:defer
|
||||||
@@ -51,16 +65,20 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
longitude={weather.longitude}
|
longitude={weather.longitude}
|
||||||
cityName={weather.location}
|
cityName={weather.location}
|
||||||
timezone={weather.timezone}
|
timezone={weather.timezone}
|
||||||
>
|
/>
|
||||||
</WeatherSection>
|
|
||||||
|
|
||||||
<LatestPostsSection />
|
<RecentPostsSection
|
||||||
|
posts={recentPosts}
|
||||||
|
title="Latest Posts"
|
||||||
|
subTitle="Checkout my most recent thoughts here"
|
||||||
|
/>
|
||||||
|
|
||||||
<GiteaSection
|
<GiteaSection
|
||||||
title="Follow me on Gitea"
|
title="Follow me on Gitea"
|
||||||
subTitle="I love open source and have my code availabile on my Gitea server."
|
subTitle="I love open source and have my code availabile on my Gitea server."
|
||||||
url="https://gitea.alexlebens.dev"
|
url="https://gitea.alexlebens.dev"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
@utility button-bg-teal {
|
@utility button-bg-teal {
|
||||||
@apply transition-all duration-300
|
@apply transition-all duration-300
|
||||||
bg-bermuda hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda
|
bg-bermuda hover:bg-turquoise group-hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda dark:group-hover:bg-bermuda
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility button-bg-neutral {
|
@utility button-bg-neutral {
|
||||||
@@ -53,12 +53,20 @@
|
|||||||
|
|
||||||
/* Card classes */
|
/* Card classes */
|
||||||
@utility card-base {
|
@utility card-base {
|
||||||
@apply rounded-xl
|
@apply transition-all duration-300
|
||||||
|
rounded-xl
|
||||||
border border-neutral-100 dark:border-stone-500/20
|
border border-neutral-100 dark:border-stone-500/20
|
||||||
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
|
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
|
||||||
shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg
|
shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility card-base-hidden {
|
||||||
|
@apply transition-all duration-300
|
||||||
|
rounded-xl
|
||||||
|
border border-transparent
|
||||||
|
hover:bg-neutral-400/20 dark:hover:bg-neutral-800/40
|
||||||
|
}
|
||||||
|
|
||||||
@utility card-hover-icon-color {
|
@utility card-hover-icon-color {
|
||||||
@apply transition-all duration-300
|
@apply transition-all duration-300
|
||||||
text-primary
|
text-primary
|
||||||
@@ -79,9 +87,8 @@
|
|||||||
|
|
||||||
@utility card-text-header-minor {
|
@utility card-text-header-minor {
|
||||||
@apply text-header
|
@apply text-header
|
||||||
md:text-3xl
|
text-2xl md:text-3xl
|
||||||
text-2xl
|
font-semibold leading-tight tracking-tight text-balance
|
||||||
font-semibold
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility card-text-header-description {
|
@utility card-text-header-description {
|
||||||
@@ -95,6 +102,12 @@
|
|||||||
font-bold
|
font-bold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility card-text-title-major {
|
||||||
|
@apply text-header
|
||||||
|
text-4xl md:text-3xl
|
||||||
|
font-bold leading-tight tracking-tight text-balance
|
||||||
|
}
|
||||||
|
|
||||||
@utility card-hover-text-title {
|
@utility card-hover-text-title {
|
||||||
@apply transition-all duration-300
|
@apply transition-all duration-300
|
||||||
group-hover:text-main
|
group-hover:text-main
|
||||||
|
|||||||
Reference in New Issue
Block a user