Migrate to latest versions of Astro, Svelte and Tailwind

Fix hover in prose layout
This commit is contained in:
Mikkel Svartveit 2025-11-28 09:48:01 +01:00
parent 53ce4bd8ee
commit 332e1dea33
16 changed files with 3655 additions and 3704 deletions

View file

@ -1,13 +1,13 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import svelte from "@astrojs/svelte";
import mdx from "@astrojs/mdx";
import cloudflare from "@astrojs/cloudflare";
import tailwindcss from "@tailwindcss/vite";
// https://astro.build/config
export default defineConfig({
integrations: [tailwind(), svelte(), mdx()],
output: "hybrid",
integrations: [svelte(), mdx()],
output: "static",
adapter: cloudflare({
imageService: "compile",
platformProxy: {
@ -15,12 +15,10 @@ export default defineConfig({
},
}),
vite: {
plugins: [tailwindcss()],
ssr: {
external: ["node:async_hooks"],
},
},
experimental: {
actions: true,
},
site: "https://mikkelsvartveit.com",
});

View file

@ -10,20 +10,22 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/cloudflare": "^10.4.2",
"@astrojs/mdx": "^3.1.3",
"@astrojs/svelte": "^5.7.0",
"@astrojs/tailwind": "^5.1.0",
"astro": "^4.12.2",
"sharp": "^0.32.6",
"svelte": "^4.2.18",
"tailwindcss": "^3.4.7"
"@astrojs/check": "^0.9.6",
"@astrojs/cloudflare": "^12.6.12",
"@astrojs/mdx": "^4.3.12",
"@astrojs/svelte": "^7.2.2",
"astro": "^5.16.2",
"sharp": "^0.34.5",
"svelte": "^5.45.2",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13",
"prettier": "3.0.3",
"prettier-plugin-astro": "^0.12.3",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.5.14"
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.17",
"prettier": "^3.7.1",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.1"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
import { defineAction, z, getApiContext } from "astro:actions";
import { defineAction } from "astro:actions";
import { z } from "astro:schema";
export const server = {
registerView: defineAction({
input: z.object({ articleSlug: z.string() }),
handler: async ({ articleSlug }) => {
const context = getApiContext();
handler: async ({ articleSlug }, context) => {
// @ts-ignore
const { ViewCountKV } = context.locals.runtime.env;
const ViewCountKV = context.locals.runtime?.env?.ViewCountKV;
let viewCount = (await ViewCountKV.get(articleSlug)) || 0;
await ViewCountKV.put(articleSlug, ++viewCount);

View file

@ -4,14 +4,16 @@
import { expoIn } from "svelte/easing";
import { fade } from "svelte/transition";
export let articleSlug: string;
let { articleSlug }: { articleSlug: string } = $props();
let counter: number | null = null;
let counter = $state<number | null>(null);
onMount(async () => {
const readCount = await actions.registerView({ articleSlug });
const { data, error } = await actions.registerView({ articleSlug });
counter = readCount;
if (!error && data !== undefined) {
counter = data;
}
});
</script>

View file

@ -1,5 +1,5 @@
---
import { ViewTransitions } from "astro:transitions";
import { ClientRouter } from "astro:transitions";
interface Props {
title?: string;
@ -50,4 +50,4 @@ const fullImageUrl = origin + (image ?? "/favicon.png");
src="https://umami.mikkel.cloud/script.js"
data-website-id="1caff5b4-223d-47c3-8c5e-a5002d73b993"></script>
<ViewTransitions fallback="none" />
<ClientRouter fallback="none" />

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { slide, fade } from "svelte/transition";
export let currentPath: string;
$: currentPathTrimmed = currentPath.replace(/\/+$/, "");
let { currentPath }: { currentPath: string } = $props();
const currentPathTrimmed = $derived(currentPath.replace(/\/+$/, ""));
const navbarContent = [
{ name: "📝 Articles", href: "/articles" },
@ -10,7 +10,7 @@
{ name: "📷 Photography", href: "/photography" },
];
let collapsed = true;
let collapsed = $state(true);
</script>
<header>
@ -44,7 +44,7 @@
<button
aria-label="Open menu"
class="md:hidden"
on:click={() => (collapsed = !collapsed)}
onclick={() => (collapsed = !collapsed)}
>
<svg
class="h-10 w-10 stroke-yellow-500"
@ -70,20 +70,20 @@
<div
role="presentation"
transition:fade={{ duration: 100 }}
class="fixed left-0 top-0 z-20 h-full w-full bg-black opacity-50"
on:click={() => (collapsed = true)}
on:keydown={(event) => {
class="fixed top-0 left-0 z-20 h-full w-full bg-black opacity-50"
onclick={() => (collapsed = true)}
onkeydown={(event) => {
if (event.key === "Escape") collapsed = true;
}}
/>
></div>
<ul
transition:slide={{ duration: 300, axis: "x" }}
class="fixed right-0 top-0 z-30 block h-full bg-white p-4"
class="fixed top-0 right-0 z-30 block h-full bg-white p-4"
>
<button
aria-label="Close menu"
on:click={() => (collapsed = true)}
onclick={() => (collapsed = true)}
class="ml-auto block h-10 w-10"
>
<svg
@ -101,11 +101,11 @@
</button>
{#each navbarContent as { name, href }}
<li class="my-6 ml-2 mr-16">
<li class="my-6 mr-16 ml-2">
<a
{href}
on:click={() => (collapsed = true)}
class="whitespace-nowrap text-lg tracking-wide text-gray-600 decoration-yellow-400 decoration-2 underline-offset-8"
onclick={() => (collapsed = true)}
class="text-lg tracking-wide whitespace-nowrap text-gray-600 decoration-yellow-400 decoration-2 underline-offset-8"
class:underline={href === currentPathTrimmed}
>
{name}

View file

@ -1,6 +1,7 @@
---
import Navbar from "@components/Navbar.svelte";
import HeadContent from "@components/HeadContent.astro";
import "../styles/global.css";
---
<html lang="en">
@ -11,10 +12,7 @@ import HeadContent from "@components/HeadContent.astro";
</head>
<body class="overflow-y-scroll bg-gray-50">
<Navbar
client:load
currentPath={Astro.url.pathname}
/>
<Navbar client:load currentPath={Astro.url.pathname} />
<main class="pb-6">
<slot />

View file

@ -1,5 +1,5 @@
<div
class="prose-inline-code:rounded prose-inline-code:bg-slate-200 prose-inline-code:px-1 prose-inline-code:font-normal prose-inline-code:tracking-tight prose-inline-code:text-gray-700 before:prose-inline-code:content-[''] after:prose-inline-code:content-[''] prose prose-lg max-w-none font-serif prose-headings:text-gray-600 prose-h1:font-light prose-h1:leading-snug prose-h1:underline prose-h1:decoration-yellow-400 prose-h1:decoration-2 prose-h1:underline-offset-8 prose-p:text-gray-700 prose-a:text-emerald-700 prose-a:underline-offset-2 prose-a:duration-100 hover:prose-a:text-emerald-500 prose-strong:text-gray-700"
class="prose-inline-code:rounded prose-inline-code:bg-slate-200 prose-inline-code:px-1 prose-inline-code:font-normal prose-inline-code:tracking-tight prose-inline-code:text-gray-700 before:prose-inline-code:content-[''] after:prose-inline-code:content-[''] prose prose-lg prose-headings:text-gray-600 prose-h1:font-light prose-h1:leading-snug prose-h1:underline prose-h1:decoration-yellow-400 prose-h1:decoration-2 prose-h1:underline-offset-8 prose-p:text-gray-700 prose-a:text-emerald-700 prose-a:underline-offset-2 prose-a:duration-100 prose-a:hover:text-emerald-500 prose-strong:text-gray-700 max-w-none font-serif"
>
<slot />
</div>

View file

@ -5,7 +5,7 @@ import NotFoundImage from "../assets/images/404.jpeg";
---
<BaseLayout>
<section class="mx-auto max-w-4xl px-3 pb-8 pt-12 sm:px-6">
<section class="mx-auto max-w-4xl px-3 pt-12 pb-8 sm:px-6">
<h1
class="mb-8 text-center font-serif text-3xl font-light tracking-wide text-gray-600 sm:text-4xl"
>

View file

@ -2,23 +2,24 @@
import ContainerLayout from "@layouts/ContainerLayout.astro";
import BaseLayout from "@layouts/BaseLayout.astro";
import TextContentLayout from "@layouts/TextContentLayout.astro";
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import { getCollection, type CollectionEntry } from "astro:content";
import ProseLayout from "@layouts/ProseLayout.astro";
import ArticleViewCounter from "@components/ArticleViewCounter.svelte";
import HeadContent from "@components/HeadContent.astro";
export const getStaticPaths = (async () => {
export async function getStaticPaths() {
const blogCollection = await getCollection("blog");
return blogCollection.map((entry) => ({
params: {
article: entry.slug,
},
params: { article: entry.slug },
props: { entry },
}));
}) satisfies GetStaticPaths;
}
const project = Astro.props.entry;
type Props = {
entry: CollectionEntry<"blog">;
};
const { entry: project } = Astro.props;
const { Content, headings } = await project.render();
const { date, intro, image } = project.data;
const title = headings[0].text;

View file

@ -9,7 +9,7 @@ import portraitImage from "@assets/images/portrait.jpg";
const articles = await getCollection("blog");
const latestArticle = articles.sort(
(p2, p1) => p1.data.date.getTime() - p2.data.date.getTime()
(p2, p1) => p1.data.date.getTime() - p2.data.date.getTime(),
)[0];
const latestArticleSlug = latestArticle.slug;
const latestArticleTitle = (await latestArticle.render()).headings[0].text;
@ -18,9 +18,9 @@ const latestArticleImage = latestArticle.data.image;
<BaseLayout>
<div
class="mx-auto flex max-w-5xl flex-col-reverse items-stretch px-4 pb-6 pt-12 sm:px-6 md:flex-row"
class="mx-auto flex max-w-5xl flex-col-reverse items-stretch px-4 pt-12 pb-6 sm:px-6 md:flex-row"
>
<section class="mb-10 mt-4 w-full md:w-3/5">
<section class="mt-4 mb-10 w-full md:w-3/5">
<h1
class="mb-8 font-serif text-3xl font-light tracking-wide text-gray-600 sm:text-4xl"
>
@ -86,7 +86,7 @@ const latestArticleImage = latestArticle.data.image;
</a>
</BaseLayout>
<footer class="mb-8 mt-20 flex w-full justify-center">
<footer class="mt-20 mb-8 flex w-full justify-center">
<p class="text-xs text-gray-500">
Built with
<a

View file

@ -1,22 +1,29 @@
---
import type { GetStaticPaths } from "astro";
import { Image } from "astro:assets";
import type { ImageMetadata } from "astro";
import { getFileNameFromPath } from "./index.astro";
import RootLayout from "@layouts/RootLayout.astro";
import HeadContent from "@components/HeadContent.astro";
export const getStaticPaths = (async () => {
const photos = await Astro.glob("../../assets/photos/*");
interface PhotoModule {
default: ImageMetadata;
}
return photos.map((photo: any) => ({
export async function getStaticPaths() {
const photoModules = import.meta.glob<PhotoModule>("../../assets/photos/*", {
eager: true,
});
const photos = Object.values(photoModules);
return photos.map((photo) => ({
params: {
photo: getFileNameFromPath(photo.default.src),
},
props: {
photo,
},
props: { photo },
}));
}) satisfies GetStaticPaths;
}
interface Props {
photo: PhotoModule;
}
const { photo } = Astro.props;
---

View file

@ -4,11 +4,19 @@ import ContainerLayout from "@layouts/ContainerLayout.astro";
import BaseLayout from "@layouts/BaseLayout.astro";
import TextContentLayout from "@layouts/TextContentLayout.astro";
import { Image } from "astro:assets";
import type { ImageMetadata } from "astro";
import HeadContent from "@components/HeadContent.astro";
const photos = await Astro.glob("../../assets/photos/*");
interface PhotoModule {
default: ImageMetadata;
}
const sortFiles = (a: any, b: any) => {
const photoModules = import.meta.glob<PhotoModule>("../../assets/photos/*", {
eager: true,
});
const photos = Object.values(photoModules);
const sortFiles = (a: PhotoModule, b: PhotoModule) => {
const aNum = Number(a.default.src.match(/\d+/));
const bNum = Number(b.default.src.match(/\d+/));

View file

@ -3,7 +3,7 @@ import ContainerLayout from "@layouts/ContainerLayout.astro";
import BaseLayout from "@layouts/BaseLayout.astro";
import TextContentLayout from "@layouts/TextContentLayout.astro";
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import { getCollection, type CollectionEntry } from "astro:content";
import ProseLayout from "@layouts/ProseLayout.astro";
import HeadContent from "@components/HeadContent.astro";
@ -17,7 +17,11 @@ export const getStaticPaths = (async () => {
}));
}) satisfies GetStaticPaths;
const project = Astro.props.entry;
type Props = {
entry: CollectionEntry<"programming">;
};
const { entry: project } = Astro.props;
const { Content } = await project.render();
const { title, description, image, date, technologies, website, repository } =
project.data;

13
src/styles/global.css Normal file
View file

@ -0,0 +1,13 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@theme {
--font-sans: "Nunito", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-serif: "Source Serif Pro", ui-serif, Georgia, Cambria,
"Times New Roman", Times, serif;
--font-mono: "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
}
@custom-variant prose-inline-code (&.prose :where(:not(pre)>code):not(:where([class~="not-prose"] *)));