From 44fb751eca41de2859562a724ac9a567080a75d9 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 23 Apr 2024 09:30:35 +0100 Subject: [PATCH] feat: render npub and nprofile mentions so that they display the inline UserHeader rather long npub terxt --- package.json | 1 + .../events/content/ParsedContent.svelte | 7 +++ src/lib/components/events/content/utils.ts | 56 +++++++++++++++++-- yarn.lock | 36 ++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c75d72f..fecaa33 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "daisyui": "^4.4", "dayjs": "^1.11.10", "highlight.js": "^11.9.0", + "nostr-tools": "^2.5.0", "parse-diff": "^0.11.1", "ramda": "^0.29.1", "svelte-markdown": "^0.4.1" diff --git a/src/lib/components/events/content/ParsedContent.svelte b/src/lib/components/events/content/ParsedContent.svelte index 1eeadc3..2567eb3 100644 --- a/src/lib/components/events/content/ParsedContent.svelte +++ b/src/lib/components/events/content/ParsedContent.svelte @@ -4,10 +4,13 @@ isImage, isParsedLink, isParsedNewLine, + isParsedNprofile, + isParsedNpub, isParsedText, parseContent, type ParsedPart, } from './utils' + import UserHeader from '$lib/components/users/UserHeader.svelte' export let content: string = '' export let tags: NDKTag[] = [] @@ -31,6 +34,10 @@ {part.url.replace(/https?:\/\/(www\.)?/, '')} {/if} + {:else if isParsedNpub(part) || isParsedNprofile(part)} +
+ +
{:else if isParsedText(part)} {part.value} {/if} diff --git a/src/lib/components/events/content/utils.ts b/src/lib/components/events/content/utils.ts index 12394a9..8463421 100644 --- a/src/lib/components/events/content/utils.ts +++ b/src/lib/components/events/content/utils.ts @@ -1,4 +1,5 @@ import type { NDKTag } from '@nostr-dev-kit/ndk' +import { nip19 } from 'nostr-tools' import { last } from 'ramda' export const TOPIC = 'topic' @@ -7,8 +8,6 @@ export const HTML = 'html' export const INVOICE = 'invoice' export const NOSTR_NOTE = 'nostr:note' export const NOSTR_NEVENT = 'nostr:nevent' -export const NOSTR_NPUB = 'nostr:npub' -export const NOSTR_NPROFILE = 'nostr:nprofile' export const NOSTR_NADDR = 'nostr:naddr' // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -53,6 +52,22 @@ type Imeta = { blurhash: string | undefined } +export const NOSTR_NPUB = 'nostr:npub' +type PartTypeNpub = 'nostr:npub' +export type ParsedNpub = { + type: PartTypeNpub + hex: string +} +export const NOSTR_NPROFILE = 'nostr:nprofile' +type PartTypeNprofile = 'nostr:nprofile' +export type ParsedNprofile = { + type: PartTypeNprofile + hex: string + relays: string[] +} + +export type ParsedNostrLink = ParsedNpub | ParsedNprofile + export const TEXT = 'text' type PartTypeText = 'text' export type ParsedText = { @@ -60,7 +75,11 @@ export type ParsedText = { value: string } -export type ParsedPart = ParsedNewLine | ParsedText | ParsedLink +export type ParsedPart = + | ParsedNewLine + | ParsedText + | ParsedNostrLink + | ParsedLink export const isParsedNewLine = (part: ParsedPart): part is ParsedNewLine => part.type == NEWLINE @@ -68,6 +87,15 @@ export const isParsedNewLine = (part: ParsedPart): part is ParsedNewLine => export const isParsedLink = (part: ParsedPart): part is ParsedLink => part.type == LINK +export const isParsedNostrLink = (part: ParsedPart): part is ParsedNostrLink => + part.type == NOSTR_NPUB || part.type == NOSTR_NPROFILE + +export const isParsedNpub = (part: ParsedPart): part is ParsedNpub => + part.type == NOSTR_NPUB + +export const isParsedNprofile = (part: ParsedPart): part is ParsedNprofile => + part.type == NOSTR_NPROFILE + export const isParsedText = (part: ParsedPart): part is ParsedText => part.type == TEXT @@ -140,9 +168,29 @@ export const parseContent = (content: string, tags: NDKTag[]): ParsedPart[] => { ] } + const parseNostrLinks = (): undefined | [string, ParsedNostrLink] => { + const bech32: string = first( + text.match( + /^(web\+)?(nostr:)?\/?\/?n(event|ote|profile|pub|addr)1[\d\w]+/i + ) + ) + if (bech32) { + try { + const entity = fromNostrURI(bech32) + const decoded = nip19.decode(entity) + if (decoded.type === 'npub') { + return [bech32, { type: NOSTR_NPUB, hex: decoded.data }] + } + if (decoded.type === 'nprofile') { + return [bech32, { type: NOSTR_NPUB, hex: decoded.data.pubkey }] + } + } catch {} + } + } + while (text) { // The order that this runs matters - const part = parseNewline() || parseUrl() + const part = parseNewline() || parseUrl() || parseNostrLinks() if (part) { if (buffer) { diff --git a/yarn.lock b/yarn.lock index b76d104..587ce1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1971,6 +1971,11 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7" integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw== +"@noble/ciphers@^0.5.1": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.2.tgz#879367fd51d59185259eb844d5b9a78f408b4a12" + integrity sha512-GADtQmZCdgbnNp+daPLc3OY3ibEtGGDV/+CzeM3MFnhiQ7ELQKlsHWYq0YbYUXx4jU3/Y1erAxU6r+hwpewqmQ== + "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -1978,11 +1983,23 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/hashes@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@^1.3.1": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" @@ -8606,6 +8623,25 @@ nostr-tools@^1.15.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +nostr-tools@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.5.0.tgz#083c8a22eb88c65f30d88a25e200ea2274348663" + integrity sha512-G02O3JYNCfhx9NDjd3NOCw/5ck8PX5hiOIhHKpsXyu49ZtZbxGH3OLP9tf0fpUZ+EVWdjIYFR689sV0i7+TOng== + dependencies: + "@noble/ciphers" "^0.5.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.1" + "@scure/base" "1.1.1" + "@scure/bip32" "1.3.1" + "@scure/bip39" "1.2.1" + optionalDependencies: + nostr-wasm v0.1.0 + +nostr-wasm@v0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" + integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA== + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"