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