From 4080f8b3b3503ec3265f14902c221ea96b58b410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:50:34 +0100 Subject: [PATCH] redesign: finish blog & projects --- package.json | 2 + pnpm-lock.yaml | 23 ++++ src/components/ArrowCard.astro | 29 ----- .../{ArrowCardInner.tsx => ArrowCard.tsx} | 21 +++- src/components/ArticleContent.astro | 33 ++++++ src/components/BaseHead.astro | 4 + src/components/Blog.tsx | 108 +++++++++++++++++ src/components/Header.astro | 4 +- src/components/Projects.tsx | 109 ++++++++++++++++++ src/components/Search.tsx | 13 +-- src/layouts/ArticleBottomLayout.astro | 85 +++++--------- src/layouts/ArticleTopLayout.astro | 74 +++++------- src/pages/blog/[...slug].astro | 2 + src/pages/blog/index.astro | 5 +- src/pages/index.astro | 2 +- src/pages/projects/[...slug].astro | 2 + src/pages/projects/index.astro | 6 +- src/pages/search/index.astro | 2 +- 18 files changed, 373 insertions(+), 151 deletions(-) delete mode 100644 src/components/ArrowCard.astro rename src/components/{ArrowCardInner.tsx => ArrowCard.tsx} (70%) create mode 100644 src/components/ArticleContent.astro create mode 100644 src/components/Blog.tsx create mode 100644 src/components/Projects.tsx diff --git a/package.json b/package.json index 746ec57..a31c753 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@astrojs/rss": "^4.0.17", "@astrojs/sitemap": "^3.6.0", "@astrojs/solid-js": "^6.0.0", + "@iconify-icon/solid": "^3.0.3", "@iconify-json/devicon": "^1.2.61", "@iconify-json/material-icon-theme": "^1.2.56", "@iconify-json/pixel": "^1.2.1", @@ -31,6 +32,7 @@ "canvaskit-wasm": "^0.40.0", "clsx": "^2.1.1", "fuse.js": "^7.1.0", + "iconify-icon": "^3.0.2", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "sharp": "^0.34.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab64f0f..845a342 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@astrojs/solid-js': specifier: ^6.0.0 version: 6.0.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(solid-js@1.9.11)(yaml@2.8.2) + '@iconify-icon/solid': + specifier: ^3.0.3 + version: 3.0.3(solid-js@1.9.11) '@iconify-json/devicon': specifier: ^1.2.61 version: 1.2.61 @@ -59,6 +62,9 @@ importers: fuse.js: specifier: ^7.1.0 version: 7.1.0 + iconify-icon: + specifier: ^3.0.2 + version: 3.0.2 rehype-katex: specifier: ^7.0.1 version: 7.0.1 @@ -426,6 +432,11 @@ packages: cpu: [x64] os: [win32] + '@iconify-icon/solid@3.0.3': + resolution: {integrity: sha512-VUM8/gscfrU0e6dUWjitdsJrNzYoY1bGTp1+gfZIAbvo+tIGcP9bD3WAvFb6pHaCYL/5X6erZUUn8pL0c0xX9Q==} + peerDependencies: + solid-js: '>=1.0.0' + '@iconify-json/devicon@1.2.61': resolution: {integrity: sha512-GvJZrzyWbChwki9yscm8WaCfIHqFVi4hQbjNxoDS0JLsuAk4uyGOnmlHCJFN+SzAZ9FMU2RtmkdJGjYuw4G96w==} @@ -1501,6 +1512,9 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + iconify-icon@3.0.2: + resolution: {integrity: sha512-DYPAumiUeUeT/GHT8x2wrAVKn1FqZJqFH0Y5pBefapWRreV1BBvqBVMb0020YQ2njmbR59r/IathL2d2OrDrxA==} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3021,6 +3035,11 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true + '@iconify-icon/solid@3.0.3(solid-js@1.9.11)': + dependencies: + iconify-icon: 3.0.2 + solid-js: 1.9.11 + '@iconify-json/devicon@1.2.61': dependencies: '@iconify/types': 2.0.0 @@ -4237,6 +4256,10 @@ snapshots: http-cache-semantics@4.2.0: {} + iconify-icon@3.0.2: + dependencies: + '@iconify/types': 2.0.0 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 diff --git a/src/components/ArrowCard.astro b/src/components/ArrowCard.astro deleted file mode 100644 index 48e60ae..0000000 --- a/src/components/ArrowCard.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import { Icon } from "astro-icon/components"; -import ArrowCardInner from "./ArrowCardInner"; -import type { CollectionEntry } from "astro:content"; - -export interface Props { - entry: CollectionEntry<"blog"> | CollectionEntry<"projects">; - pill?: string; -} - -const { entry, pill } = Astro.props; ---- - - -
- - -
-
diff --git a/src/components/ArrowCardInner.tsx b/src/components/ArrowCard.tsx similarity index 70% rename from src/components/ArrowCardInner.tsx rename to src/components/ArrowCard.tsx index 44fb4c9..380b2f7 100644 --- a/src/components/ArrowCardInner.tsx +++ b/src/components/ArrowCard.tsx @@ -1,16 +1,14 @@ import type { CollectionEntry } from "astro:content"; import { formatDate } from "@/lib/utils"; -import { children } from "solid-js"; +import { Icon } from '@iconify-icon/solid'; type Props = { entry: CollectionEntry<"blog"> | CollectionEntry<"projects">; pill?: string; - children: any; }; -export default function ArrowCard({ entry, pill, children: c }: Props) { - const arrow = children(() => c).toArray(); +export default function ArrowCard({ entry, pill }: Props) { return ( - {arrow} +
+ + +
); } \ No newline at end of file diff --git a/src/components/ArticleContent.astro b/src/components/ArticleContent.astro new file mode 100644 index 0000000..2afc9fa --- /dev/null +++ b/src/components/ArticleContent.astro @@ -0,0 +1,33 @@ +--- +import { render, type CollectionEntry } from "astro:content"; +import Container from "./Container.astro"; + +type Props = { + entry: CollectionEntry<"projects"> | CollectionEntry<"blog">; +}; + +const { entry } = Astro.props; + +const { Content } = await render(entry); +--- + + +
+ +
+
+ + diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro index 70aca40..c91f40d 100644 --- a/src/components/BaseHead.astro +++ b/src/components/BaseHead.astro @@ -97,6 +97,10 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props; ); + + []; +}; + +export default function Blog({ data, tags }: Props) { + const [filter, setFilter] = createSignal(new Set()); + const [posts, setPosts] = createSignal[]>([]); + + createEffect(() => { + setPosts( + data.filter((entry) => + Array.from(filter()).every((value) => + entry.data.tags.some( + (tag: string) => + tag.toLowerCase() === String(value).toLowerCase() + ) + ) + ) + ); + }); + + function toggleTag(tag: string) { + setFilter( + (prev) => + new Set( + prev.has(tag) + ? [...prev].filter((t) => t !== tag) + : [...prev, tag] + ) + ); + } + + return ( +
+
+
+
+ Filter +
+
    + + {(tag) => ( +
  • + +
  • + )} +
    +
+
+
+
+
+
+ SHOWING {posts().length} OF {data.length} POSTS +
+
    + {posts().map((post) => ( +
  • + +
  • + ))} + + +

    + There seem to be no blogs matching the filters + yet... +

    +
    +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Header.astro b/src/components/Header.astro index 775d0ee..b372beb 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -25,7 +25,7 @@ const subpath = pathname.match(/[^/]+/g); src={pictureOfMe} loading="eager" alt="Me" - class="size-6 fill-current rounded-lg" + class="size-6 fill-current rounded-lg mr-2" quality="low" />
@@ -49,7 +49,7 @@ const subpath = pathname.match(/[^/]+/g); "flex items-center justify-center", "transition-colors duration-300 ease-in-out", pathname === LINK.HREF || - "/" + subpath?.[0] === LINK.HREF + "/" + subpath?.[0] + "/" === LINK.HREF ? "bg-black dark:bg-white text-white dark:text-black" : "hover:bg-black/5 dark:hover:bg-white/20 hover:text-black dark:hover:text-white", )} diff --git a/src/components/Projects.tsx b/src/components/Projects.tsx new file mode 100644 index 0000000..f7961eb --- /dev/null +++ b/src/components/Projects.tsx @@ -0,0 +1,109 @@ +import type { CollectionEntry } from "astro:content"; +import { createEffect, createSignal, For, Show } from "solid-js"; +import ArrowCard from "@/components/ArrowCard"; +import { cn } from "@/lib/utils"; +import { Icon } from "@iconify-icon/solid"; + +type Props = { + tags: string[]; + data: CollectionEntry<"projects">[]; +}; + +export default function Projects({ data, tags }: Props) { + const [filter, setFilter] = createSignal(new Set()); + const [projects, setProjects] = createSignal[]>( + [] + ); + + createEffect(() => { + setProjects( + data.filter((entry) => + Array.from(filter()).every((value) => + entry.data.tags.some( + (tag: string) => + tag.toLowerCase() === String(value).toLowerCase() + ) + ) + ) + ); + }); + + function toggleTag(tag: string) { + setFilter( + (prev) => + new Set( + prev.has(tag) + ? [...prev].filter((t) => t !== tag) + : [...prev, tag] + ) + ); + } + + return ( +
+
+
+
+ Filter +
+
    + + {(tag) => ( +
  • + +
  • + )} +
    +
+
+
+
+
+
+ SHOWING {projects().length} OF {data.length} PROJECTS +
+
    + {projects().map((project) => ( +
  • + +
  • + ))} + +

    + There seem to be no projects matching the + filters yet... +

    +
    +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Search.tsx b/src/components/Search.tsx index c0fc015..e24944c 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -1,13 +1,13 @@ import type { CollectionEntry } from "astro:content" import { createEffect, createSignal } from "solid-js" import Fuse from "fuse.js" -// import ArrowCard from "@components/ArrowCard" +import ArrowCard from "@/components/ArrowCard" type Props = { data: CollectionEntry<"blog">[] } -export default function Search({data}: Props) { +export default function Search({ data }: Props) { const [query, setQuery] = createSignal("") const [results, setResults] = createSignal[]>([]) @@ -34,21 +34,20 @@ export default function Search({data}: Props) { return (
- + - +
{(query().length >= 2 && results().length >= 1) && (
-
+
Found {results().length} results for {`'${query()}'`}
    {results().map(result => (
  • - {/* */} -
    {JSON.stringify(result, undefined, 2)}
    +
  • ))}
diff --git a/src/layouts/ArticleBottomLayout.astro b/src/layouts/ArticleBottomLayout.astro index b2e7439..495d6bb 100644 --- a/src/layouts/ArticleBottomLayout.astro +++ b/src/layouts/ArticleBottomLayout.astro @@ -1,5 +1,6 @@ --- -import { type CollectionEntry, getCollection, render } from "astro:content"; +import { type CollectionEntry, getCollection } from "astro:content"; +import { Icon } from "astro-icon/components"; type Props = { entry: CollectionEntry<"blog"> | CollectionEntry<"projects">; @@ -8,7 +9,6 @@ type Props = { // Get the requested entry const { entry } = Astro.props; const { collection } = entry; -const { Content } = await render(entry); // Get the next and prev entries (modulo to wrap index) const items = (await getCollection(collection)) @@ -20,9 +20,6 @@ const next = items[(index - 1 + items.length) % items.length]; --- - - - - +
+ + +
diff --git a/src/layouts/ArticleTopLayout.astro b/src/layouts/ArticleTopLayout.astro index 002dfc6..1c170d0 100644 --- a/src/layouts/ArticleTopLayout.astro +++ b/src/layouts/ArticleTopLayout.astro @@ -1,5 +1,6 @@ --- import type { CollectionEntry } from "astro:content"; +import { Icon } from "astro-icon/components"; import { formatDate, readingTime } from "@/lib/utils"; type Props = { @@ -19,50 +20,41 @@ const repoUrl = collection === "projects" ? data.repoUrl : null; href={`/${collection}/`} class="group w-fit p-1.5 gap-1.5 text-sm flex items-center border rounded hover:bg-black/5 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out" > - - - - +
+ + +
Back to {collection}
-
+
- - - + {formatDate(date)}
- - - + {readingTime(body ?? "")}
-

+

{title}

@@ -77,13 +69,8 @@ const repoUrl = collection === "projects" ? data.repoUrl : null; target="_blank" class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend" > - - - - + + See Demo @@ -94,13 +81,8 @@ const repoUrl = collection === "projects" ? data.repoUrl : null; target="_blank" class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend" > - - - - + + See Repository diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro index b5645a5..9306970 100644 --- a/src/pages/blog/[...slug].astro +++ b/src/pages/blog/[...slug].astro @@ -5,6 +5,7 @@ import TopLayout from "@/layouts/TopLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro"; import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro"; import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro"; +import ArticleContent from "@/components/ArticleContent.astro"; // Create the static blog pages export async function getStaticPaths() { @@ -33,6 +34,7 @@ const { title, summary } = post.data;
+
diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index a6e4f4b..556c0ca 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -4,6 +4,7 @@ import PageLayout from "@/layouts/PageLayout.astro"; import TopLayout from "@/layouts/TopLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro"; import { BLOG } from "@/consts"; +import Blog from "@/components/Blog"; const posts = (await getCollection("blog")) .filter((post) => !post.data.draft) @@ -22,9 +23,7 @@ const tags = [...new Set(posts.flatMap((post) => post.data.tags))].sort(
- -
{JSON.stringify(posts)}
-
{JSON.stringify(tags)}
+
diff --git a/src/pages/index.astro b/src/pages/index.astro index ba81734..3e6f440 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,7 +1,7 @@ --- import { getCollection } from "astro:content"; import PageLayout from "@/layouts/PageLayout.astro"; -import ArrowCard from "@/components/ArrowCard.astro"; +import ArrowCard from "@/components/ArrowCard"; import StackCard from "@/components/StackCard.astro"; import { SITE, SOCIALS } from "@/consts"; diff --git a/src/pages/projects/[...slug].astro b/src/pages/projects/[...slug].astro index 3daf1da..8c534dd 100644 --- a/src/pages/projects/[...slug].astro +++ b/src/pages/projects/[...slug].astro @@ -5,6 +5,7 @@ import TopLayout from "@/layouts/TopLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro"; import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro"; import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro"; +import ArticleContent from "@/components/ArticleContent.astro"; // Create the static projects pages export async function getStaticPaths() { @@ -33,6 +34,7 @@ const { title, summary } = project.data;
+
diff --git a/src/pages/projects/index.astro b/src/pages/projects/index.astro index 2715381..2cdd067 100644 --- a/src/pages/projects/index.astro +++ b/src/pages/projects/index.astro @@ -3,7 +3,7 @@ import { getCollection } from "astro:content"; import PageLayout from "@/layouts/PageLayout.astro"; import TopLayout from "@/layouts/TopLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro"; -// import Projects from "@components/Projects"; +import Projects from "@/components/Projects"; import { PROJECTS } from "@/consts"; const projects = (await getCollection("projects")) @@ -23,9 +23,7 @@ const tags = [
- -
{JSON.stringify(projects)}
-
{JSON.stringify(tags)}
+
diff --git a/src/pages/search/index.astro b/src/pages/search/index.astro index 9a23215..33a0d71 100644 --- a/src/pages/search/index.astro +++ b/src/pages/search/index.astro @@ -17,7 +17,7 @@ const data = [...posts, ...projects] as CollectionEntry<"blog">[]; -
+
{SEARCH.TITLE}