forked from coracle/flotilla
Support copying and pasting npubs better
This commit is contained in:
@@ -8,6 +8,8 @@
|
|||||||
* Support multiple platform relays
|
* Support multiple platform relays
|
||||||
* Remove default general room
|
* Remove default general room
|
||||||
* Remove room tag from threads/calendars
|
* Remove room tag from threads/calendars
|
||||||
|
* Show pubkey on profile detail
|
||||||
|
* Support pasting pubkey into chat start dialog
|
||||||
|
|
||||||
# 1.0.4
|
# 1.0.4
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
|
import {onMount} from "svelte"
|
||||||
|
import {writable} from "svelte/store"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
|
import {tryCatch, uniq} from "@welshman/lib"
|
||||||
|
import {fromNostrURI} from "@welshman/util"
|
||||||
import {pubkey} from "@welshman/app"
|
import {pubkey} from "@welshman/app"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
@@ -14,7 +19,36 @@
|
|||||||
|
|
||||||
const onSubmit = () => goto(makeChatPath([...pubkeys, $pubkey!]))
|
const onSubmit = () => goto(makeChatPath([...pubkeys, $pubkey!]))
|
||||||
|
|
||||||
|
const addPubkey = (pubkey: string) => {
|
||||||
|
pubkeys = uniq([...pubkeys, pubkey])
|
||||||
|
term.set("")
|
||||||
|
}
|
||||||
|
|
||||||
|
const term = writable("")
|
||||||
|
|
||||||
let pubkeys: string[] = $state([])
|
let pubkeys: string[] = $state([])
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
return term.subscribe(t => {
|
||||||
|
if (t.match(/^[0-9a-f]{64}$/)) {
|
||||||
|
addPubkey(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.match(/^(nostr:)?(npub1|nprofile1)/)) {
|
||||||
|
tryCatch(() => {
|
||||||
|
const {type, data} = nip19.decode(fromNostrURI(t))
|
||||||
|
|
||||||
|
if (type === "npub") {
|
||||||
|
addPubkey(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "nprofile") {
|
||||||
|
addPubkey(data.pubkey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
|
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
|
||||||
@@ -28,7 +62,7 @@
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<Field>
|
<Field>
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<ProfileMultiSelect autofocus bind:value={pubkeys} />
|
<ProfileMultiSelect autofocus bind:value={pubkeys} {term} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeNil} from "@welshman/lib"
|
||||||
import {displayPubkey, getPubkeyTagValues, getListTags} from "@welshman/util"
|
import {displayPubkey, getPubkeyTagValues, getListTags} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
@@ -10,18 +11,22 @@
|
|||||||
deriveProfile,
|
deriveProfile,
|
||||||
deriveProfileDisplay,
|
deriveProfileDisplay,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import WotScore from "@lib/components/WotScore.svelte"
|
import WotScore from "@lib/components/WotScore.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
import {clip} from "@app/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
url?: string
|
url?: string
|
||||||
|
showPubkey?: boolean
|
||||||
|
avatarSize?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
||||||
|
|
||||||
const relays = removeNil([url])
|
const relays = removeNil([url])
|
||||||
const profile = deriveProfile(pubkey, relays)
|
const profile = deriveProfile(pubkey, relays)
|
||||||
@@ -31,14 +36,16 @@
|
|||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
||||||
|
|
||||||
|
const copyPubkey = () => clip(nip19.npubEncode(pubkey))
|
||||||
|
|
||||||
const following = $derived(
|
const following = $derived(
|
||||||
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
|
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex max-w-full gap-3">
|
<div class="flex max-w-full items-start gap-3">
|
||||||
<Button onclick={openProfile} class="py-1">
|
<Button onclick={openProfile} class="py-1">
|
||||||
<Avatar src={$profile?.picture} size={10} />
|
<Avatar src={$profile?.picture} size={avatarSize} />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -47,8 +54,18 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<WotScore score={$score} active={following} />
|
<WotScore score={$score} active={following} />
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden text-ellipsis text-sm opacity-75">
|
{#if $handle}
|
||||||
{$handle ? displayHandle($handle) : displayPubkey(pubkey)}
|
<div class="overflow-hidden text-ellipsis text-sm opacity-75">
|
||||||
</div>
|
{displayHandle($handle)}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if showPubkey}
|
||||||
|
<div class="flex items-center gap-1 overflow-hidden text-ellipsis text-xs opacity-60">
|
||||||
|
{displayPubkey(pubkey)}
|
||||||
|
<Button onclick={copyPubkey} class="pt-1">
|
||||||
|
<Icon size={3} icon="copy" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {removeNil} from "@welshman/lib"
|
|
||||||
import {displayPubkey, getPubkeyTagValues, getListTags} from "@welshman/util"
|
|
||||||
import {
|
|
||||||
session,
|
|
||||||
userFollows,
|
|
||||||
deriveUserWotScore,
|
|
||||||
deriveHandleForPubkey,
|
|
||||||
displayHandle,
|
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
|
||||||
import WotScore from "@lib/components/WotScore.svelte"
|
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
||||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||||
import {canDecrypt, pubkeyLink} from "@app/state"
|
import {canDecrypt, pubkeyLink} from "@app/state"
|
||||||
@@ -30,40 +18,15 @@
|
|||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
const relays = removeNil([url])
|
|
||||||
const profile = deriveProfile(pubkey, relays)
|
|
||||||
const display = deriveProfileDisplay(pubkey, relays)
|
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
|
||||||
const score = deriveUserWotScore(pubkey)
|
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const chatPath = makeChatPath([pubkey])
|
const chatPath = makeChatPath([pubkey])
|
||||||
|
|
||||||
const openChat = () => ($canDecrypt ? goto(chatPath) : pushModal(ChatEnable, {next: chatPath}))
|
const openChat = () => ($canDecrypt ? goto(chatPath) : pushModal(ChatEnable, {next: chatPath}))
|
||||||
|
|
||||||
const following = $derived(
|
|
||||||
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
<div class="flex max-w-full gap-3">
|
<Profile showPubkey avatarSize={14} {pubkey} {url} />
|
||||||
<span class="py-1">
|
|
||||||
<Avatar src={$profile?.picture} size={10} />
|
|
||||||
</span>
|
|
||||||
<div class="flex min-w-0 flex-col">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-bold overflow-hidden text-ellipsis">
|
|
||||||
{$display}
|
|
||||||
</span>
|
|
||||||
<WotScore score={$score} active={following} />
|
|
||||||
</div>
|
|
||||||
<div class="overflow-hidden text-ellipsis text-sm opacity-75">
|
|
||||||
{$handle ? displayHandle($handle) : displayPubkey(pubkey)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProfileInfo {pubkey} {url} />
|
<ProfileInfo {pubkey} {url} />
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onclick={back} class="hidden md:btn md:btn-link">
|
<Button onclick={back} class="hidden md:btn md:btn-link">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
|
import type {Writable} from "svelte/store"
|
||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {append, remove, uniq} from "@welshman/lib"
|
import {append, remove, uniq} from "@welshman/lib"
|
||||||
import {profileSearch} from "@welshman/app"
|
import {profileSearch} from "@welshman/app"
|
||||||
@@ -15,11 +16,10 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
value: string[]
|
value: string[]
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
|
term?: Writable<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
let {value = $bindable(), autofocus = false}: Props = $props()
|
let {value = $bindable(), term = writable(""), autofocus = false}: Props = $props()
|
||||||
|
|
||||||
const term = writable("")
|
|
||||||
|
|
||||||
const search = (term: string) => $profileSearch.searchValues(term)
|
const search = (term: string) => $profileSearch.searchValues(term)
|
||||||
|
|
||||||
@@ -44,6 +44,9 @@
|
|||||||
let instance: any = $state()
|
let instance: any = $state()
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
oninput?.($term)
|
||||||
|
|
||||||
if ($term) {
|
if ($term) {
|
||||||
popover?.show()
|
popover?.show()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
const image = new Image()
|
const image = new Image()
|
||||||
|
|
||||||
image.addEventListener("error", () => {
|
image.addEventListener("error", () => {
|
||||||
element.querySelector(".hidden")?.classList.remove("hidden")
|
element?.querySelector(".hidden")?.classList.remove("hidden")
|
||||||
})
|
})
|
||||||
|
|
||||||
image.src = src
|
image.src = src
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import {throttle, clamp} from "@welshman/lib"
|
import {throttle, clamp} from "@welshman/lib"
|
||||||
import {preventDefault, stopPropagation} from "@lib/html"
|
import {preventDefault, stopPropagation} from "@lib/html"
|
||||||
|
|
||||||
const {term, search, select, component: Component, allowCreate = false} = $props()
|
const {term, search, select, component: Component, style = "", allowCreate = false} = $props()
|
||||||
|
|
||||||
let index = $state(0)
|
let index = $state(0)
|
||||||
let items: string[] = $state([])
|
let items: string[] = $state([])
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div transition:fly|local={{duration: 200}} class="tiptap-suggestions">
|
<div transition:fly|local={{duration: 200}} class="tiptap-suggestions" {style}>
|
||||||
<div class="tiptap-suggestions__content max-h-[40vh]">
|
<div class="tiptap-suggestions__content max-h-[40vh]">
|
||||||
{#if $term && allowCreate && !items.includes($term)}
|
{#if $term && allowCreate && !items.includes($term)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
...restProps
|
...restProps
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
const reactiveProps = $derived(props)
|
|
||||||
|
|
||||||
let element: Element
|
let element: Element
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -28,7 +26,7 @@
|
|||||||
...params,
|
...params,
|
||||||
})
|
})
|
||||||
|
|
||||||
instance = mount(component, {target, props: reactiveProps})
|
instance = mount(component, {target, props})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
popover?.destroy()
|
popover?.destroy()
|
||||||
|
|||||||
Reference in New Issue
Block a user