mirror of
https://github.com/mikkelsvartveit/astro-personal-website.git
synced 2025-12-22 11:12:38 +00:00
Migrate to latest versions of Astro, Svelte and Tailwind
Fix hover in prose layout
This commit is contained in:
parent
53ce4bd8ee
commit
332e1dea33
16 changed files with 3655 additions and 3704 deletions
|
|
@ -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",
|
||||
});
|
||||
|
|
|
|||
28
package.json
28
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7177
pnpm-lock.yaml
7177
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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+/));
|
||||
|
||||
|
|
|
|||
|
|
@ -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
13
src/styles/global.css
Normal 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"] *)));
|
||||
Loading…
Reference in a new issue