This commit is contained in:
MassiveBox 2025-03-17 17:43:53 +01:00
commit d6c414f887
Signed by: massivebox
GPG key ID: 9B74D3A59181947D
97 changed files with 17956 additions and 0 deletions

130
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,130 @@
---
import { LOCALE, SITE } from "@config";
import "../../custom.scss";
import Matomo from "../components/Matomo.astro";
const googleSiteVerification = import.meta.env.PUBLIC_GOOGLE_SITE_VERIFICATION;
export interface Props {
pageTitle?: string;
title?: string;
author?: string;
description?: string;
ogImage?: string;
canonicalURL?: string;
pubDatetime?: Date;
modDatetime?: Date | null;
}
const {
pageTitle = "Home",
title = pageTitle + " - " +SITE.title,
author = SITE.author,
description = SITE.desc,
ogImage = SITE.ogImage,
canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
pubDatetime,
modDatetime,
} = Astro.props;
const socialImageURL = new URL(
ogImage ?? SITE.ogImage ?? "og.png",
Astro.url.origin
).href;
---
<!doctype html>
<html
lang=`${LOCALE.lang ?? "en"}`
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
<link rel="canonical" href={canonicalURL} />
<meta name="generator" content={Astro.generator} />
<!-- General Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta name="author" content={author} />
<link rel="sitemap" href="/sitemap-index.xml" />
<!-- Open Graph / Facebook -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={socialImageURL} />
<!-- Article Published/Modified time -->
{
pubDatetime && (
<meta
property="article:published_time"
content={pubDatetime.toISOString()}
/>
)
}
{
modDatetime && (
<meta
property="article:modified_time"
content={modDatetime.toISOString()}
/>
)
}
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />
<!-- Icons -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#050732">
<meta name="theme-color" content="#050732">
{
// If PUBLIC_GOOGLE_SITE_VERIFICATION is set in the environment variable,
// include google-site-verification tag in the heading
// Learn more: https://support.google.com/webmasters/answer/9008080#meta_tag_verification&zippy=%2Chtml-tag
googleSiteVerification && (
<meta
name="google-site-verification"
content={googleSiteVerification}
/>
)
}
<!-- ViewTransitions /-->
<script is:inline src="/toggle-theme.js"></script>
<Matomo />
</head>
<body>
<slot />
</body>
</html>
<style>
html {
height: 100%;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
html, body { max-width: 100%; overflow-x: hidden; }
</style>

40
src/layouts/Main.astro Normal file
View file

@ -0,0 +1,40 @@
---
import { Heading } from "react-bulma-components"
interface StringTitleProp {
pageTitle?: string;
pageDesc?: string;
fullWidth?: boolean;
}
interface ArrayTitleProp {
pageTitle?: [string, string];
titleTransition: string;
pageDesc?: string;
fullWidth?: boolean;
}
export type Props = StringTitleProp | ArrayTitleProp;
const { props } = Astro;
let maxWidth = props.fullWidth ? "84rem" : "" ;
---
<main id="main-content" style={{ maxWidth: maxWidth }}>
{
"titleTransition" in props ? (
<Heading>
{ props.pageTitle?.[0] }
<span transition:name={props.titleTransition}>
{ props.pageTitle?.[1] }
</span>
</Heading>
) : (
<Heading>{ props.pageTitle }</Heading>
)
}
<p class="my-2">{ props.pageDesc }</p>
<slot />
</main>

View file

@ -0,0 +1,29 @@
---
import { SITE } from "@config";
import Breadcrumbs from "@components/Breadcrumbs.astro";
import Footer from "@components/Footer.astro";
import Header from "@components/Header.astro";
import Layout from "./Layout.astro";
export interface Props {
frontmatter: {
title: string;
description?: string;
};
}
const { frontmatter } = Astro.props;
---
<Layout title={`${frontmatter.title} | ${SITE.title}`}>
<Header activeNav="about" />
<main id="main-content">
<section id="about" class="prose mb-28 max-w-3xl prose-img:border-0">
<h1 class="title mb-3">{frontmatter.title}</h1>
<div class="content">
<slot />
</div>
</section>
</main>
<Footer />
</Layout>

View file

@ -0,0 +1,129 @@
---
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Tag from "@components/Tag.astro";
import Datetime from "@components/Datetime.astro";
import type { CollectionEntry } from "astro:content";
import { slugifyStr } from "@utils/slugify";
import ShareLinks from "@components/ShareLinks.astro";
import { SITE } from "@config";
import { Icon } from 'astro-icon/components'
import { Level, Button } from "react-bulma-components"
export interface Props {
post: CollectionEntry<"blog">;
}
const { post } = Astro.props;
const {
title,
author,
description,
ogImage,
canonicalURL,
pubDatetime,
modDatetime,
tags,
} = post.data;
const { Content } = await post.render();
const ogImageUrl = typeof ogImage === "string" ? ogImage : ogImage?.src;
const ogUrl = new URL(
ogImageUrl ?? `/assets/og.webp`,
Astro.url.origin
).href;
const layoutProps = {
title: `${title} | ${SITE.title}`,
author,
description,
pubDatetime,
modDatetime,
canonicalURL,
ogImage: ogUrl,
scrollSmooth: true,
};
---
<Layout {...layoutProps}>
<Header />
<main id="main-content">
<h1 transition:name={slugifyStr(title)} class="title">{title}</h1>
<Datetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
className="my-1"
/>
{
ogImage && <img src={ogImageUrl} alt="Cover image">
}
<ul class="mb-4">
{
tags.map(tag => <Tag tag={slugifyStr(tag)} />)
}
</ul>
<article id="article" role="article">
<div class="content">
<Content />
</div>
</article>
<Level className="my-4" breakpoint="mobile">
<Level.Side>
<Button id="back-to-top">
<Icon name="fa6-solid:arrow-up" />
<span class="ml-2">Back to Top</span>
</Button>
</Level.Side>
<Level.Side align="right">
<ShareLinks />
</Level.Side>
</Level>
{
SITE.issoLocation != "" && (
<p><span class="is-size-3"><b>Comments</b></span> (<a target="_blank" rel="noopener noreferrer" href="/pages/blog-legal#comments-rules">rules</a>, <a target="_blank" rel="noopener noreferrer" href="/pages/blog-legal#comments-privacy">privacy</a>)</p>
<script is:inline data-isso={ SITE.issoLocation } src={ SITE.issoLocation + "/js/embed.min.js" } async></script>
<div id="isso-thread"><noscript>You need to enable JavaScript to see and post comments.</noscript></div>
)
}
</main>
<Footer />
</Layout>
<script is:inline>
function addHeadingLinks() {
let headings = Array.from(document.querySelectorAll("h2, h3, h4, h5, h6"));
for (let heading of headings) {
heading.classList.add("group");
let link = document.createElement("a");
link.innerText = "#";
link.className = "ml-2";
link.href = "#" + heading.id;
link.ariaHidden = "true";
heading.appendChild(link);
}
}
addHeadingLinks();
function backToTop() {
document.querySelector("#back-to-top")?.addEventListener("click", () => {
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
});
}
backToTop();
</script>

47
src/layouts/Posts.astro Normal file
View file

@ -0,0 +1,47 @@
---
import type { CollectionEntry } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Pagination from "@components/Pagination.astro";
import BlogCard from "../components/BlogCard.astro";
import { Button } from "react-bulma-components";
import { Icon } from "astro-icon/components";
export interface Props {
currentPage: number;
totalPages: number;
paginatedPosts: CollectionEntry<"blog">[];
}
const { currentPage, totalPages, paginatedPosts} = Astro.props;
---
<Layout pageTitle="Blog">
<Header activeNav="blog" />
<Main pageTitle="Blog" pageDesc="All the articles I've posted.">
<a href="/rss.xml">
<Button className="mb-5">
<Icon name="fa6-solid:rss" />
<span class="ml-2">RSS Feed</span>
</Button>
</a>
<div>
{
paginatedPosts.map(({ data, slug }) => (
<BlogCard href={`/blog/${slug}/`} frontmatter={data} />
))
}
</div>
</Main>
<Pagination
{currentPage}
{totalPages}
prevUrl={`/blog${currentPage - 1 !== 1 ? "/" + (currentPage - 1) : ""}/`}
nextUrl={`/blog/${currentPage + 1}/`}
/>
<Footer />
</Layout>

View file

@ -0,0 +1,49 @@
---
import { type CollectionEntry } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import BlogCard from "../components/BlogCard.astro";
import Pagination from "@components/Pagination.astro";
import { SITE } from "@config";
export interface Props {
currentPage: number;
totalPages: number;
paginatedPosts: CollectionEntry<"blog">[];
tag: string;
tagName: string;
}
const { currentPage, totalPages, paginatedPosts, tag, tagName } = Astro.props;
---
<Layout title={`Tag: ${tagName} | ${SITE.title}`}>
<Header activeNav="tags" />
<Main
pageTitle={[`Tag:`, `${tagName}`]}
titleTransition={tag}
pageDesc={`All the articles with the tag "${tagName}".`}
>
<h1 slot="title" transition:name={tag}>{`Tag:${tag}`}</h1>
<ul>
{
paginatedPosts.map(({ data, slug }) => (
<BlogCard href={`/posts/${slug}/`} frontmatter={data} />
))
}
</ul>
</Main>
<Pagination
{currentPage}
{totalPages}
prevUrl={`/tags/${tag}${
currentPage - 1 !== 1 ? "/" + (currentPage - 1) : ""
}/`}
nextUrl={`/tags/${tag}/${currentPage + 1}/`}
/>
<Footer noMarginTop={totalPages > 1} />
</Layout>