feat: display articles
Some checks failed
Deploy to SFTP Server / build (push) Failing after 1m38s

This commit is contained in:
Max Richter
2025-10-22 17:17:34 +02:00
parent 3a120e32fc
commit ae266dbdc5
10 changed files with 121 additions and 47 deletions

View File

@@ -1,5 +1,5 @@
--- ---
import markdownToText from "@helpers/markdownToText"; import { markdownToText,readDuration } from "@helpers/markdown";
import { Card } from "./card"; import { Card } from "./card";
import { useTranslatedPath, useTranslations } from "@i18n/utils"; import { useTranslatedPath, useTranslations } from "@i18n/utils";
import Image from "@components/Image.astro"; import Image from "@components/Image.astro";
@@ -30,7 +30,7 @@ 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={`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 && (<Card.Metadata date={date} />)} {(date || body)&& <Card.Metadata date={date} readDuration={readDuration(body)} />}
<Card.Title classes="text-4xl flex items-center gap-2"> <Card.Title classes="text-4xl flex items-center gap-2">
{ {
icon && icon &&

View File

@@ -1,5 +1,5 @@
--- ---
import markdownToText from "@helpers/markdownToText"; import { markdownToText } from "@helpers/markdown";
import { useTranslatedPath } from "@i18n/utils"; import { useTranslatedPath } from "@i18n/utils";
import type { InferEntrySchema } from "astro:content"; import type { InferEntrySchema } from "astro:content";

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
export let date: string | Date; export let date: string | Date;
export let readDuration: string | 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;
@@ -22,4 +23,8 @@
<time class="text-sm text-neutral" datetime={iso(date)} <time class="text-sm text-neutral" datetime={iso(date)}
>{formatDate(date)}</time> >{formatDate(date)}</time>
{/if} {/if}
{#if readDuration}
<div class="text-sm text-neutral">{readDuration} mins read</div>
{/if}
</div> </div>

View File

@@ -0,0 +1,25 @@
---
import * as memorium from "@helpers/memorium";
import { markdownToHtml, markdownToText } from "@helpers/markdown";
import Image from "@components/Image.astro";
const { resource } = Astro.props;
const ingredients = resource?.content?.recipeIngredient || [];
const instructions = resource?.content?.recipeInstructions || [];
---
<h1 class="text-4xl">{resource?.content?.headline}</h1>
<div>
{
resource?.content?.image && (
<Image
hash
src={{ src: memorium.getImageUrl(resource.content.image) }}
alt="Cover for {resource?.content?.name}"
class="rounded-2xl overflow-hidden"
pictureClass="rounded-2xl"
/>
)
}
</div>
<div set:html={markdownToHtml(resource?.content?.articleBody)} />

View File

@@ -0,0 +1,13 @@
---
import Recipe from "./Recipe.astro";
import Article from "./Article.astro";
const { resource } = Astro.props;
const type = resource?.content?._type ?? "unknown";
---
{type === "Recipe" && <Recipe resource={resource} />}
{type === "Article" && <Article resource={resource} />}
{type === "unknown" && <div>
<h3>Unknown resource type</h3>
</div>}

View File

@@ -0,0 +1,32 @@
---
import * as memorium from "@helpers/memorium";
import { markdownToHtml, markdownToText } from "@helpers/markdown";
import Image from "@components/Image.astro";
const { resource } = Astro.props
const ingredients = resource?.content?.recipeIngredient || [];
const instructions = resource?.content?.recipeInstructions || [];
---
<h1 class="text-4xl">{resource?.content?.name}</h1>
<div>
{resource?.content?.image && <Image hash src={{src: memorium.getImageUrl(resource.content.image)}} alt="Cover for {resource?.content?.name}" class="rounded-2xl overflow-hidden" pictureClass="rounded-2xl" />}
</div>
<p>{resource?.content?.description}</p>
<h2 class="text-2xl">Ingredients</h2>
<ul>
{
ingredients.map((ingredient) => (
<li set:html={markdownToHtml(ingredient)}/>
))
}
</ul>
<h2 class="text-2xl">Steps</h2>
<ol>
{
instructions.map((ingredient) => (
<li set:html={markdownToHtml(ingredient)}/>
))
}
</ol>

View File

@@ -1,8 +1,26 @@
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
const parser = new MarkdownIt(); const parser = new MarkdownIt();
export default function markdownToText(markdown: string): string { export function readDuration(markdown: string): number | undefined {
if(!markdown) return '' const words = markdown.split(" ")?.filter(Boolean)?.length;
return words && Math.round(words / 250);
}
export function markdownToHtml(markdown: string): string {
const md = new MarkdownIt({
html: false, // set to true only if you trust the source
linkify: true,
typographer: true,
breaks: true,
});
// Convert -> sanitize
const unsafeHtml = md.render(markdown);
return unsafeHtml;
}
export function markdownToText(markdown: string): string {
if (!markdown) return "";
return parser return parser
.render(markdown) .render(markdown)
.split("\n") .split("\n")

View File

@@ -1,9 +1,10 @@
--- ---
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import { useTranslatedPath } from "@i18n/utils"; import { useTranslatedPath } from "@i18n/utils";
import ResourceDisplay from "@components/resources/Display.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 markdownToText from "@helpers/markdownToText"; import { markdownToText } from "@helpers/markdown";
import Image from "@components/Image.astro"; import Image from "@components/Image.astro";
const { resourceType, resourceName } = Astro?.params; const { resourceType, resourceName } = Astro?.params;
@@ -35,9 +36,6 @@ export async function getStaticPaths() {
const resource = await memorium.listResource( const resource = await memorium.listResource(
`${resourceType}/${resourceName}.md`, `${resourceType}/${resourceName}.md`,
); );
const ingredients = resource?.content?.recipeIngredient || [];
const instructions = resource?.content?.recipeInstructions || [];
--- ---
<Layout title="Max Richter"> <Layout title="Max Richter">
@@ -49,7 +47,7 @@ const instructions = resource?.content?.recipeInstructions || [];
</a> </a>
<div class="date opacity-50"> <div class="date opacity-50">
{ {
resource?.content.date?.toLocaleString("en-US", { resource?.content.datePublished?.toLocaleString("en-US", {
month: "long", month: "long",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
@@ -57,28 +55,5 @@ const instructions = resource?.content?.recipeInstructions || [];
} }
</div> </div>
</div> </div>
<ResourceDisplay resource={resource} />
<!-- <pre>{JSON.stringify(resource, null, 2)}</pre> -->
<h1 class="text-4xl">{resource?.content?.name}</h1>
<div>
{resource?.content?.image && <Image hash src={{src: memorium.getImageUrl(resource.content.image)}} alt="Cover for {resource?.content?.name}" class="rounded-2xl overflow-hidden" pictureClass="rounded-2xl" />}
</div>
<p>{resource?.content?.description}</p>
<h2 class="text-2xl">Ingredients</h2>
<ul>
{
ingredients.map((ingredient) => (
<li>{markdownToText(ingredient)}</li>
))
}
</ul>
<h2 class="text-2xl">Steps</h2>
<ol>
{
instructions.map((ingredient) => (
<li>{markdownToText(ingredient)}</li>
))
}
</ol>
</Layout> </Layout>

View File

@@ -3,7 +3,7 @@ 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 markdownToText from "@helpers/markdownToText"; import { markdownToText } from "@helpers/markdown";
const { resourceType } = Astro.params; const { resourceType } = Astro.params;
@@ -28,19 +28,25 @@ export async function getStaticPaths() {
}); });
} }
function isValidResource(res) {
if (res?.content?.name) return true;
if (res?.content?.headline) return true;
return false;
}
--- ---
<Layout title="Max Richter"> <Layout title="Max Richter">
{ {
resources.content resources.content
.filter((res) => res && res?.content && res?.content?.name) .filter((res) => res && res?.content)
.map((resource: any) => ( .map((resource: any) => (
<HeroCard <HeroCard
post={{ post={{
collection: "resources/" + resourceType, collection: "resources/" + resourceType,
id: resource.name.replace(/\.md$/, ""), id: resource.name.replace(/\.md$/, ""),
data: { data: {
title: resource.content.name, title: resource.content.name ?? resource.content.headline,
date: resource?.content?.datePublished,
cover: { cover: {
src: memorium.getImageUrl(resource.content.image), src: memorium.getImageUrl(resource.content.image),
}, },
@@ -50,3 +56,4 @@ export async function getStaticPaths() {
)) ))
} }
</Layout> </Layout>
</Layout>

View File

@@ -19,15 +19,14 @@ type Resource = {
// }, // },
// }; // };
// const articles = { const articles = {
// id: "Articles", id: "articles",
// collection, collection,
// body: "Articles saved", data: {
// data: { title: "Articles",
// title: "Articles", icon: "📰",
// icon: "📰", },
// }, };
// };
const recipes = { const recipes = {
id: "recipes", id: "recipes",
@@ -58,4 +57,4 @@ const recipes = {
// }, // },
// }; // };
export const resources: Resource[] = [recipes]; export const resources: Resource[] = [recipes, articles];