Fix pin references for non-addressable events, malformed shelf addresses, and profile links

This commit is contained in:
Jon Staab 2026-07-27 15:16:21 -07:00
parent 883528242c
commit 0f17e62e54
7 changed files with 50 additions and 25 deletions

View file

@ -2,7 +2,7 @@
import {onMount} from "svelte"
import type {Snippet} from "svelte"
import type {TrustedEvent} from "@welshman/util"
import {COMMENT, getAddress, ManagementMethod} from "@welshman/util"
import {COMMENT, ManagementMethod} from "@welshman/util"
import {pubkey, repository, manageRelay} from "@welshman/app"
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
@ -38,7 +38,7 @@
const showInfo = () => pushModal(EventInfo, {url, event})
const addToLibrary = () => pushModal(PinboardSelect, {url, address: getAddress(event)})
const addToLibrary = () => pushModal(PinboardSelect, {url, event})
const share = () => shareEventToChat(url, noun, event)

View file

@ -18,6 +18,8 @@
if (type === "e") return "nostr:" + nip19.neventEncode({id: data})
if (type === "p") return "nostr:" + nip19.nprofileEncode({pubkey: data})
if (type === "a") {
const {kind, pubkey, identifier} = Address.from(data)
@ -35,10 +37,10 @@
{#if pin.description}
<Content event={{content: pin.description, tags: []}} />
{/if}
{#if pin.value[0] === "i"}
<Content event={{content, tags: []}} />
{:else}
{#if pin.value[0] === "e" || pin.value[0] === "a"}
<PinContentEvent value={pin.value[1]} relays={pin.value[2] ? [pin.value[2]] : []} />
{:else}
<Content event={{content, tags: []}} />
{/if}
{#if pin.topics.length > 0}
<div class="mt-auto flex flex-wrap gap-1">

View file

@ -130,7 +130,7 @@
Link
{/snippet}
{#snippet info()}
<p>A URL or nostr link (note, nevent, or naddr).</p>
<p>A URL or nostr link (note, nevent, naddr, npub, or nprofile).</p>
{/snippet}
{#snippet input()}
<input bind:value class="input input-bordered w-full" placeholder="URL or nevent..." />

View file

@ -25,6 +25,8 @@
if (type === "e") return "nostr:" + nip19.neventEncode({id: data, relays: [url]})
if (type === "p") return "nostr:" + nip19.nprofileEncode({pubkey: data, relays: [url]})
if (type === "a") {
const {kind, pubkey, identifier} = Address.from(data)
@ -52,10 +54,10 @@
{#if pin.description}
<Content event={{content: pin.description, tags: []}} {url} />
{/if}
{#if pin.value[0] === "i"}
<Content event={{content, tags: []}} {url} />
{:else}
{#if pin.value[0] === "e" || pin.value[0] === "a"}
<PinContentEvent {url} value={pin.value[1]} relays={removeUndefined([pin.value[2], url])} />
{:else}
<Content event={{content, tags: []}} {url} />
{/if}
{#if !minimal && pin.topics.length > 0}
<div class="mt-auto flex flex-wrap gap-1">

View file

@ -1,4 +1,5 @@
<script lang="ts">
import type {TrustedEvent} from "@welshman/util"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@ -10,19 +11,19 @@
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import RelayName from "@app/components/RelayName.svelte"
import PinAdd from "@app/components/PinAdd.svelte"
import {deriveBoards, addressToReference, type PublishedBoard} from "@app/pinboards"
import {deriveBoards, eventToReference, type PublishedBoard} from "@app/pinboards"
import {makeSpacePath} from "@app/routes"
import {pushModal} from "@app/modal"
type Props = {
url: string
address: string
event: TrustedEvent
}
const {url, address}: Props = $props()
const {url, event}: Props = $props()
const boards = deriveBoards(url)
const reference = addressToReference(address)
const reference = eventToReference(event)
const selectBoard = (board: PublishedBoard) =>
pushModal(PinAdd, {url, address: board.address, reference})

View file

@ -1,6 +1,6 @@
<script lang="ts">
import type {TrustedEvent} from "@welshman/util"
import {ManagementMethod, getAddress, getTagValue} from "@welshman/util"
import {ManagementMethod, getTagValue} from "@welshman/util"
import {pubkey, manageRelay, repository} from "@welshman/app"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
@ -36,7 +36,7 @@
const addToLibrary = () => {
onClick()
pushModal(PinboardSelect, {url, address: getAddress(event)})
pushModal(PinboardSelect, {url, event})
}
const report = () => {

View file

@ -1,4 +1,5 @@
import {derived} from "svelte/store"
import {derived, readable} from "svelte/store"
import type {Readable} from "svelte/store"
import * as nip19 from "nostr-tools/nip19"
import {now, randomId} from "@welshman/lib"
import {
@ -10,10 +11,12 @@ import {
getListTags,
getTagValue,
getTagValues,
isReplaceableKind,
readList,
sortEventsDesc,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {isLink, parse} from "@welshman/content"
import {deriveEventsForUrl, deriveRelaySignedEvents} from "@app/repository"
import {signAsRelay} from "@app/relays"
@ -45,8 +48,9 @@ export type Pin = {
description: string
title: string
topics: string[]
// The pin's content reference: the first e/a/i tag found, e.g. ["e", <id>],
// ["a", <coord>], or ["i", <external id>]. Empty when the pin has no reference.
// The pin's content reference: the first e/a/p/i tag found, e.g. ["e", <id>],
// ["a", <coord>], ["p", <pubkey>], or ["i", <url>]. Empty when the pin has no
// reference.
value: string[]
}
@ -78,7 +82,7 @@ export const readPin = (event: TrustedEvent): PublishedPin => ({
description: event.content,
title: getTagValue("title", event.tags) ?? "",
topics: getTagValues("t", event.tags),
value: event.tags.find(tag => ["e", "a", "i"].includes(tag[0])) ?? [],
value: event.tags.find(tag => ["e", "a", "p", "i"].includes(tag[0])) ?? [],
})
export const deriveBoards = (url: string) =>
@ -89,8 +93,13 @@ export const deriveBoard = (url: string, identifier: string) =>
event ? readBoard(event) : undefined,
)
export const deriveBoardByAddress = (url: string, address: string) =>
deriveBoard(url, Address.from(address).identifier)
export const deriveBoardByAddress = (
url: string,
address: string,
): Readable<PublishedBoard | undefined> =>
Address.isAddress(address)
? deriveBoard(url, Address.from(address).identifier)
: readable(undefined)
export const derivePins = (url: string, address: string) =>
derived(deriveRelaySignedEvents(url, [{kinds: [PIN], "#A": [address]}]), $events =>
@ -175,6 +184,8 @@ const valueToReference = (value: string[]): string => {
if (type === "e") return nip19.neventEncode({id: data, relays})
if (type === "p") return nip19.nprofileEncode({pubkey: data, relays})
if (type === "a") {
const {kind, pubkey, identifier} = Address.from(data)
@ -186,31 +197,40 @@ const valueToReference = (value: string[]): string => {
export const pinToReference = (pin: Pin): string => valueToReference(pin.value)
export const addressToReference = (address: string): string => valueToReference(["a", address])
export const eventToReference = (event: TrustedEvent): string =>
valueToReference(isReplaceableKind(event.kind) ? ["a", getAddress(event)] : ["e", event.id])
export const referenceToPin = (reference: string): Partial<Pin> | undefined => {
const trimmed = reference.trim()
if (!trimmed) return undefined
try {
const decoded = nip19.decode(fromNostrURI(trimmed))
if (decoded.type === "note") return {value: ["e", decoded.data]}
if (decoded.type === "npub") return {value: ["p", decoded.data]}
if (decoded.type === "nevent") {
const {id, relays} = decoded.data
return {value: relays?.[0] ? ["e", id, relays[0]] : ["e", id]}
}
if (decoded.type === "nprofile") {
const {pubkey, relays} = decoded.data
return {value: relays?.[0] ? ["p", pubkey, relays[0]] : ["p", pubkey]}
}
if (decoded.type === "naddr") {
const {kind, pubkey, identifier, relays} = decoded.data
const coordinate = `${kind}:${pubkey}:${identifier}`
return {value: relays?.[0] ? ["a", coordinate, relays[0]] : ["a", coordinate]}
}
return undefined
} catch {
// Not a nostr entity; fall through to external url handling.
}
return {value: ["i", trimmed]}
const parsed = parse({content: trimmed})
return parsed.length === 1 && isLink(parsed[0]) ? {value: ["i", trimmed]} : undefined
}