spa: kommentar-author klickbar (njump) + external-client-links am post
Zwei Erweiterungen, die Community-Interaktion an Nostr-native Clients auslagern statt in der SPA nachzubauen: 1. ReplyItem-Header ist jetzt ein <a href=https://njump.me/<npub> target=_blank>. Klick auf Avatar/Name öffnet das vollständige Profil des Kommentar-Authors mit allen Events. 2. Neue ExternalClientLinks.svelte zwischen Reactions und Composer: dezente Box mit "In Nostr-Client öffnen" — drei Links (Habla, Yakihonne, njump) über naddr, damit Leser Thread-Replies, Reactions, Teilen dort nutzen können, wo die volle Nostr-Social- Layer läuft. Nostr-Helper erweitert: - buildNpub(hex) — npub1…-Bech32-Encoding - buildNjumpProfileUrl(hex) — njump.me/<npub> - externalClientLinks({pubkey, kind, identifier}) — Liste der drei etablierten Langform-Viewer mit naddr1…-URLs. npm run check: 0 errors, 611 files. Deploy live. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eb400a8a6a
commit
3ad1a72d84
|
|
@ -0,0 +1,56 @@
|
|||
<script lang="ts">
|
||||
import { externalClientLinks } from '$lib/nostr/naddr';
|
||||
import { AUTHOR_PUBKEY_HEX } from '$lib/nostr/config';
|
||||
|
||||
interface Props {
|
||||
dtag: string;
|
||||
}
|
||||
let { dtag }: Props = $props();
|
||||
|
||||
const links = $derived(
|
||||
externalClientLinks({
|
||||
pubkey: AUTHOR_PUBKEY_HEX,
|
||||
kind: 30023,
|
||||
identifier: dtag
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<section class="external">
|
||||
<span class="label">In Nostr-Client öffnen (für Threads, Reactions, Teilen):</span>
|
||||
<ul>
|
||||
{#each links as l}
|
||||
<li><a href={l.url} target="_blank" rel="noopener">{l.label}</a></li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.external {
|
||||
margin: 2rem 0 1rem;
|
||||
padding: 0.8rem 1rem;
|
||||
background: var(--code-bg);
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
li a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
import Reactions from './Reactions.svelte';
|
||||
import ReplyList from './ReplyList.svelte';
|
||||
import ReplyComposer from './ReplyComposer.svelte';
|
||||
import ExternalClientLinks from './ExternalClientLinks.svelte';
|
||||
|
||||
interface Props {
|
||||
event: NostrEvent;
|
||||
|
|
@ -71,6 +72,7 @@
|
|||
|
||||
{#if dtag}
|
||||
<Reactions {dtag} />
|
||||
<ExternalClientLinks {dtag} />
|
||||
<ReplyComposer {dtag} eventId={event.id} onPublished={handlePublished} />
|
||||
<ReplyList {dtag} optimistic={optimisticReplies} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import type { NostrEvent, Profile } from '$lib/nostr/loaders';
|
||||
import { getProfile } from '$lib/nostr/profileCache';
|
||||
import { buildNjumpProfileUrl } from '$lib/nostr/naddr';
|
||||
|
||||
interface Props {
|
||||
event: NostrEvent;
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
|
||||
const date = $derived(new Date(event.created_at * 1000).toLocaleString('de-DE'));
|
||||
const npubPrefix = $derived(event.pubkey.slice(0, 12) + '…');
|
||||
const profileUrl = $derived(buildNjumpProfileUrl(event.pubkey));
|
||||
|
||||
let profile = $state<Profile | null>(null);
|
||||
|
||||
|
|
@ -25,7 +27,7 @@
|
|||
</script>
|
||||
|
||||
<li class="reply">
|
||||
<div class="header">
|
||||
<a class="header" href={profileUrl} target="_blank" rel="noopener">
|
||||
{#if profile?.picture}
|
||||
<img class="avatar" src={profile.picture} alt={displayName} />
|
||||
{:else}
|
||||
|
|
@ -35,7 +37,7 @@
|
|||
<span class="name">{displayName}</span>
|
||||
<span class="date">{date}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="content">{event.content}</div>
|
||||
</li>
|
||||
|
||||
|
|
@ -50,6 +52,17 @@
|
|||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
margin-bottom: 0.4rem;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
margin-left: -2px;
|
||||
}
|
||||
.header:hover {
|
||||
background: var(--code-bg);
|
||||
}
|
||||
.header:hover .name {
|
||||
color: var(--accent);
|
||||
}
|
||||
.avatar {
|
||||
flex: 0 0 32px;
|
||||
|
|
|
|||
|
|
@ -32,3 +32,34 @@ export function buildNaddr(args: NaddrArgs): string {
|
|||
export function buildHablaLink(args: NaddrArgs): string {
|
||||
return `${HABLA_BASE}${buildNaddr(args)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* `npub1…`-Bech32-String für einen Pubkey — für Profil-Links außerhalb
|
||||
* der SPA (z. B. njump.me).
|
||||
*/
|
||||
export function buildNpub(pubkeyHex: string): string {
|
||||
return nip19.npubEncode(pubkeyHex);
|
||||
}
|
||||
|
||||
/**
|
||||
* njump.me-Profil-URL. Öffnet das Nostr-native Profil-Browser mit
|
||||
* vollständiger Event-Historie.
|
||||
*/
|
||||
export function buildNjumpProfileUrl(pubkeyHex: string): string {
|
||||
return `https://njump.me/${buildNpub(pubkeyHex)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste externer Nostr-Clients für „Post öffnen in …"-Links.
|
||||
* Nutzt naddr, damit jeder Client das addressable Event adressieren kann.
|
||||
*/
|
||||
export function externalClientLinks(
|
||||
args: NaddrArgs
|
||||
): { label: string; url: string }[] {
|
||||
const naddr = buildNaddr(args);
|
||||
return [
|
||||
{ label: 'Habla', url: `https://habla.news/a/${naddr}` },
|
||||
{ label: 'Yakihonne', url: `https://yakihonne.com/article/${naddr}` },
|
||||
{ label: 'njump', url: `https://njump.me/${naddr}` }
|
||||
];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue