Fix pin references for non-addressable events, malformed shelf addresses, and profile links
This commit is contained in:
parent
883528242c
commit
0f17e62e54
7 changed files with 50 additions and 25 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
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 {pubkey, repository, manageRelay} from "@welshman/app"
|
||||||
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
||||||
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
const showInfo = () => pushModal(EventInfo, {url, event})
|
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)
|
const share = () => shareEventToChat(url, noun, event)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
if (type === "e") return "nostr:" + nip19.neventEncode({id: data})
|
if (type === "e") return "nostr:" + nip19.neventEncode({id: data})
|
||||||
|
|
||||||
|
if (type === "p") return "nostr:" + nip19.nprofileEncode({pubkey: data})
|
||||||
|
|
||||||
if (type === "a") {
|
if (type === "a") {
|
||||||
const {kind, pubkey, identifier} = Address.from(data)
|
const {kind, pubkey, identifier} = Address.from(data)
|
||||||
|
|
||||||
|
|
@ -35,10 +37,10 @@
|
||||||
{#if pin.description}
|
{#if pin.description}
|
||||||
<Content event={{content: pin.description, tags: []}} />
|
<Content event={{content: pin.description, tags: []}} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if pin.value[0] === "i"}
|
{#if pin.value[0] === "e" || pin.value[0] === "a"}
|
||||||
<Content event={{content, tags: []}} />
|
|
||||||
{:else}
|
|
||||||
<PinContentEvent value={pin.value[1]} relays={pin.value[2] ? [pin.value[2]] : []} />
|
<PinContentEvent value={pin.value[1]} relays={pin.value[2] ? [pin.value[2]] : []} />
|
||||||
|
{:else}
|
||||||
|
<Content event={{content, tags: []}} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if pin.topics.length > 0}
|
{#if pin.topics.length > 0}
|
||||||
<div class="mt-auto flex flex-wrap gap-1">
|
<div class="mt-auto flex flex-wrap gap-1">
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
Link
|
Link
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet info()}
|
{#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}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<input bind:value class="input input-bordered w-full" placeholder="URL or nevent..." />
|
<input bind:value class="input input-bordered w-full" placeholder="URL or nevent..." />
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
if (type === "e") return "nostr:" + nip19.neventEncode({id: data, relays: [url]})
|
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") {
|
if (type === "a") {
|
||||||
const {kind, pubkey, identifier} = Address.from(data)
|
const {kind, pubkey, identifier} = Address.from(data)
|
||||||
|
|
||||||
|
|
@ -52,10 +54,10 @@
|
||||||
{#if pin.description}
|
{#if pin.description}
|
||||||
<Content event={{content: pin.description, tags: []}} {url} />
|
<Content event={{content: pin.description, tags: []}} {url} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if pin.value[0] === "i"}
|
{#if pin.value[0] === "e" || pin.value[0] === "a"}
|
||||||
<Content event={{content, tags: []}} {url} />
|
|
||||||
{:else}
|
|
||||||
<PinContentEvent {url} value={pin.value[1]} relays={removeUndefined([pin.value[2], url])} />
|
<PinContentEvent {url} value={pin.value[1]} relays={removeUndefined([pin.value[2], url])} />
|
||||||
|
{:else}
|
||||||
|
<Content event={{content, tags: []}} {url} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if !minimal && pin.topics.length > 0}
|
{#if !minimal && pin.topics.length > 0}
|
||||||
<div class="mt-auto flex flex-wrap gap-1">
|
<div class="mt-auto flex flex-wrap gap-1">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
|
@ -10,19 +11,19 @@
|
||||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
import PinAdd from "@app/components/PinAdd.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 {makeSpacePath} from "@app/routes"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
address: string
|
event: TrustedEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, address}: Props = $props()
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
const boards = deriveBoards(url)
|
const boards = deriveBoards(url)
|
||||||
const reference = addressToReference(address)
|
const reference = eventToReference(event)
|
||||||
|
|
||||||
const selectBoard = (board: PublishedBoard) =>
|
const selectBoard = (board: PublishedBoard) =>
|
||||||
pushModal(PinAdd, {url, address: board.address, reference})
|
pushModal(PinAdd, {url, address: board.address, reference})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
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 {pubkey, manageRelay, repository} from "@welshman/app"
|
||||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
const addToLibrary = () => {
|
const addToLibrary = () => {
|
||||||
onClick()
|
onClick()
|
||||||
pushModal(PinboardSelect, {url, address: getAddress(event)})
|
pushModal(PinboardSelect, {url, event})
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = () => {
|
const report = () => {
|
||||||
|
|
|
||||||
|
|
@ -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 * as nip19 from "nostr-tools/nip19"
|
||||||
import {now, randomId} from "@welshman/lib"
|
import {now, randomId} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
|
|
@ -10,10 +11,12 @@ import {
|
||||||
getListTags,
|
getListTags,
|
||||||
getTagValue,
|
getTagValue,
|
||||||
getTagValues,
|
getTagValues,
|
||||||
|
isReplaceableKind,
|
||||||
readList,
|
readList,
|
||||||
sortEventsDesc,
|
sortEventsDesc,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {isLink, parse} from "@welshman/content"
|
||||||
import {deriveEventsForUrl, deriveRelaySignedEvents} from "@app/repository"
|
import {deriveEventsForUrl, deriveRelaySignedEvents} from "@app/repository"
|
||||||
import {signAsRelay} from "@app/relays"
|
import {signAsRelay} from "@app/relays"
|
||||||
|
|
||||||
|
|
@ -45,8 +48,9 @@ export type Pin = {
|
||||||
description: string
|
description: string
|
||||||
title: string
|
title: string
|
||||||
topics: string[]
|
topics: string[]
|
||||||
// The pin's content reference: the first e/a/i tag found, e.g. ["e", <id>],
|
// The pin's content reference: the first e/a/p/i tag found, e.g. ["e", <id>],
|
||||||
// ["a", <coord>], or ["i", <external id>]. Empty when the pin has no reference.
|
// ["a", <coord>], ["p", <pubkey>], or ["i", <url>]. Empty when the pin has no
|
||||||
|
// reference.
|
||||||
value: string[]
|
value: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +82,7 @@ export const readPin = (event: TrustedEvent): PublishedPin => ({
|
||||||
description: event.content,
|
description: event.content,
|
||||||
title: getTagValue("title", event.tags) ?? "",
|
title: getTagValue("title", event.tags) ?? "",
|
||||||
topics: getTagValues("t", 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) =>
|
export const deriveBoards = (url: string) =>
|
||||||
|
|
@ -89,8 +93,13 @@ export const deriveBoard = (url: string, identifier: string) =>
|
||||||
event ? readBoard(event) : undefined,
|
event ? readBoard(event) : undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
export const deriveBoardByAddress = (url: string, address: string) =>
|
export const deriveBoardByAddress = (
|
||||||
deriveBoard(url, Address.from(address).identifier)
|
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) =>
|
export const derivePins = (url: string, address: string) =>
|
||||||
derived(deriveRelaySignedEvents(url, [{kinds: [PIN], "#A": [address]}]), $events =>
|
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 === "e") return nip19.neventEncode({id: data, relays})
|
||||||
|
|
||||||
|
if (type === "p") return nip19.nprofileEncode({pubkey: data, relays})
|
||||||
|
|
||||||
if (type === "a") {
|
if (type === "a") {
|
||||||
const {kind, pubkey, identifier} = Address.from(data)
|
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 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 => {
|
export const referenceToPin = (reference: string): Partial<Pin> | undefined => {
|
||||||
const trimmed = reference.trim()
|
const trimmed = reference.trim()
|
||||||
|
|
||||||
if (!trimmed) return undefined
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoded = nip19.decode(fromNostrURI(trimmed))
|
const decoded = nip19.decode(fromNostrURI(trimmed))
|
||||||
|
|
||||||
if (decoded.type === "note") return {value: ["e", decoded.data]}
|
if (decoded.type === "note") return {value: ["e", decoded.data]}
|
||||||
|
if (decoded.type === "npub") return {value: ["p", decoded.data]}
|
||||||
if (decoded.type === "nevent") {
|
if (decoded.type === "nevent") {
|
||||||
const {id, relays} = decoded.data
|
const {id, relays} = decoded.data
|
||||||
|
|
||||||
return {value: relays?.[0] ? ["e", id, relays[0]] : ["e", id]}
|
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") {
|
if (decoded.type === "naddr") {
|
||||||
const {kind, pubkey, identifier, relays} = decoded.data
|
const {kind, pubkey, identifier, relays} = decoded.data
|
||||||
const coordinate = `${kind}:${pubkey}:${identifier}`
|
const coordinate = `${kind}:${pubkey}:${identifier}`
|
||||||
|
|
||||||
return {value: relays?.[0] ? ["a", coordinate, relays[0]] : ["a", coordinate]}
|
return {value: relays?.[0] ? ["a", coordinate, relays[0]] : ["a", coordinate]}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
} catch {
|
} catch {
|
||||||
// Not a nostr entity; fall through to external url handling.
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue