feat: 1-to-1 message render + web data-lake backend

This commit is contained in:
hh committed 2026-05-31 01:45:05 +02:00
1 parent f0afb7ec5b
commit 75425d1bee
110 files changed
+10199 -54

No files matched your search

@@ -0,0 +1,179 @@
<script lang="ts">
import { SvelteSet } from "svelte/reactivity";
import type { EntityView } from "$lib/api/types";
import {
buildEntityTree,
type EntityNode,
type EntityTreeNode,
jumboEmojiCount,
linkHref,
} from "$lib/format/entities";
interface Props {
entities: EntityView[];
own?: boolean;
text: string;
}
let { text, entities, own = false }: Props = $props();
const nodes = $derived(buildEntityTree(text, entities));
const jumbo = $derived(entities.length === 0 ? jumboEmojiCount(text) : 0);
const revealed = new SvelteSet<string>();
function spoilerKey(entity: EntityView): string {
return `${entity.offset}:${entity.length}`;
}
</script>
{#snippet tree(items: EntityTreeNode[])}
{#each items as node, i (i)}
{#if node.kind === "text"}
{node.text}
{:else}
{@render entity(node)}
{/if}
{/each}
{/snippet}
{#snippet entity(node: EntityNode)}
{@const type = node.entity.type}
{#if type === "bold"}
<strong>{@render tree(node.children)}</strong>
{:else if type === "italic"}
<em>{@render tree(node.children)}</em>
{:else if type === "underline"}
<ins>{@render tree(node.children)}</ins>
{:else if type === "strikethrough"}
<del>{@render tree(node.children)}</del>
{:else if type === "spoiler"}
{@const key = spoilerKey(node.entity)}
<button
type="button"
class="spoiler"
class:revealed={revealed.has(key)}
onclick={() => revealed.add(key)}
>
{@render tree(node.children)}
</button>
{:else if type === "code"}
<code class="code" class:own>{@render tree(node.children)}</code>
{:else if type === "pre"}
<pre
class="pre"
class:own
>{#if node.entity.language}<span class="pre-lang">{node.entity.language}</span>{/if}<code>{@render tree(node.children)}</code></pre>
{:else if type === "blockquote"}
<blockquote class="blockquote">{@render tree(node.children)}</blockquote>
{:else if type === "url" || type === "text_link" || type === "email" || type === "phone_number"}
<a
class="link"
href={linkHref(node)}
target="_blank"
rel="noopener noreferrer"
>{@render tree(node.children)}</a
>
{:else if type === "mention" || type === "text_mention" || type === "hashtag" || type === "cashtag" || type === "bot_command"}
<span class="link">{@render tree(node.children)}</span>
{:else}
{@render tree(node.children)}
{/if}
{/snippet}
<span class="EntityText" class:jumbo={jumbo > 0} class:jumbo-1={jumbo === 1}>
{@render tree(nodes)}
</span>
<style lang="scss">
.EntityText {
overflow-wrap: anywhere;
white-space: pre-wrap;
&.jumbo {
font-size: 1.75rem;
line-height: 1.25;
}
&.jumbo-1 {
font-size: 2.5rem;
}
}
.link {
color: var(--color-links);
text-decoration: none;
word-break: break-all;
&:hover {
text-decoration: underline;
}
}
.code,
.pre {
font-family: var(--font-family-monospace, monospace);
font-size: 0.9375rem;
color: var(--color-code);
background-color: var(--color-code-bg);
&.own {
color: var(--color-code-own);
background-color: var(--color-code-own-bg);
}
}
.code {
padding: 0.0625rem 0.25rem;
border-radius: 0.25rem;
}
.pre {
overflow-x: auto;
display: block;
margin: 0.125rem 0;
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
white-space: pre;
}
.pre-lang {
display: block;
margin-bottom: 0.25rem;
font-size: 0.75rem;
opacity: 0.7;
}
.blockquote {
margin: 0.125rem 0;
padding: 0.125rem 0.5rem;
border-left: 0.1875rem solid var(--color-links);
border-radius: 0.25rem;
background-color: var(--color-primary-tint);
}
.spoiler {
cursor: pointer;
padding: 0;
border: none;
font: inherit;
color: transparent;
text-shadow: none;
background-color: var(--color-text);
border-radius: 0.25rem;
transition: color 0.15s, background-color 0.15s;
&.revealed {
color: inherit;
background-color: transparent;
}
}
</style>