feat: 1-to-1 message render + web data-lake backend
This commit is contained in:
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>
|
||||
Reference in new issue
Block a user