refactor: Rename renderTextWithLinkedTags to renderTextWithLinks for clarity and combine hashtag and nostr reference handling

This commit is contained in:
2025-05-23 21:24:09 +02:00
parent 8b53f5da79
commit 1ed65cd0ea
3 changed files with 121 additions and 4 deletions

View File

@@ -13,7 +13,7 @@ import type { Event as NostrEvent } from "nostr-tools"
import ZapButton from "./ZapButton"
import Image from "next/image"
import CardOptionsDropdown from "./CardOptionsDropdown"
import { renderTextWithLinkedTags } from "@/utils/textUtils"
import { renderTextWithLinkedTags, renderTextWithLinks } from "@/utils/textUtils"
// Function to extract all images from a kind 20 event's imeta tags
const extractImagesFromEvent = (tags: string[][]): string[] => {
@@ -169,7 +169,7 @@ const KIND20Card: React.FC<KIND20CardProps> = ({
)}
</div>
<div className="p-4">
<div className="break-word overflow-hidden">{renderTextWithLinkedTags(text, tags)}</div>
<div className="break-word overflow-hidden">{renderTextWithLinks(text, tags, { [pubkey]: userData || {} })}</div>
<hr className="my-4" />
<div className="space-x-4 flex justify-between items-start">
<div className="flex space-x-4">

View File

@@ -30,7 +30,7 @@ import Link from 'next/link';
import { Event as NostrEvent } from "nostr-tools";
import ZapButton from './ZapButton';
import CardOptionsDropdown from './CardOptionsDropdown';
import { renderTextWithLinkedTags } from '@/utils/textUtils';
import { renderTextWithLinkedTags, renderTextWithLinks } from '@/utils/textUtils';
interface NoteCardProps {
pubkey: string;
@@ -143,7 +143,7 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
}
<br />
<div className='break-word overflow-hidden'>
{renderTextWithLinkedTags(textWithoutImage, tags)}
{renderTextWithLinks(textWithoutImage, tags, { [pubkey]: userData || {} })}
</div>
</div>
<hr />

View File

@@ -1,5 +1,6 @@
import React, { ReactNode } from 'react';
import Link from 'next/link';
import { nip19 } from 'nostr-tools';
/**
* Renders text content with hyperlinked hashtags
@@ -54,3 +55,119 @@ export function renderTextWithLinkedTags(content: string, eventTags: string[][])
return result;
}
/**
* Replace nostr:npub references with @username in content
* @param content The text content that may contain nostr:npub references
* @param eventTags The tags array from a Nostr event
* @param userData Optional object containing profile data for referenced users
* @returns Text with replaced nostr:npub references
*/
export function replaceNostrReferences(
content: string,
eventTags: string[][],
userData?: Record<string, { name?: string; display_name?: string; username?: string; }>
): ReactNode[] {
if (!content) return [];
// Extract all pubkey references from the event tags (p tags contain referenced pubkeys)
const pubkeyRefs = eventTags
.filter((tag) => tag[0] === "p")
.map((tag) => ({ pubkey: tag[1], relay: tag.length > 2 ? tag[2] : undefined, petname: tag.length > 3 ? tag[3] : undefined }));
// Find nostr:npub and nostr:nprofile references in the content
const nostrRegex = /nostr:(npub1[a-z0-9]+|nprofile1[a-z0-9]+)/g;
let lastIndex = 0;
const result: ReactNode[] = [];
let match;
while ((match = nostrRegex.exec(content)) !== null) {
const fullRef = match[0]; // nostr:npub1...
const matchIndex = match.index;
// Add text before the reference
if (matchIndex > lastIndex) {
result.push(content.substring(lastIndex, matchIndex));
}
try {
// Extract the identifier (remove "nostr:" prefix)
const nostrId = fullRef.substring(6);
let pubkey: string;
let profileRoute = nostrId;
// Decode npub or nprofile to get the pubkey
if (nostrId.startsWith('npub1')) {
const { data } = nip19.decode(nostrId);
pubkey = data as string;
} else if (nostrId.startsWith('nprofile1')) {
const { data } = nip19.decode(nostrId);
pubkey = (data as { pubkey: string }).pubkey;
// Still use the nprofile as the route to preserve relay information
profileRoute = nostrId;
} else {
throw new Error('Unsupported nostr ID type');
}
// Find if we have any profile data for this pubkey
const pubkeyData = pubkeyRefs.find(ref => ref.pubkey === pubkey);
const displayName = pubkeyData?.petname ||
(userData && userData[pubkey] &&
(userData[pubkey].username ||
userData[pubkey].display_name ||
userData[pubkey].name)) ||
nostrId.substring(0, 8) + '...';
// Create a link for the user reference
result.push(
<Link href={`/profile/${profileRoute}`} key={`${nostrId}-${matchIndex}`} className="text-blue-500 hover:underline">
@{displayName}
</Link>
);
} catch (error) {
// If there's an error parsing the npub, just include it as-is
result.push(fullRef);
}
lastIndex = matchIndex + fullRef.length;
}
// Add any remaining text
if (lastIndex < content.length) {
result.push(content.substring(lastIndex));
}
return result;
}
/**
* Combines hashtag and nostr reference handling in one function
* @param content The text content to process
* @param eventTags The tags array from a Nostr event
* @param userData Optional object containing profile data for referenced users
* @returns Processed content with links
*/
export function renderTextWithLinks(
content: string,
eventTags: string[][],
userData?: Record<string, { name?: string; display_name?: string; username?: string; }>
): ReactNode[] {
if (!content) return [];
// First replace hashtags
const withHashtags = renderTextWithLinkedTags(content, eventTags);
// Then handle nostr references for each text segment
const result: ReactNode[] = [];
for (const item of withHashtags) {
if (typeof item === 'string' && (item.includes('nostr:npub') || item.includes('nostr:nprofile'))) {
const withReferences = replaceNostrReferences(item, eventTags, userData);
result.push(...withReferences);
} else {
result.push(item);
}
}
return result;
}