This commit is contained in:
@@ -15,7 +15,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const {
|
||||
data: { title, cover, icon, date, reviewRating },
|
||||
data: { title, cover, icon, date, rating },
|
||||
collection,
|
||||
body,
|
||||
id,
|
||||
@@ -28,11 +28,11 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||
---
|
||||
|
||||
<Card
|
||||
classes={`grid gradient border-1 border-neutral overflow-hidden ${cover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||
classes={`max-h-250px grid gradient border-1 border-neutral overflow-hidden ${cover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
||||
{
|
||||
(date || body || reviewRating !== undefined) && (
|
||||
<Card.Metadata date={date} readDuration={readDuration(body)} rating={reviewRating} />
|
||||
(date || body || rating !== undefined) && (
|
||||
<Card.Metadata date={date} readDuration={readDuration(body)} rating={rating} />
|
||||
)
|
||||
}
|
||||
<Card.Title classes="text-4xl flex items-center gap-2">
|
||||
|
||||
@@ -15,12 +15,12 @@ interface Props {
|
||||
}
|
||||
|
||||
async function checkImage(image: ImageMetadata) {
|
||||
const src = image.src;
|
||||
const src = typeof image === "string" ? image : image.src;
|
||||
if (!src) return false;
|
||||
try {
|
||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true;
|
||||
const res = await inferRemoteSize(src);
|
||||
if (res.format) {
|
||||
console.log(res)
|
||||
image.format = res.format;
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let date: string | Date;
|
||||
export let readDuration: string | undefined;
|
||||
export let readDuration: number | undefined;
|
||||
export let rating: string | number | undefined;
|
||||
|
||||
const toDate = (d: string | Date) =>
|
||||
typeof d === "string" ? new Date(d) : d;
|
||||
@@ -10,6 +11,13 @@
|
||||
return isNaN(v.getTime()) ? "" : v.toISOString();
|
||||
};
|
||||
|
||||
function formatRating(rating: string | number) {
|
||||
if (typeof rating === "number") {
|
||||
return "⭐".repeat(rating);
|
||||
}
|
||||
return rating;
|
||||
}
|
||||
|
||||
const formatDate = (d: string | Date) => {
|
||||
try {
|
||||
return new Intl.DateTimeFormat("de-DE", {
|
||||
@@ -29,7 +37,13 @@
|
||||
>{formatDate(date)}</time>
|
||||
{/if}
|
||||
|
||||
{#if readDuration}
|
||||
<div class="text-sm text-neutral">{readDuration} mins read</div>
|
||||
{#if typeof readDuration === "number"}
|
||||
{#if readDuration > 1}
|
||||
<div class="text-sm text-neutral">{readDuration} mins read</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if rating}
|
||||
<div class="text-sm text-neutral">{formatRating(rating)}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Code } from 'astro:components';
|
||||
import Recipe from "./Recipe.astro";
|
||||
import Article from "./Article.astro";
|
||||
import IconCode from "~icons/tabler/Code";
|
||||
import Review from "./Review.astro";
|
||||
const { resource } = Astro.props;
|
||||
const type = resource?.content?._type ?? "unknown";
|
||||
@@ -20,15 +19,7 @@ const type = resource?.content?._type ?? "unknown";
|
||||
)
|
||||
}
|
||||
|
||||
<details>
|
||||
<summary><IconCode class="inline"/></summary>
|
||||
<details >
|
||||
<summary class="flex"><span class="i-tabler-code w-6 h-6 inline"/></summary>
|
||||
<Code code={JSON.stringify(resource, null, 2)} lang="json" theme="dark-plus" />
|
||||
</details>
|
||||
|
||||
<style>
|
||||
|
||||
summary::marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,7 @@ const { resource } = Astro.props;
|
||||
/>
|
||||
)
|
||||
}
|
||||
<h1 class="text-4xl">{resource?.content?.itemReviewed?.name || "Unknown Name"}</h1>
|
||||
<h1 class="text-4xl mb-4">{resource?.content?.itemReviewed?.name || "Unknown Name"}</h1>
|
||||
{ resource?.content?.reviewRating?.ratingValue !== undefined && <div>{resource?.content?.reviewRating?.ratingValue}</div>}
|
||||
<div set:html={markdownToHtml(resource?.content?.reviewBody)} />
|
||||
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
import { glob } from 'astro/loaders';
|
||||
import { defineCollection, z, type ImageFunction } from 'astro:content';
|
||||
|
||||
const defaultSchema = ({ image }: { image: ImageFunction }) => z.object({
|
||||
title: z.string(),
|
||||
date: z.date(),
|
||||
cover: image().optional(),
|
||||
links: z.array(z.array(z.string())).optional(),
|
||||
coverAlt: z.string().optional(),
|
||||
toc: z.boolean().optional(),
|
||||
description: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
draft: z.boolean().optional(),
|
||||
featured: z.boolean().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
_layout: z.enum(['normal', 'transparent']).optional(),
|
||||
})
|
||||
import { glob } from "astro/loaders";
|
||||
import { defineCollection, type ImageFunction, z } from "astro:content";
|
||||
|
||||
const defaultSchema = ({ image }: { image: ImageFunction }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
date: z.date(),
|
||||
cover: image().optional(),
|
||||
links: z.array(z.array(z.string())).optional(),
|
||||
rating: z.union([z.string(), z.number()]).optional(),
|
||||
coverAlt: z.string().optional(),
|
||||
toc: z.boolean().optional(),
|
||||
description: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
draft: z.boolean().optional(),
|
||||
featured: z.boolean().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
_layout: z.enum(["normal", "transparent"]).optional(),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
'blog': defineCollection({
|
||||
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/blog" }),
|
||||
"blog": defineCollection({
|
||||
loader: glob({ pattern: "**/[^_]*.{md,mdx}", base: "./src/content/blog" }),
|
||||
schema: defaultSchema,
|
||||
}),
|
||||
"projects": defineCollection({
|
||||
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/projects" }),
|
||||
loader: glob({
|
||||
pattern: "**/[^_]*.{md,mdx}",
|
||||
base: "./src/content/projects",
|
||||
}),
|
||||
schema: defaultSchema,
|
||||
}),
|
||||
"photos": defineCollection({
|
||||
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/photos" }),
|
||||
loader: glob({
|
||||
pattern: "**/[^_]*.{md,mdx}",
|
||||
base: "./src/content/photos",
|
||||
}),
|
||||
schema: defaultSchema,
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ const parser = new MarkdownIt();
|
||||
export function readDuration(markdown: string): number | undefined {
|
||||
if (!markdown) return;
|
||||
const words = markdown.split(" ")?.filter(Boolean)?.length;
|
||||
if (!words) return;
|
||||
return words && Math.round(words / 250);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
export const languages = {
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
};
|
||||
|
||||
export const defaultLang = 'de';
|
||||
export const defaultLang = "de";
|
||||
|
||||
export const ui = {
|
||||
en: {
|
||||
@@ -15,15 +14,24 @@ export const ui = {
|
||||
"read-more": "Read More",
|
||||
"latest-posts": "Latest Posts",
|
||||
"latest-projects": "Latest Projects",
|
||||
'home.title': 'Hi, I’m Max :)',
|
||||
'website-source': 'source',
|
||||
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
|
||||
'nav.blog': 'Blog',
|
||||
'nav.projects': 'Projects',
|
||||
'nav.resources': 'Resources',
|
||||
'nav.photos': 'Photos',
|
||||
'toc.title': 'Table of Contents',
|
||||
"home.title": "Hi, I’m Max :)",
|
||||
"website-source": "source",
|
||||
"home.subtitle":
|
||||
"Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.",
|
||||
"nav.blog": "Blog",
|
||||
"nav.projects": "Projects",
|
||||
"nav.resources": "Resources",
|
||||
"nav.photos": "Photos",
|
||||
"toc.title": "Table of Contents",
|
||||
"resume": "Resume",
|
||||
"articles": "📰 Articles",
|
||||
"articles.description": "Random articles that impressed me while reading.",
|
||||
"movies": "🎥 Movies",
|
||||
"movies.description": "Watched, not watched, found good, found bad.",
|
||||
"recipes": "🍲 Recipes",
|
||||
"recipes.description": "Things I have cooked or want to cook.",
|
||||
"series": "📺 Series",
|
||||
"series.description": "Series I have watched or plan to watch.",
|
||||
},
|
||||
de: {
|
||||
"en": "English",
|
||||
@@ -33,15 +41,26 @@ export const ui = {
|
||||
"latest-posts": "Neueste Posts",
|
||||
"latest-projects": "Neueste Projekte",
|
||||
"read-more": "weiterlesen",
|
||||
'home.title': 'Hi, ich bin Max :)',
|
||||
'website-source': 'Sourcecode',
|
||||
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
|
||||
'nav.blog': 'Blog',
|
||||
'nav.projects': 'Projekte',
|
||||
'nav.resources': 'Resources',
|
||||
'nav.photos': 'Fotos',
|
||||
"home.title": "Hi, ich bin Max :)",
|
||||
"website-source": "Sourcecode",
|
||||
"home.subtitle":
|
||||
"Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.",
|
||||
"nav.blog": "Blog",
|
||||
"nav.projects": "Projekte",
|
||||
"nav.resources": "Resourcen",
|
||||
"nav.photos": "Fotos",
|
||||
"resume": "Lebenslauf",
|
||||
'toc.title': 'Inhaltsverzeichnis',
|
||||
"toc.title": "Inhaltsverzeichnis",
|
||||
"articles": "📰 Artikel",
|
||||
"articles.description":
|
||||
"Random Artikel die mich beim lesen beindruckt haben",
|
||||
"movies": "🎥 Filme",
|
||||
"movies.description":
|
||||
"Gesehen, nicht gesehen, für gut befunden, für schlecht befunden.",
|
||||
"recipes": "🍲 Rezepte",
|
||||
"recipes.description": "Sachen die ich gekocht habe oder kochen will",
|
||||
"series": "📺 Serien",
|
||||
"series.description": "Serien die ich angeguckt habe",
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ import Layout from "@layouts/Layout.astro";
|
||||
import HeroCard from "@components/HeroCard.astro";
|
||||
import * as memorium from "@helpers/memorium";
|
||||
import { resources as resourceTypes } from "../resources.ts";
|
||||
import {useTranslations} from "@i18n/utils";
|
||||
|
||||
const { resourceType } = Astro.params;
|
||||
|
||||
const t = useTranslations(Astro.url);
|
||||
|
||||
async function safeGetResource() {
|
||||
try {
|
||||
return await memorium.listResource(resourceType);
|
||||
@@ -21,7 +24,6 @@ export async function getStaticPaths() {
|
||||
return {
|
||||
params: {
|
||||
resourceType: type.id,
|
||||
resourceName: type.data.title,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -32,7 +34,10 @@ function isValidResource(res) {
|
||||
}
|
||||
---
|
||||
|
||||
|
||||
<Layout title="Max Richter">
|
||||
<h1 class="text-4xl mb-4">{t(resourceType)}</h1>
|
||||
<p>{t(`${resourceType as "articles"}.description`)}</p>
|
||||
{
|
||||
resources.content
|
||||
.filter((res) => isValidResource(res))
|
||||
@@ -44,7 +49,7 @@ function isValidResource(res) {
|
||||
data: {
|
||||
title: resource?.content?.name ?? resource?.content?.headline ?? resource.content?.itemReviewed?.name,
|
||||
date: resource?.content?.datePublished,
|
||||
rating: resource?.content?.reviewRating,
|
||||
rating: resource?.content?.reviewRating?.ratingValue,
|
||||
cover: {
|
||||
src: memorium.getImageUrl(resource.content.image),
|
||||
},
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import HeroCard from "@components/HeroCard.astro";
|
||||
import { resources } from "./resources.ts";
|
||||
import {useTranslations} from "@i18n/utils";
|
||||
|
||||
const t = useTranslations(Astro.url);
|
||||
---
|
||||
|
||||
<Layout title="Max Richter">
|
||||
{resources.map((resource) => <HeroCard post={resource} />)}
|
||||
{resources.map((resource) => <HeroCard post={{...resource, body: t(`${resource.id}.description`), data: {cover:{src:resource.cover}, title: t(resource.id)}}} />)}
|
||||
</Layout>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
const collection = "resources";
|
||||
|
||||
export type ResourceType = {
|
||||
id: string;
|
||||
id: "articles" | "recipes" | "movies" | "series";
|
||||
collection: string;
|
||||
body?: string;
|
||||
data: {
|
||||
title: string;
|
||||
icon: string;
|
||||
};
|
||||
cover: string;
|
||||
};
|
||||
|
||||
// const wiki = {
|
||||
@@ -23,37 +19,29 @@ export type ResourceType = {
|
||||
const articles = {
|
||||
id: "articles",
|
||||
collection,
|
||||
data: {
|
||||
title: "Articles",
|
||||
icon: "📰",
|
||||
},
|
||||
};
|
||||
cover:
|
||||
"https://i0.wp.com/old-dressaday-images.s3-website-us-west-2.amazonaws.com/6a0133ed1b1479970b0134809d9f8b970c.jpg",
|
||||
} as const;
|
||||
|
||||
const recipes = {
|
||||
id: "recipes",
|
||||
collection,
|
||||
data: {
|
||||
title: "Recipes",
|
||||
icon: "🍲",
|
||||
},
|
||||
};
|
||||
cover:
|
||||
"https://marka.max-richter.dev/resources/recipes/images/auberginen_reispfanne.webp",
|
||||
} as const;
|
||||
|
||||
const movies = {
|
||||
id: "movies",
|
||||
collection,
|
||||
data: {
|
||||
title: "Movies",
|
||||
icon: "🎥",
|
||||
},
|
||||
};
|
||||
cover:
|
||||
"https://marka.max-richter.dev/resources/movies/images/2001_a_space_odyssey_cover.jpg",
|
||||
} as const;
|
||||
|
||||
const series = {
|
||||
id: "series",
|
||||
collection,
|
||||
data: {
|
||||
title: "Series",
|
||||
icon: "📺",
|
||||
},
|
||||
};
|
||||
cover:
|
||||
"https://marka.max-richter.dev/resources/series/images/arcane_cover.jpg",
|
||||
} as const;
|
||||
|
||||
export const resources: ResourceType[] = [recipes, articles, movies, series];
|
||||
|
||||
Reference in New Issue
Block a user