diff options
| author | Adam Soltys <[email protected]> | 2026-03-02 19:26:41 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-02 19:26:41 -0800 |
| commit | 90f29dce3d675f850e48ede2edb187e6c53b84da (patch) | |
| tree | 104f3f5a397b94571be6272bc28329f24ee2a893 /src/components | |
| parent | 9e7ea9ad86fcd7ac11d4b5f845ebe558089ddd2d (diff) | |
| download | coinos-ui-90f29dce3d675f850e48ede2edb187e6c53b84da.tar.gz coinos-ui-90f29dce3d675f850e48ede2edb187e6c53b84da.zip | |
parse links
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/ChatMessage.svelte | 87 | ||||
| -rw-r--r-- | src/components/Event.svelte | 2 |
2 files changed, 89 insertions, 0 deletions
diff --git a/src/components/ChatMessage.svelte b/src/components/ChatMessage.svelte new file mode 100644 index 00000000..7283430f --- /dev/null +++ b/src/components/ChatMessage.svelte @@ -0,0 +1,87 @@ +<script lang="ts"> + import { parseContent, isImage, isVideo, isAudio } from "$lib/parseContent"; + + let { content, tags = [] }: { content: string; tags?: any[] } = $props(); + + let parts = $derived(parseContent({ content, tags })); + + const truncUrl = (url: string) => { + const short = url.replace(/^https?:\/\/(www\.)?/, ""); + return short.length > 40 ? short.slice(0, 40) + "..." : short; + }; + + const resolveName = async (pubkey: string): Promise<string> => { + try { + const res = await fetch(`/api/users/${pubkey}`); + if (res.ok) { + const info = await res.json(); + if (!info.anon && info.username) return `@${info.username}`; + } + } catch {} + return `@${pubkey.slice(0, 8)}...`; + }; +</script> + +{#each parts as { type, value }} + {#if type === "text"} + {value} + {:else if type === "newline"} + <br /> + {:else if type === "link"} + {#if isImage(value.url)} + <a href={value.url} target="_blank" rel="noopener noreferrer"> + <img src={value.url} alt="" class="chat-img" /> + </a> + {:else if isVideo(value.url)} + <!-- svelte-ignore a11y_media_has_caption --> + <video src={value.url} controls class="chat-video"></video> + {:else if isAudio(value.url)} + <audio src={value.url} controls> + <a href={value.url}>{truncUrl(value.url)}</a> + </audio> + {:else} + <a href={value.url} target="_blank" rel="noopener noreferrer" class="chat-link">{truncUrl(value.url)}</a> + {/if} + {:else if type === "topic"} + <a href={`/tag/${encodeURIComponent(value)}`} class="chat-topic">#{value}</a> + {:else if type.match(/^nostr:n(profile|pub)$/)} + {#await resolveName(value.pubkey)} + <a href={`/${value.pubkey}`} class="chat-mention">@{value.pubkey.slice(0, 8)}...</a> + {:then name} + <a href={`/${value.pubkey}`} class="chat-mention">{name}</a> + {/await} + {:else if type === "nostr:note"} + <a href={`/e/${value.id}`} class="chat-link">nostr:{value.entity?.slice(0, 16)}...</a> + {:else if type === "nostr:nevent"} + <a href={`/e/${value.id}`} class="chat-link">nostr:{value.entity?.slice(0, 16)}...</a> + {/if} +{/each} + +<style> + .chat-img { + max-width: 100%; + border-radius: 8px; + margin: 4px 0; + } + + .chat-video { + max-width: 100%; + border-radius: 8px; + margin: 4px 0; + } + + .chat-link { + text-decoration: underline; + word-break: break-all; + } + + .chat-topic { + font-weight: bold; + color: var(--accent, #6366f1); + } + + .chat-mention { + font-weight: bold; + color: var(--accent, #6366f1); + } +</style> diff --git a/src/components/Event.svelte b/src/components/Event.svelte index 7a9c75d4..9d25e26e 100644 --- a/src/components/Event.svelte +++ b/src/components/Event.svelte @@ -70,6 +70,8 @@ {value} {:else if type === "link"} <Media {value} {minimal} /> + {:else if type === "topic"} + <a href={`/tag/${encodeURIComponent(value)}`} class="font-bold text-accent">#{value}</a> {:else if type.match(/^nostr:np(rofile|ub)$/)} <a href={`/${value.pubkey}`} class="font-bold">@{names[value.pubkey]}</a> {/if} |
