Add proper p tags for replies and mentions
This commit is contained in:
parent
a8aebe8862
commit
33f650276d
4 changed files with 89 additions and 1 deletions
|
|
@ -105,6 +105,17 @@
|
|||
textareaEl?.focus();
|
||||
}
|
||||
|
||||
// Warm the kind:10002 cache for thread participants so relay hints are
|
||||
// ready by publish time. Best-effort, dedup across focus events.
|
||||
const prefetched = new Set<string>();
|
||||
function onTextareaFocus() {
|
||||
for (const pk of contextPubkeys) {
|
||||
if (prefetched.has(pk)) continue;
|
||||
prefetched.add(pk);
|
||||
loadRelayList(pk).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
function togglePreview() {
|
||||
previewing = !previewing;
|
||||
}
|
||||
|
|
@ -334,6 +345,7 @@
|
|||
onkeydown={onTextareaKeydown}
|
||||
onclick={onTextareaClickOrSelect}
|
||||
onkeyup={onTextareaClickOrSelect}
|
||||
onfocus={onTextareaFocus}
|
||||
onblur={onTextareaBlur}
|
||||
aria-autocomplete="list"
|
||||
aria-controls={mentionOpen ? "mention-listbox" : undefined}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { SimplePool } from "@nostr/tools";
|
|||
import { auth } from "$lib/auth.svelte";
|
||||
import { withJoin } from "$lib/join.svelte";
|
||||
import { GROUP_ID, RELAY_URL } from "$lib/config";
|
||||
import { extractMentionPubkeys, buildPTagHints } from "$lib/mentions";
|
||||
|
||||
let modalOpen = $state(false);
|
||||
let iconized = $state(false);
|
||||
|
|
@ -80,6 +81,10 @@ export async function publishDraft(): Promise<{ ok: boolean; threadId?: string }
|
|||
publishError = null;
|
||||
let threadId: string | undefined;
|
||||
|
||||
const ownPubkey = auth.user?.pubkey;
|
||||
const mentionPubkeys = extractMentionPubkeys(c).filter((pk) => pk !== ownPubkey);
|
||||
const hints = await buildPTagHints(mentionPubkeys);
|
||||
|
||||
try {
|
||||
const success = await withJoin(async () => {
|
||||
const tags: string[][] = [
|
||||
|
|
@ -87,6 +92,10 @@ export async function publishDraft(): Promise<{ ok: boolean; threadId?: string }
|
|||
["title", t],
|
||||
];
|
||||
for (const l of labels) tags.push(["t", l]);
|
||||
for (const pk of mentionPubkeys) {
|
||||
const hint = hints.get(pk);
|
||||
tags.push(hint ? ["p", pk, hint] : ["p", pk]);
|
||||
}
|
||||
|
||||
const event = await auth.signer!.signEvent({
|
||||
kind: 11,
|
||||
|
|
|
|||
46
src/lib/mentions.ts
Normal file
46
src/lib/mentions.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import * as nip19 from "@nostr/tools/nip19";
|
||||
import { loadRelayList } from "@nostr/gadgets/lists";
|
||||
|
||||
const RELAY_HINT_TIMEOUT_MS = 1500;
|
||||
|
||||
export function extractMentionPubkeys(content: string): string[] {
|
||||
const re = /nostr:(npub1[a-z0-9]+|nprofile1[a-z0-9]+)/gi;
|
||||
const out = new Set<string>();
|
||||
for (const m of content.matchAll(re)) {
|
||||
try {
|
||||
const decoded = nip19.decode(m[1]);
|
||||
if (decoded.type === "npub") out.add(decoded.data);
|
||||
else if (decoded.type === "nprofile") out.add(decoded.data.pubkey);
|
||||
} catch {
|
||||
// Invalid bech32, skip
|
||||
}
|
||||
}
|
||||
return [...out];
|
||||
}
|
||||
|
||||
export async function relayHintFor(pubkey: string): Promise<string | undefined> {
|
||||
try {
|
||||
const TIMEOUT = Symbol();
|
||||
const result = await Promise.race([
|
||||
loadRelayList(pubkey),
|
||||
new Promise<typeof TIMEOUT>((resolve) =>
|
||||
setTimeout(() => resolve(TIMEOUT), RELAY_HINT_TIMEOUT_MS),
|
||||
),
|
||||
]);
|
||||
if (result === TIMEOUT) return undefined;
|
||||
return result.items.find((r) => r.write)?.url;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildPTagHints(pubkeys: Iterable<string>): Promise<Map<string, string>> {
|
||||
const hints = new Map<string, string>();
|
||||
await Promise.all(
|
||||
[...pubkeys].map(async (pk) => {
|
||||
const url = await relayHintFor(pk);
|
||||
if (url) hints.set(pk, url);
|
||||
}),
|
||||
);
|
||||
return hints;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { RELAY_URL, GROUP_ID } from "$lib/config";
|
|||
import { threads as mockThreads } from "$lib/mock";
|
||||
import { auth } from "$lib/auth.svelte";
|
||||
import { ingestNostrUser } from "$lib/profiles.svelte";
|
||||
import { extractMentionPubkeys, buildPTagHints } from "$lib/mentions";
|
||||
|
||||
const isNostrId = (id: string) => /^[0-9a-f]{64}$/.test(id);
|
||||
|
||||
|
|
@ -112,11 +113,31 @@ export async function sendReply(content: string, ownPubkey: string) {
|
|||
.slice(-3)
|
||||
.map((p) => p.id.slice(0, 8));
|
||||
|
||||
// NIP-7D mandates flat-against-root, so parent === root for every reply.
|
||||
// Build the notify set: thread participants + mentions in content, minus self.
|
||||
const notifyPubkeys = new Set<string>();
|
||||
notifyPubkeys.add(detail.op.pubkey);
|
||||
for (const r of detail.replies) notifyPubkeys.add(r.pubkey);
|
||||
for (const pk of extractMentionPubkeys(content)) notifyPubkeys.add(pk);
|
||||
notifyPubkeys.delete(ownPubkey);
|
||||
|
||||
// Best-effort relay hints — cached calls return instantly, others race a timeout.
|
||||
const hints = await buildPTagHints([...notifyPubkeys, detail.op.pubkey]);
|
||||
|
||||
const opHint = hints.get(detail.op.pubkey) ?? RELAY_URL;
|
||||
|
||||
const tags: string[][] = [
|
||||
["h", GROUP_ID],
|
||||
["E", detail.id, opHint, detail.op.pubkey],
|
||||
["K", "11"],
|
||||
["E", detail.id, RELAY_URL, detail.op.pubkey],
|
||||
["P", detail.op.pubkey, opHint],
|
||||
["e", detail.id, opHint, detail.op.pubkey],
|
||||
["k", "11"],
|
||||
];
|
||||
for (const pk of notifyPubkeys) {
|
||||
const hint = hints.get(pk);
|
||||
tags.push(hint ? ["p", pk, hint] : ["p", pk]);
|
||||
}
|
||||
if (previousRefs.length > 0) tags.push(["previous", ...previousRefs]);
|
||||
|
||||
console.log("[reply] signing event…");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue