Link nostr entities and URLs in chat messages
Image URLs included
This commit is contained in:
parent
19b71a87c0
commit
fc51e33ceb
2 changed files with 214 additions and 54 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import * as nip19 from "@nostr/tools/nip19";
|
||||
import { loadNostrUser, type NostrUser } from "@nostr/gadgets/metadata";
|
||||
import { tokenizeChat } from "$lib/linkify";
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
|
|
@ -9,53 +9,7 @@
|
|||
|
||||
let { content, profiles = {} }: Props = $props();
|
||||
|
||||
type Token =
|
||||
| { type: "text"; value: string }
|
||||
| { type: "mention"; pubkey: string; entity: string; fallback: string };
|
||||
|
||||
const MENTION_RE = /nostr:(npub1[a-z0-9]+|nprofile1[a-z0-9]+)/gi;
|
||||
|
||||
function shortEntity(entity: string): string {
|
||||
const m = entity.match(/^(npub|nprofile)1/);
|
||||
if (!m) return entity;
|
||||
const prefix = m[0];
|
||||
const rest = entity.slice(prefix.length);
|
||||
if (rest.length <= 12) return entity;
|
||||
return `${prefix}${rest.slice(0, 6)}…${rest.slice(-4)}`;
|
||||
}
|
||||
|
||||
const tokens = $derived.by<Token[]>(() => {
|
||||
const out: Token[] = [];
|
||||
let last = 0;
|
||||
for (const m of content.matchAll(MENTION_RE)) {
|
||||
const start = m.index ?? 0;
|
||||
const entity = m[1].toLowerCase();
|
||||
let pubkey: string | null = null;
|
||||
try {
|
||||
const decoded = nip19.decode(entity);
|
||||
if (decoded.type === "npub") pubkey = decoded.data;
|
||||
else if (decoded.type === "nprofile") pubkey = decoded.data.pubkey;
|
||||
} catch {
|
||||
// Invalid bech32, fall through to text
|
||||
}
|
||||
if (start > last)
|
||||
out.push({ type: "text", value: content.slice(last, start) });
|
||||
if (pubkey) {
|
||||
out.push({
|
||||
type: "mention",
|
||||
pubkey,
|
||||
entity,
|
||||
fallback: shortEntity(entity),
|
||||
});
|
||||
} else {
|
||||
out.push({ type: "text", value: m[0] });
|
||||
}
|
||||
last = start + m[0].length;
|
||||
}
|
||||
if (last < content.length)
|
||||
out.push({ type: "text", value: content.slice(last) });
|
||||
return out;
|
||||
});
|
||||
const tokens = $derived(tokenizeChat(content));
|
||||
|
||||
let resolvedUsers = $state<Record<string, NostrUser>>({});
|
||||
|
||||
|
|
@ -72,10 +26,21 @@
|
|||
|
||||
<span class="break-words whitespace-pre-wrap"
|
||||
>{#each tokens as t (t)}{#if t.type === "mention"}{@const u =
|
||||
profiles[t.pubkey] ?? resolvedUsers[t.pubkey]}<a
|
||||
href="https://njump.me/{t.entity}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-brand hover:underline">@{u?.shortName ?? t.fallback}</a
|
||||
>{:else}{t.value}{/if}{/each}</span
|
||||
profiles[t.pubkey] ?? resolvedUsers[t.pubkey]}<a
|
||||
href="https://njump.me/{t.entity}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-brand hover:underline">@{u?.shortName ?? t.fallback}</a
|
||||
>{:else if t.type === "entity"}<a
|
||||
href={t.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-brand hover:underline">{t.label}</a
|
||||
>{:else if t.type === "link"}<a
|
||||
href={t.href}
|
||||
title={t.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-brand hover:underline">{t.label}</a
|
||||
>{:else}{t.value}{/if}{/each}</span
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue