diff --git a/src/components/BookmarkItem.tsx b/src/components/BookmarkItem.tsx index 7902194d..43515e2e 100644 --- a/src/components/BookmarkItem.tsx +++ b/src/components/BookmarkItem.tsx @@ -62,7 +62,7 @@ export const BookmarkItem: React.FC = ({ bookmark, index, onS // Map kind numbers to FontAwesome icons const getKindIcon = (kind: number) => { - const iconMap: Record = { + const iconMap: Record = { 0: faCircleUser, 1: faFeather, 6: faRetweet, diff --git a/src/components/ContentWithResolvedProfiles.tsx b/src/components/ContentWithResolvedProfiles.tsx new file mode 100644 index 00000000..716c1ad3 --- /dev/null +++ b/src/components/ContentWithResolvedProfiles.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { useEventModel } from 'applesauce-react/hooks' +import { Models } from 'applesauce-core' +import { decode } from 'nostr-tools/nip19' +import { getPubkeyFromDecodeResult } from 'applesauce-core/helpers' +import { extractNprofilePubkeys } from '../utils/helpers' + +interface Props { content: string } + +const ContentWithResolvedProfiles: React.FC = ({ content }) => { + const matches = extractNprofilePubkeys(content) + const decoded = matches + .map((m) => { + try { return decode(m) } catch { return undefined } + }) + .filter(Boolean) + + const lookups = decoded.map((res) => getPubkeyFromDecodeResult(res as any)).filter(Boolean) as string[] + + const profiles = lookups.map((pubkey) => ({ pubkey, profile: useEventModel(Models.ProfileModel, [pubkey]) })) + + let rendered = content + matches.forEach((m, i) => { + const pk = getPubkeyFromDecodeResult(decoded[i] as any) + const found = profiles.find((p) => p.pubkey === pk) + const name = found?.profile?.name || found?.profile?.display_name || found?.profile?.nip05 || `${pk?.slice(0,8)}...` + if (name) rendered = rendered.replace(m, `@${name}`) + }) + + return
{rendered}
+} + +export default ContentWithResolvedProfiles + + diff --git a/src/utils/bookmarkUtils.tsx b/src/utils/bookmarkUtils.tsx index 5fe21cb1..b41a9613 100644 --- a/src/utils/bookmarkUtils.tsx +++ b/src/utils/bookmarkUtils.tsx @@ -1,76 +1,13 @@ import React from 'react' import { ParsedContent, ParsedNode } from '../types/bookmarks' -import { decode } from 'nostr-tools/nip19' -import { getPubkeyFromDecodeResult } from 'applesauce-core/helpers' -import { useEventModel } from 'applesauce-react/hooks' -import { Models } from 'applesauce-core' +import { ContentWithResolvedProfiles } from '../components/ContentWithResolvedProfiles' export const formatDate = (timestamp: number) => { return new Date(timestamp * 1000).toLocaleDateString() } -// Extract pubkeys from nprofile strings in content -export const extractNprofilePubkeys = (content: string): string[] => { - const nprofileRegex = /nprofile1[a-z0-9]+/gi - const matches = content.match(nprofileRegex) || [] - const pubkeys: string[] = [] - - for (const match of matches) { - try { - const decoded = decode(match) - const pubkey = getPubkeyFromDecodeResult(decoded) - if (pubkey && !pubkeys.includes(pubkey)) { - pubkeys.push(pubkey) - } - } catch (error) { - // Invalid nprofile string, skip - console.warn('Failed to decode nprofile:', match, error) - } - } - - return pubkeys -} - // Component to render content with resolved nprofile names -export const ContentWithResolvedProfiles: React.FC<{ content: string }> = ({ content }) => { - const nprofilePubkeys = extractNprofilePubkeys(content) - - // Create individual profile hooks for each pubkey - const profiles = nprofilePubkeys.map(pubkey => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const profile = useEventModel(Models.ProfileModel, [pubkey]) - return { pubkey, profile } - }) - - // Replace nprofile strings with resolved names - const renderContent = () => { - let renderedContent = content - - profiles.forEach(({ pubkey, profile }) => { - const displayName = profile?.name || profile?.display_name || profile?.nip05 || `${pubkey.slice(0, 8)}...` - - // Replace all instances of this nprofile with the display name - const nprofileRegex = new RegExp(`nprofile1[a-z0-9]+`, 'gi') - const matches = content.match(nprofileRegex) || [] - - matches.forEach(match => { - try { - const decoded = decode(match) - const matchPubkey = getPubkeyFromDecodeResult(decoded) - if (matchPubkey === pubkey) { - renderedContent = renderedContent.replace(match, `@${displayName}`) - } - } catch (error) { - // Skip invalid nprofile - } - }) - }) - - return renderedContent - } - - return
{renderContent()}
-} +export { default as ContentWithResolvedProfiles } from '../components/ContentWithResolvedProfiles' // Component to render parsed content using applesauce-content export const renderParsedContent = (parsedContent: ParsedContent) => { diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 00000000..101e2c54 --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,13 @@ +export const formatDate = (timestamp: number): string => { + return new Date(timestamp * 1000).toLocaleDateString() +} + +// Extract pubkeys from nprofile strings in content +export const extractNprofilePubkeys = (content: string): string[] => { + const nprofileRegex = /nprofile1[a-z0-9]+/gi + const matches = content.match(nprofileRegex) || [] + const unique = new Set(matches) + return Array.from(unique) +} + +