77 lines
4.4 KiB
Svelte
77 lines
4.4 KiB
Svelte
<script lang="ts">
|
|
import type { ArticleMark, ArticleNode } from '$lib/articles/document';
|
|
import BuildEmbed from '$lib/components/BuildEmbed.svelte';
|
|
|
|
let {
|
|
document,
|
|
builds = {},
|
|
images = {}
|
|
}: {
|
|
document: ArticleNode;
|
|
builds?: Record<string, {
|
|
buildId: string;
|
|
title: string;
|
|
slug: string;
|
|
author: string;
|
|
ownerShared: boolean;
|
|
available: boolean;
|
|
weapons: Array<{ name: string; category: string }>;
|
|
gear: Array<{ name: string; slot: string }>;
|
|
}>;
|
|
images?: Record<string, { url: string; alt: string; caption: string }>;
|
|
} = $props();
|
|
|
|
function link(mark: ArticleMark) {
|
|
return mark.type === 'link' ? mark.attrs?.href ?? '#' : '#';
|
|
}
|
|
|
|
function orderedListStart(node: ArticleNode) {
|
|
const start = Number(node.attrs?.start ?? 1);
|
|
return Number.isInteger(start) && start > 1 ? start : undefined;
|
|
}
|
|
|
|
function orderedListType(node: ArticleNode): 'a' | 'A' | 'i' | 'I' | undefined {
|
|
const type = node.attrs?.type;
|
|
return ['a', 'A', 'i', 'I'].includes(String(type)) ? type as 'a' | 'A' | 'i' | 'I' : undefined;
|
|
}
|
|
</script>
|
|
|
|
{#snippet renderText(text: string, marks: ArticleMark[], index = 0)}
|
|
{#if index >= marks.length}{text}{:else}
|
|
{@const mark = marks[index]}
|
|
{#if mark.type === 'bold'}<strong>{@render renderText(text, marks, index + 1)}</strong>
|
|
{:else if mark.type === 'italic'}<em>{@render renderText(text, marks, index + 1)}</em>
|
|
{:else if mark.type === 'code'}<code>{@render renderText(text, marks, index + 1)}</code>
|
|
{:else}<a href={link(mark)} title={mark.attrs?.title} rel="nofollow ugc noopener noreferrer">{@render renderText(text, marks, index + 1)}</a>{/if}
|
|
{/if}
|
|
{/snippet}
|
|
|
|
{#snippet children(node: ArticleNode)}
|
|
{#each node.content ?? [] as child}{@render renderNode(child)}{/each}
|
|
{/snippet}
|
|
|
|
{#snippet renderNode(node: ArticleNode)}
|
|
{#if node.type === 'text'}{@render renderText(node.text ?? '', node.marks ?? [])}
|
|
{:else if node.type === 'paragraph'}<p>{@render children(node)}</p>
|
|
{:else if node.type === 'heading' && Number(node.attrs?.level) === 2}<h2>{@render children(node)}</h2>
|
|
{:else if node.type === 'heading'}<h3>{@render children(node)}</h3>
|
|
{:else if node.type === 'bulletList'}<ul>{@render children(node)}</ul>
|
|
{:else if node.type === 'orderedList'}<ol start={orderedListStart(node)} type={orderedListType(node)}>{@render children(node)}</ol>
|
|
{:else if node.type === 'listItem'}<li>{@render children(node)}</li>
|
|
{:else if node.type === 'blockquote'}<blockquote>{@render children(node)}</blockquote>
|
|
{:else if node.type === 'horizontalRule'}<hr/>
|
|
{:else if node.type === 'hardBreak'}<br/>
|
|
{:else if node.type === 'buildEmbed' && builds[String(node.attrs?.embedKey ?? '')]}
|
|
<BuildEmbed embed={builds[String(node.attrs?.embedKey ?? '')]}/>
|
|
{:else if node.type === 'articleImage' && images[String(node.attrs?.imageId ?? '')]}
|
|
{@const image = images[String(node.attrs?.imageId ?? '')]}
|
|
<figure><img src={image.url} alt={image.alt}/>{#if image.caption}<figcaption>{image.caption}</figcaption>{/if}</figure>
|
|
{/if}
|
|
{/snippet}
|
|
|
|
<div class="article-content">{@render children(document)}</div>
|
|
|
|
<style>
|
|
.article-content{max-width:76ch;color:var(--shd-text);font-family:var(--shd-font-editorial);font-size:clamp(1.02rem,1.4vw,1.15rem);line-height:1.72}.article-content :global(p){margin:0 0 1.25em}.article-content :global(h2),.article-content :global(h3){font-family:var(--shd-font);line-height:1.05}.article-content :global(h2){margin:2.1em 0 .6em;font-size:clamp(1.8rem,4vw,2.5rem)}.article-content :global(h3){margin:1.8em 0 .55em;font-size:1.4rem}.article-content :global(ul),.article-content :global(ol){display:grid;gap:7px;margin:0 0 1.35em;padding-left:1.4em}.article-content :global(blockquote){margin:2em 0;padding:18px 22px;border:1px solid var(--shd-border-strong);background:#0d1011;font-size:1.16em;font-style:italic}.article-content :global(hr){margin:38px 0;border:0;border-top:1px solid var(--shd-border-strong)}.article-content :global(a){color:var(--shd-orange);text-decoration:underline;text-underline-offset:3px}.article-content :global(code){padding:1px 4px;background:#080a0b;color:var(--shd-info);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.88em}.article-content :global(figure){margin:34px 0}.article-content :global(figure img){max-width:100%;height:auto;display:block}.article-content :global(figcaption){padding:9px 0;color:var(--shd-text-muted);font-family:var(--shd-font);font-size:var(--shd-type-supporting);line-height:1.45}
|
|
</style>
|