Add blog post rendering

main
trivernis 5 months ago
parent 1e11512b21
commit 5f17c0451c
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: 7E6D18B61C8D2F4B

60
package-lock.json generated

@ -10,7 +10,8 @@
"dependencies": {
"moment": "^2.30.1",
"qs": "^6.12.2",
"sanitize.css": "^13.0.0"
"sanitize.css": "^13.0.0",
"svelte-markdown": "^0.4.1"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
@ -31,7 +32,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@ -429,7 +429,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -443,7 +442,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -452,7 +450,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -460,14 +457,12 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@ -964,8 +959,12 @@
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/marked": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg=="
},
"node_modules/@types/pug": {
"version": "2.0.10",
@ -995,7 +994,6 @@
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@ -1044,7 +1042,6 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"dependencies": {
"dequal": "^2.0.3"
}
@ -1053,7 +1050,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
"integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
"dev": true,
"dependencies": {
"dequal": "^2.0.3"
}
@ -1165,7 +1161,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1",
@ -1231,7 +1226,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"dev": true,
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
@ -1286,7 +1280,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -1391,7 +1384,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
@ -1700,7 +1692,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"dev": true,
"dependencies": {
"@types/estree": "*"
}
@ -1741,8 +1732,7 @@
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
"node_modules/lru-cache": {
"version": "10.3.1",
@ -1757,16 +1747,25 @@
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/marked": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
"integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"dev": true
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
"node_modules/mdsvex": {
"version": "0.11.2",
@ -1963,7 +1962,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^3.0.0",
@ -2273,7 +2271,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -2402,7 +2399,6 @@
"version": "4.2.18",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz",
"integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
@ -2455,6 +2451,18 @@
"svelte": "^3.19.0 || ^4.0.0"
}
},
"node_modules/svelte-markdown": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.4.1.tgz",
"integrity": "sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==",
"dependencies": {
"@types/marked": "^5.0.1",
"marked": "^5.1.2"
},
"peerDependencies": {
"svelte": "^4.0.0"
}
},
"node_modules/svelte-preprocess": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",

@ -27,6 +27,7 @@
"dependencies": {
"moment": "^2.30.1",
"qs": "^6.12.2",
"sanitize.css": "^13.0.0"
"sanitize.css": "^13.0.0",
"svelte-markdown": "^0.4.1"
}
}

@ -0,0 +1,7 @@
<script lang="ts">
import SvelteMarkdown from "svelte-markdown";
export let markdown: string;
</script>
<SvelteMarkdown source={markdown}/>

@ -1,34 +1,32 @@
<script lang="ts">
import type { ImageMetadata } from "$lib";
import "$lib/vars.scss";
import { onMount } from "svelte";
export let sources: {
src: string;
type: string;
}[];
export let imageData: ImageMetadata;
export let caption: string | undefined = undefined;
export let alt: string | undefined = undefined;
export let border = true;
let alt: string | undefined = undefined;
onMount(() => {
alt =
alt ?? caption ?? "Unfortunately theres no description for this image.";
imageData.altText ??
imageData.caption ??
"Unfortunately theres no description for this image.";
});
</script>
<div class="image" class:border={border}>
<div class="image">
<figure>
<picture>
{#each sources as source}
<source type={source.type} srcset={source.src} />
{#each imageData.formats as format}
<source media={format.width? `(min-width: ${format.width * 2}px)` : "(min-width: 0px)"} type={format.mime} srcset={format.url} />
{/each}
<img src={sources[0]?.src} {alt} class:rounded={!border} />
<img src={imageData.formats[0]?.url} {alt} />
</picture>
{#if caption}
{#if imageData.caption}
<figcaption>
{caption}
{imageData.caption}
</figcaption>
{/if}
</figure>
@ -37,34 +35,23 @@
<style lang="scss">
@layer component {
.image {
&.border {
border-radius: 10px;
padding: 1em;
background-color: var(--color-image-frame);
figure {
margin: 0.5em;
}
}
figure {
max-width: 100%;
display: flex;
flex-direction: column;
margin: auto;
margin: 0.5em;
picture {
img {
width: 100%;
height: 100%;
object-fit: cover;
&.rounded {
border-radius: 10px;
}
}
}
figcaption {
padding-top: 0.75em;

@ -9,7 +9,7 @@
{#if imageData}
<picture>
{#each imageData.formats as format}
<source media={`(min-width: ${format.width * 2}px)`} type={format.mime} srcset={format.url} />
<source media={format.width? `(min-width: ${format.width * 2}px)` : "(min-width: 0px)"} type={format.mime} srcset={format.url} />
{/each}
<img src={imageData.formats[0]?.url} alt={imageData.altText} />
</picture>

@ -0,0 +1,42 @@
<script lang="ts">
import type { BlogPostContentEntry } from "$lib/cms/blog";
import "$lib/vars.scss";
import Box from "../atoms/Box.svelte";
import Markdown from "../atoms/Markdown.svelte";
import Image from "../molecules/Image.svelte";
export let content: BlogPostContentEntry;
</script>
<div class="post-content">
{#if content.__component === "content.text-markdown"}
{#if content.type === "paragraph"}
<Markdown markdown={content.value} />
{:else if content.type === "infobox"}
<Box color="cyan">
<b class="info-label">Info</b>
<Markdown markdown={content.value} />
</Box>
{/if}
{:else if content.__component === "content.image"}
{@const imageData = content.value.data.attributes}
<Image
imageData={{
caption: imageData.caption,
altText: imageData.alternativeText,
formats: Object.values(imageData.formats),
}}
/>
{/if}
</div>
<style lang="scss">
@layer component {
.post-content {
margin: 1em 0;
}
.info-label {
color: var(--color-cyan);
}
}
</style>

@ -25,10 +25,22 @@ export type BlogPostTeaser = {
export type BlogPost = BlogPostTeaser & {
attributes: {
content: any;
content: BlogPostContentEntry[];
};
};
type BlogPostContentTemplate<S, T> = {
id: number;
__component: S;
} & T;
export type BlogPostContentEntry =
| BlogPostContentTemplate<
"content.text-markdown",
{ value: string; type: "paragraph" | "infobox" }
>
| BlogPostContentTemplate<"content.image", { value: { data: StrapiImage } }>;
export async function getPosts(locale = "all"): Promise<BlogPostTeaser[]> {
return await fetchApi<BlogPostTeaser[]>({
endpoint: "blog-posts",
@ -72,6 +84,9 @@ export async function getPost(slug: string): Promise<BlogPost> {
teaserImage: {
populate: "*",
},
content: {
populate: "*",
},
},
filters: {
slug: {

@ -15,15 +15,15 @@ export type ImageMetadata = {
altText?: string;
formats: {
ext: string;
ext?: string;
url: string;
hash: string;
hash?: string;
mime: string;
name: string;
path?: string;
size: number;
width: number;
height: number;
sizeInBytes: number;
size?: number;
width?: number;
height?: number;
sizeInBytes?: number;
}[];
};

@ -40,7 +40,7 @@
}
@include landscape {
margin: 0 30%;
margin: 0 25%;
}
}

@ -1,7 +1,7 @@
<script>
import "$lib/vars.scss";
import Box from "../components/atoms/Box.svelte";
import Image from "../components/molecules/Image.svelte";
import Thumbnail from "../components/molecules/Thumbnail.svelte";
</script>
<h1>Welcome to my website</h1>
@ -9,21 +9,24 @@
<div class="flex-row text-and-image">
<div class="text">
<p>
Heyyy. I'm a software developer and tinkerer from
Germany. I do a lot of stuff so this website is an attempt in providing
an overview.
Heyyy. I'm a software developer and tinkerer from Germany. I do a lot of
stuff so this website is an attempt in providing an overview.
</p>
</div>
<div class="image">
<Image
border={false}
sources={[
<Thumbnail
imageData={{
altText:
"A picture of Ferris, the Rust mascot. An orange crab plushie.",
formats: [
{
src: "/images/profile.jpg",
type: "image/jpeg",
name: "original",
mime: "image/jpeg",
url: "/images/profile.jpg",
},
]}
></Image>
],
}}
/>
</div>
</div>
</Box>
@ -33,7 +36,7 @@
.text {
display: box;
width: 63%;
margin-right: 3%
margin-right: 3%;
}
.image {

@ -1,11 +1,27 @@
<script lang="ts">
import Error from "../../../components/molecules/Error.svelte";
import Box from "../../../components/atoms/Box.svelte";
import Error from "../../../components/molecules/Error.svelte";
import BlogPostContent from "../../../components/organisms/BlogPostContent.svelte";
import type { PageData } from "./$types";
export let data: PageData;
</script>
<code>{JSON.stringify(data.post, null, 2)}</code>
{#if data.post}
<Box>
{@const post = data.post}
{@const author = data.post.attributes.author.data?.attributes}
<h1>{post.attributes.title}</h1>
<h3>by {author?.name}</h3>
{#each post.attributes.content as contentEntry}
<BlogPostContent content={contentEntry}/>
{/each}
</Box>
{:else}
<Error error={{message: "Could not find the post"}} />
{/if}
{#if data.error}
<Error error={data.error} />

Loading…
Cancel
Save