Add missing markdown rendering

main
trivernis 3 months ago
parent a00ae0b6f8
commit 4d10a4db8a
Signed by: Trivernis
GPG Key ID: 7E6D18B61C8D2F4B

9
package-lock.json generated

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"marked": "^13.0.2", "marked": "^13.0.2",
"marked-emoji": "^1.4.1",
"moment": "^2.30.1", "moment": "^2.30.1",
"qs": "^6.12.2", "qs": "^6.12.2",
"sanitize.css": "^13.0.0", "sanitize.css": "^13.0.0",
@ -1234,6 +1235,14 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/marked-emoji": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.1.tgz",
"integrity": "sha512-3xHWQn8XD1LyhMpHxWpHTDWBZ9bpXLlW8JIqvyXTO6he7okKIB/W9fD/3fTg0DQuZlSQvPZ6Ub5hN6Rnmn7j9g==",
"peerDependencies": {
"marked": ">=4 <14"
}
},
"node_modules/mdsvex": { "node_modules/mdsvex": {
"version": "0.11.2", "version": "0.11.2",
"resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.11.2.tgz", "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.11.2.tgz",

@ -30,6 +30,7 @@
"dependencies": { "dependencies": {
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"marked": "^13.0.2", "marked": "^13.0.2",
"marked-emoji": "^1.4.1",
"moment": "^2.30.1", "moment": "^2.30.1",
"qs": "^6.12.2", "qs": "^6.12.2",
"sanitize.css": "^13.0.0", "sanitize.css": "^13.0.0",

@ -0,0 +1,30 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
};
const { children }: Props = $props();
</script>
<blockquote class="blockquote">
{@render children()}
</blockquote>
<style lang="scss">
@layer component {
.blockquote {
margin: 0;
padding-left: 0.5em;
border-left: 5px solid var(--color-context);
border-radius: 5px;
background-color: var(--color-background-light);
font-family: var(--font-readable);
:global(> .blockquote) {
box-shadow: 0 -5px 5px var(--color-background);
}
}
}
</style>

@ -34,10 +34,12 @@
</div> </div>
<style lang="scss"> <style lang="scss">
@import "$lib/styles/mixins.scss";
@layer component { @layer component {
.box { .box {
position: relative; position: relative;
--box-border-color: color-mix(in srgb, var(--box-color), #fff 65%); @include lighten-color(--box-border-color, var(--box-color));
--box-text-color: color-mix( --box-text-color: color-mix(
in srgb, in srgb,
var(--box-color), var(--box-color),

@ -5,15 +5,28 @@
type Props = { type Props = {
language?: string; language?: string;
code: string; code: string;
lineNumbers?: boolean;
}; };
const { language, code }: Props = $props(); const { language, code, lineNumbers = true }: Props = $props();
function addLineNumbers(rawHtml: string) {
if (!lineNumbers) {
return rawHtml;
}
return rawHtml
.split("\n")
.map((line, index) => {
return `<span class="line-number">${index + 1}</span>${line}`;
})
.join("\n");
}
const html: string = $derived.by(() => { const html: string = $derived.by(() => {
if (language) { if (language) {
return hljs.highlight(code, { language }).value; return addLineNumbers(hljs.highlight(code, { language }).value);
} }
return hljs.highlightAuto(code).value; return addLineNumbers(hljs.highlightAuto(code).value);
}); });
</script> </script>
@ -26,6 +39,16 @@
.code { .code {
margin: 0; margin: 0;
font-family: var(--font-monospace); font-family: var(--font-monospace);
background-color: var(--color-background-light);
padding: 0.25em;
border-radius: 5px;
user-select: contain;
:global(.line-number) {
color: var(--color-foreground-hint);
margin-right: 0.75em;
user-select: none;
}
} }
} }
</style> </style>

@ -0,0 +1,19 @@
<script lang="ts">
type Props = {
src: string;
title?: string;
text?: string;
};
const { src, title, text }: Props = $props();
</script>
<img class="image" {src} {title} alt={text} />
<style lang="scss">
@layer component {
.image {
max-width: 35%;
}
}
</style>

@ -0,0 +1,35 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
header: Snippet;
rows: Snippet;
};
const { header, rows }: Props = $props();
</script>
<table class="table">
<thead>
{@render header()}
</thead>
<tbody>
{@render rows()}
</tbody>
</table>
<style lang="scss">
@layer component {
.table {
border-collapse: true;
margin: 1em 0;
thead {
font-family: var(--font-heading);
}
tbody {
font-family: var(--font-readable);
}
}
}
</style>

@ -0,0 +1,27 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
};
const { children }: Props = $props();
</script>
<td class="table-cell">
{@render children()}
</td>
<style lang="scss">
@layer component {
.table-cell {
padding: 0.25em;
border-top: 2px solid var(--color-context);
border-left: 2px solid var(--color-context);
&:first-child {
border-left: none;
}
}
}
</style>

@ -0,0 +1,26 @@
<script lang="ts">
import type { Snippet } from "svelte";
type Props = {
children: Snippet;
};
const { children }: Props = $props();
</script>
<th class="table-header">
{@render children()}
</th>
<style lang="scss">
@layer component {
.table-header {
padding: 0.25em;
border-left: 2px solid var(--color-context);
&:first-child {
border-left: none;
}
}
}
</style>

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

@ -7,6 +7,15 @@
import ListItem from "../atoms/ListItem.svelte"; import ListItem from "../atoms/ListItem.svelte";
import HighlightedCode from "../atoms/HighlightedCode.svelte"; import HighlightedCode from "../atoms/HighlightedCode.svelte";
import HorizontalRuler from "../atoms/HorizontalRuler.svelte"; import HorizontalRuler from "../atoms/HorizontalRuler.svelte";
import MarkdownInline from "./MarkdownInline.svelte";
import Blockquote from "../atoms/Blockquote.svelte";
import Table from "../atoms/Table.svelte";
import TableHeader from "../atoms/TableHeader.svelte";
import TableRow from "../atoms/TableRow.svelte";
import TableCell from "../atoms/TableCell.svelte";
import Image from "../atoms/Image.svelte";
import { markedEmoji } from "marked-emoji";
import emojis from "$lib/emojis";
type Props = { type Props = {
markdown: string; markdown: string;
@ -14,37 +23,63 @@
const { markdown }: Props = $props(); const { markdown }: Props = $props();
marked.use(markedEmoji({ emojis }));
const markdownTokens = marked.lexer(markdown); const markdownTokens = marked.lexer(markdown);
</script> </script>
{#snippet markdownToken(token)} {#snippet markdownToken(token)}
{#if token.type === "text" && !token.tokens} {#if ["text", "strong", "link", "em", "del", "codespan", "emoji"].includes(token.type)}
{token.text} <MarkdownInline markdownToken={token} />
{:else if token.type === "heading"} {:else if token.type === "heading"}
<Heading depth={token.depth}> <Heading depth={token.depth}>
{token.text} {@render tokenValue(token)}
</Heading> </Heading>
{:else if token.type === "paragraph"} {:else if token.type === "paragraph"}
<Paragraph> <Paragraph>
{token.text} {@render tokenValue(token)}
</Paragraph> </Paragraph>
{:else if token.type === "blockquote"}
<Blockquote>
{@render tokenValue(token)}
</Blockquote>
{:else if token.type === "code"} {:else if token.type === "code"}
<HighlightedCode language={token.language} code={token.text} /> <HighlightedCode language={token.language} code={token.text} />
{:else if token.type === "space"} {:else if token.type === "space"}
<Space /> <Space />
{:else if token.type === "hr"} {:else if token.type === "hr"}
<HorizontalRuler /> <HorizontalRuler />
{:else if token.type === "image"}
<Image src={token.href} text={token.text} title={token.title} />
{:else if token.type === "list"} {:else if token.type === "list"}
<List ordered={token.ordered} start={token.start}> <List ordered={token.ordered} start={token.start}>
{#each token.items as item} {#each token.items as item}
{@render markdownToken(item)} {@render markdownToken(item)}
{/each} {/each}
</List> </List>
{:else if token.type === "table"}
<Table>
{#snippet header()}
{#each token.header as header}
<TableHeader>
{@render tokenValue(header)}
</TableHeader>
{/each}
{/snippet}
{#snippet rows()}
{#each token.rows as row}
<TableRow>
{#each row as cell}
<TableCell>
{@render tokenValue(cell)}
</TableCell>
{/each}
</TableRow>
{/each}
{/snippet}
</Table>
{:else if token.type === "list_item"} {:else if token.type === "list_item"}
<ListItem> <ListItem>
{#each token.tokens as itemToken} {@render tokenValue(token)}
{@render markdownToken(itemToken)}
{/each}
</ListItem> </ListItem>
{:else} {:else}
<h4 style="color: var(--color-red)">This needs to be rendered</h4> <h4 style="color: var(--color-red)">This needs to be rendered</h4>
@ -52,6 +87,16 @@
{/if} {/if}
{/snippet} {/snippet}
{#snippet tokenValue(token)}
{#if token.tokens}
{#each token.tokens as childToken}
{@render markdownToken(childToken)}
{/each}
{:else if token.text}
{token.text}
{:else}{@html "<!-- This token does not hold value -->"}{/if}
{/snippet}
<div class="markdown"> <div class="markdown">
{#each markdownTokens as token} {#each markdownTokens as token}
{@render markdownToken(token)} {@render markdownToken(token)}

@ -0,0 +1,56 @@
<script lang="ts">
import type { Token } from "marked";
import HighlightedCode from "../atoms/HighlightedCode.svelte";
type Props = {
markdownToken: Token;
};
const { markdownToken }: Props = $props();
</script>
{#snippet inlineMarkdown(token)}
{#if token.type === "text" || token.type === "escape"}
{@render tokenValue(token)}
{:else if token.type === "strong"}
<strong>{@render tokenValue(token)}</strong>
{:else if token.type === "link"}
<a href={token.href} title={token.title}>{@render tokenValue(token)}</a>
{:else if token.type === "em"}
<em>{@render tokenValue(token)}</em>
{:else if token.type === "del"}
<del>{@render tokenValue(token)}</del>
{:else if token.type === "codespan"}
<code>{@render tokenValue(token)}</code>
{:else if token.type === "emoji"}
<span class="emoji">{token.emoji}</span>
{:else}
<h4 style="color: var(--color-red)">This needs to be rendered</h4>
<HighlightedCode code={JSON.stringify(token, null, 2)} />
{/if}
{/snippet}
{#snippet tokenValue(token)}
{#if token.tokens}
{#each token.tokens as childToken}
{@render inlineMarkdown(childToken)}
{/each}
{:else}
{@html token.text}
{/if}
{/snippet}
{@render inlineMarkdown(markdownToken)}
<style lang="scss">
@import "$lib/styles/mixins.scss";
@layer component {
.emoji {
font-family: var(--font-emoji);
font-weight: bold;
@include lighten-color(color, var(--color-context));
text-shadow: 0 0 5px var(--color-context);
}
}
</style>

@ -30,12 +30,21 @@
src: local(""), url("/fonts/3270/3270SemiCondensed-Regular.otf"), url("/fonts/3270/3270SemiCondensed-Regular.ttf"); src: local(""), url("/fonts/3270/3270SemiCondensed-Regular.otf"), url("/fonts/3270/3270SemiCondensed-Regular.ttf");
} }
@font-face {
font-family: "Symbola";
font-style: normal;
font-weight: 500;
src: local(""), url("/fonts/Symbola/Symbola.otf");
}
:root { :root {
--color-background: #2B1C3D; --color-background: #2B1C3D;
--color-background-light: #3A2A4D; --color-background-light: #3A2A4D;
--color-foreground: #ffffff; --color-foreground: #ffffff;
--color-foreground-tint: #ffd1f8; --color-foreground-tint: #ffd1f8;
--color-foreground-dim: #acbacd; --color-foreground-dim: #acbacd;
--color-foreground-hint: #886C9C;
--color-primary: #ff79c6; --color-primary: #ff79c6;
--color-purple: #bd93f9; --color-purple: #bd93f9;
--color-cyan: #8be9fd; --color-cyan: #8be9fd;
@ -50,6 +59,7 @@
--font-primary: Manifold, Lexend, Arial, Helvetica, sans-serif; --font-primary: Manifold, Lexend, Arial, Helvetica, sans-serif;
--font-readable: "3270", Lexend, Arial, Helvetica, sans-serif; --font-readable: "3270", Lexend, Arial, Helvetica, sans-serif;
--font-monospace: Fira Code, monospace; --font-monospace: Fira Code, monospace;
--font-emoji: Symbola, Twemoji, "Twemoji Mozilla", Noto-Emoji, sans-serif;
--font-page-title: GradientVector, Manifold, Lexend, Arial, Helvetica, sans-serif; --font-page-title: GradientVector, Manifold, Lexend, Arial, Helvetica, sans-serif;
} }

File diff suppressed because it is too large Load Diff

@ -15,4 +15,8 @@
@media (max-aspect-ratio: 10/16) { @media (max-aspect-ratio: 10/16) {
@content; @content;
} }
}
@mixin lighten-color($property, $color, $amount: 65%) {
#{$property}: color-mix(in srgb, $color, #fff $amount);
} }

Binary file not shown.
Loading…
Cancel
Save