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": {
"highlight.js": "^11.10.0",
"marked": "^13.0.2",
"marked-emoji": "^1.4.1",
"moment": "^2.30.1",
"qs": "^6.12.2",
"sanitize.css": "^13.0.0",
@ -1234,6 +1235,14 @@
"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": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.11.2.tgz",

@ -30,6 +30,7 @@
"dependencies": {
"highlight.js": "^11.10.0",
"marked": "^13.0.2",
"marked-emoji": "^1.4.1",
"moment": "^2.30.1",
"qs": "^6.12.2",
"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>
<style lang="scss">
@import "$lib/styles/mixins.scss";
@layer component {
.box {
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(
in srgb,
var(--box-color),

@ -5,15 +5,28 @@
type Props = {
language?: 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(() => {
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>
@ -26,6 +39,16 @@
.code {
margin: 0;
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>

@ -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 HighlightedCode from "../atoms/HighlightedCode.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 = {
markdown: string;
@ -14,37 +23,63 @@
const { markdown }: Props = $props();
marked.use(markedEmoji({ emojis }));
const markdownTokens = marked.lexer(markdown);
</script>
{#snippet markdownToken(token)}
{#if token.type === "text" && !token.tokens}
{token.text}
{#if ["text", "strong", "link", "em", "del", "codespan", "emoji"].includes(token.type)}
<MarkdownInline markdownToken={token} />
{:else if token.type === "heading"}
<Heading depth={token.depth}>
{token.text}
{@render tokenValue(token)}
</Heading>
{:else if token.type === "paragraph"}
<Paragraph>
{token.text}
{@render tokenValue(token)}
</Paragraph>
{:else if token.type === "blockquote"}
<Blockquote>
{@render tokenValue(token)}
</Blockquote>
{:else if token.type === "code"}
<HighlightedCode language={token.language} code={token.text} />
{:else if token.type === "space"}
<Space />
{:else if token.type === "hr"}
<HorizontalRuler />
{:else if token.type === "image"}
<Image src={token.href} text={token.text} title={token.title} />
{: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 === "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"}
<ListItem>
{#each token.tokens as itemToken}
{@render markdownToken(itemToken)}
{/each}
{@render tokenValue(token)}
</ListItem>
{:else}
<h4 style="color: var(--color-red)">This needs to be rendered</h4>
@ -52,6 +87,16 @@
{/if}
{/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">
{#each markdownTokens as 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");
}
@font-face {
font-family: "Symbola";
font-style: normal;
font-weight: 500;
src: local(""), url("/fonts/Symbola/Symbola.otf");
}
:root {
--color-background: #2B1C3D;
--color-background-light: #3A2A4D;
--color-foreground: #ffffff;
--color-foreground-tint: #ffd1f8;
--color-foreground-dim: #acbacd;
--color-foreground-hint: #886C9C;
--color-primary: #ff79c6;
--color-purple: #bd93f9;
--color-cyan: #8be9fd;
@ -50,6 +59,7 @@
--font-primary: Manifold, Lexend, Arial, Helvetica, sans-serif;
--font-readable: "3270", Lexend, Arial, Helvetica, sans-serif;
--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;
}

File diff suppressed because it is too large Load Diff

@ -16,3 +16,7 @@
@content;
}
}
@mixin lighten-color($property, $color, $amount: 65%) {
#{$property}: color-mix(in srgb, $color, #fff $amount);
}

Binary file not shown.
Loading…
Cancel
Save