93 lines
No EOL
2.8 KiB
TypeScript
93 lines
No EOL
2.8 KiB
TypeScript
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
|
import { NKinds, type NostrEvent } from '@nostrify/nostrify';
|
|
|
|
interface PostCommentParams {
|
|
root: NostrEvent | URL | `#${string}`; // The root event to comment on
|
|
reply?: NostrEvent | URL | `#${string}`; // Optional reply to another comment
|
|
content: string;
|
|
}
|
|
|
|
/** Post a NIP-22 (kind 1111) comment on an event. */
|
|
export function usePostComment() {
|
|
const { mutateAsync: publishEvent } = useNostrPublish();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ root, reply, content }: PostCommentParams) => {
|
|
const tags: string[][] = [];
|
|
|
|
// Root event tags
|
|
tags.push(...makeCommentTags('root', root));
|
|
|
|
// Reply event tags
|
|
if (reply) {
|
|
tags.push(...makeCommentTags('reply', reply));
|
|
} else {
|
|
// If this is a top-level comment, use the root event's tags
|
|
tags.push(...makeCommentTags('reply', root));
|
|
}
|
|
|
|
const event = await publishEvent({
|
|
kind: 1111,
|
|
content,
|
|
tags,
|
|
});
|
|
|
|
return event;
|
|
},
|
|
onSuccess: (_, { root }) => {
|
|
const rootKey = root instanceof URL ? root.toString() : typeof root === 'string' ? root : root.id;
|
|
|
|
// Invalidate and refetch comments
|
|
queryClient.invalidateQueries({
|
|
queryKey: ['nostr', 'comments', rootKey]
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
/** Build NIP-22 comment tags for a given scope and target */
|
|
function makeCommentTags(scope: 'root' | 'reply', target: NostrEvent | URL | `#${string}`): string[][] {
|
|
const tags: string[][] = [];
|
|
|
|
const d = (typeof target === 'string' || target instanceof URL)
|
|
? ''
|
|
: target.tags.find(([name]) => name === 'd')?.[1] ?? '';
|
|
|
|
if (typeof target === 'string') {
|
|
tags.push(['I', target]);
|
|
} else if (target instanceof URL) {
|
|
tags.push(['I', target.toString()]);
|
|
} else if (NKinds.addressable(target.kind)) {
|
|
tags.push(['A', `${target.kind}:${target.pubkey}:${d}`]);
|
|
} else if (NKinds.replaceable(target.kind)) {
|
|
tags.push(['A', `${target.kind}:${target.pubkey}:`]);
|
|
} else {
|
|
tags.push(['E', target.id]);
|
|
}
|
|
if (typeof target === 'string') {
|
|
tags.push(['K', '#']);
|
|
} else if (target instanceof URL) {
|
|
switch (target.protocol) {
|
|
case 'http:':
|
|
case 'https:':
|
|
tags.push(['K', 'web']);
|
|
break;
|
|
default:
|
|
tags.push(['K', target.protocol.replace(/:$/, '')]);
|
|
break;
|
|
}
|
|
} else {
|
|
tags.push(['K', target.kind.toString()]);
|
|
tags.push(['P', target.pubkey]);
|
|
}
|
|
|
|
// Lowercase all tag names for reply scope
|
|
if (scope === 'reply') {
|
|
return tags.map(([name, ...values]) => [name.toLowerCase(), ...values]);
|
|
}
|
|
|
|
// Root scope: uppercase tags
|
|
return tags;
|
|
} |