Add svelte markdown rendering

main
trivernis 3 months ago
parent 20edd1b562
commit 1c9d6bed24
Signed by: Trivernis
GPG Key ID: 7E6D18B61C8D2F4B

9
package-lock.json generated

@ -8,6 +8,7 @@
"name": "website-frontend",
"version": "0.0.1",
"dependencies": {
"highlight.js": "^11.10.0",
"marked": "^13.0.2",
"moment": "^2.30.1",
"qs": "^6.12.2",
@ -1028,6 +1029,14 @@
"node": ">= 0.4"
}
},
"node_modules/highlight.js": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz",
"integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/immutable": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",

@ -28,6 +28,7 @@
},
"type": "module",
"dependencies": {
"highlight.js": "^11.10.0",
"marked": "^13.0.2",
"moment": "^2.30.1",
"qs": "^6.12.2",

@ -1,19 +1,19 @@
<script lang="ts">
import type { Snippet } from "svelte";
import type { Snippet } from "svelte";
type Props = {
type Props = {
color?: string;
title?: string;
margin?: "slim" | "medium" | "wide";
children: Snippet;
};
};
const {
const {
color = "primary",
title,
margin = "medium",
children,
}: Props = $props();
}: Props = $props();
</script>
<div
@ -43,6 +43,7 @@ const {
var(--box-color),
var(--color-foreground) 90%
);
--color-context: var(--box-border-color);
border: 5px solid var(--box-border-color);
border-radius: 10px;

@ -0,0 +1,27 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
depth: number;
};
const { children, depth }: Props = $props();
if (depth < 0 || depth > 6) {
throw new Error("depth must be 0-6");
}
</script>
<svelte:element this={`h${depth}`} class="heading">
{@render children()}
</svelte:element>
<style lang="scss">
@layer component {
.heading {
font-family: var(--font-primary);
margin: 0.2em 0;
text-decoration: underline 0.2em var(--color-context);
}
}
</style>

@ -0,0 +1,31 @@
<script lang="ts">
import hljs from "highlight.js";
import "highlight.js/styles/night-owl.css";
type Props = {
language?: string;
code: string;
};
const { language, code }: Props = $props();
const html: string = $derived.by(() => {
if (language) {
return hljs.highlight(code, { language }).value;
}
return hljs.highlightAuto(code).value;
});
</script>
<pre class="code">
{@html html}
</pre>
<style lang="scss">
@layer component {
.code {
margin: 0;
font-family: var(--font-monospace);
}
}
</style>

@ -0,0 +1,30 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
ordered: boolean;
start: string;
};
const { children, ordered, start }: Props = $props();
</script>
{#if ordered}
<ol start={Number(start)} class="list ordered">
{@render children()}
</ol>
{:else}
<ul class="list unordered">
{@render children()}
</ul>
{/if}
<style lang="scss">
@layer component {
.list {
margin: 0;
font-family: var(--font-readable);
}
}
</style>

@ -0,0 +1,13 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
};
const { children }: Props = $props();
</script>
<li>
{@render children()}
</li>

@ -0,0 +1,22 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
};
const { children }: Props = $props();
</script>
<p class="paragraph">
{@render children()}
</p>
<style lang="scss">
@layer component {
.paragraph {
margin: 0;
font-family: var(--font-readable);
}
}
</style>

@ -0,0 +1,10 @@
<div class="space"></div>
<style lang="scss">
@layer component {
.space {
display: block;
height: 1.2em;
}
}
</style>

@ -1,5 +1,11 @@
<script lang="ts">
import { marked } from "marked";
import Heading from "../atoms/Heading.svelte";
import Paragraph from "../atoms/Paragraph.svelte";
import Space from "../atoms/Space.svelte";
import List from "../atoms/List.svelte";
import ListItem from "../atoms/ListItem.svelte";
import HighlightedCode from "../atoms/HighlightedCode.svelte";
type Props = {
markdown: string;
@ -7,21 +13,58 @@
const { markdown }: Props = $props();
const markdownHtml = marked(markdown);
const markdownTokens = marked.lexer(markdown);
</script>
{#snippet markdownToken(token)}
{#if token.type === "text"}
{token.text}
{:else if token.type === "heading"}
<Heading depth={token.depth}>
{token.text}
</Heading>
{:else if token.type === "paragraph"}
<Paragraph>
{token.text}
</Paragraph>
{:else if token.type === "code"}
<HighlightedCode language={token.language} code={token.text} />
{:else if token.type === "space"}
<Space />
{:else if token.type === "list"}
<List ordered={token.ordered} start={token.start}>
{#each token.items as item}
{@render markdownToken(item)}
{/each}
</List>
{:else if token.type === "list_item"}
<ListItem>
{#each token.tokens as itemToken}
{@render markdownToken(itemToken)}
{/each}
</ListItem>
{:else}
<pre>{JSON.stringify(token, null, 2)}</pre>
{/if}
{/snippet}
<div class="markdown">
{@html markdownHtml}
{#each markdownTokens as token}
{@render markdownToken(token)}
{/each}
</div>
<style lang="scss">
@layer component {
.markdown {
font-family: var(--font-readable);
hyphens: auto;
text-align: justify;
cursor: text;
font-size: 15pt;
}
:global(.heading) {
font-size: 20pt;
}
}
</style>

@ -45,9 +45,11 @@
--color-yellow: #f1fa8c;
--color-image-frame: #fff;
--color-image-frame-text: #204;
--color-context: var(--color-primary);
--font-primary: Manifold, Lexend, Arial, Helvetica, sans-serif;
--font-readable: "3270", Lexend, Arial, Helvetica, sans-serif;
--font-monospace: Fira Code, monospace;
--font-page-title: GradientVector, Manifold, Lexend, Arial, Helvetica, sans-serif;
}

@ -48,8 +48,3 @@
{/snippet}
</ContainerDualColumn>
</ContainerMedium>
<style lang="scss">
@layer page {
}
</style>

Loading…
Cancel
Save