import { Component, createSignal, createMemo, Show, Switch, Match, createEffect } from 'solid-js'; import { createMutation } from '@tanstack/solid-query'; import ArrowPath from 'heroicons/24/outline/arrow-path.svg'; import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg'; import GlobeAlt from 'heroicons/24/outline/globe-alt.svg'; import XMark from 'heroicons/24/outline/x-mark.svg'; import CheckCircle from 'heroicons/24/solid/check-circle.svg'; import ExclamationCircle from 'heroicons/24/solid/exclamation-circle.svg'; import Modal from '@/components/Modal'; import Timeline from '@/components/Timeline'; import Copy from '@/components/utils/Copy'; import SafeLink from '@/components/utils/SafeLink'; import useCommands from '@/nostr/useCommands'; import useConfig from '@/nostr/useConfig'; import useFollowers from '@/nostr/useFollowers'; import useFollowings from '@/nostr/useFollowings'; import useProfile from '@/nostr/useProfile'; import usePubkey from '@/nostr/usePubkey'; import useSubscription from '@/nostr/useSubscription'; import useVerification from '@/nostr/useVerification'; import ensureNonNull from '@/utils/ensureNonNull'; import epoch from '@/utils/epoch'; import npubEncodeFallback from '@/utils/npubEncodeFallback'; import timeout from '@/utils/timeout'; import ContextMenu, { MenuItem } from './ContextMenu'; export type ProfileDisplayProps = { pubkey: string; onClose?: () => void; }; const FollowersCount: Component<{ pubkey: string }> = (props) => { const { count } = useFollowers(() => ({ pubkey: props.pubkey, })); return <>{count()}; }; const ProfileDisplay: Component = (props) => { const { config, addMutedPubkey, removeMutedPubkey, isPubkeyMuted } = useConfig(); const commands = useCommands(); const myPubkey = usePubkey(); const npub = createMemo(() => npubEncodeFallback(props.pubkey)); const [hoverFollowButton, setHoverFollowButton] = createSignal(false); const [showFollowers, setShowFollowers] = createSignal(false); const { profile, query: profileQuery } = useProfile(() => ({ pubkey: props.pubkey, })); const { verification, query: verificationQuery } = useVerification(() => ensureNonNull([profile()?.nip05] as const)(([nip05]) => ({ nip05 })), ); const nip05Identifier = () => { const ident = profile()?.nip05; if (ident == null) return null; const [user, domain] = ident.split('@'); if (domain == null) return null; if (user === '_') return { domain, ident: domain }; return { user, domain, ident }; }; const isVerified = () => verification()?.pubkey === props.pubkey; const isMuted = () => isPubkeyMuted(props.pubkey); const { followingPubkeys: myFollowingPubkeys, invalidateFollowings: invalidateMyFollowings, query: myFollowingQuery, } = useFollowings(() => ensureNonNull([myPubkey()] as const)(([pubkeyNonNull]) => ({ pubkey: pubkeyNonNull, })), ); const following = () => myFollowingPubkeys().includes(props.pubkey); const { followingPubkeys: userFollowingPubkeys, query: userFollowingQuery } = useFollowings( () => ({ pubkey: props.pubkey }), ); const followed = () => { const p = myPubkey(); return p != null && userFollowingPubkeys().includes(p); }; const updateContactsMutation = createMutation({ mutationKey: ['updateContacts'], mutationFn: (...params: Parameters) => commands .updateContacts(...params) .then((promises) => Promise.allSettled(promises.map(timeout(5000)))), onSuccess: (results) => { const succeeded = results.filter((res) => res.status === 'fulfilled').length; const failed = results.length - succeeded; if (succeeded === results.length) { console.log('succeeded to update contacts'); } else if (succeeded > 0) { console.log( `succeeded to update contacts for ${succeeded} relays but failed for ${failed} relays`, ); } else { console.error('failed to update contacts'); } }, onError: (err) => { console.error('failed to update contacts: ', err); }, onSettled: () => { invalidateMyFollowings() .then(() => myFollowingQuery.refetch()) .catch((err) => console.error('failed to refetch contacts', err)); }, }); const follow = () => { const p = myPubkey(); if (p == null) return; if (!myFollowingQuery.isFetched) return; updateContactsMutation.mutate({ relayUrls: config().relayUrls, pubkey: p, content: myFollowingQuery.data?.content ?? '', followingPubkeys: [...myFollowingPubkeys(), props.pubkey], }); }; const unfollow = () => { const p = myPubkey(); if (p == null) return; if (!myFollowingQuery.isFetched) return; if (!window.confirm('本当にフォロー解除しますか?')) return; updateContactsMutation.mutate({ relayUrls: config().relayUrls, pubkey: p, content: myFollowingQuery.data?.content ?? '', followingPubkeys: myFollowingPubkeys().filter((k) => k !== props.pubkey), }); }; const menu: MenuItem[] = [ { content: () => 'IDをコピー', onSelect: () => { navigator.clipboard.writeText(npub()).catch((err) => window.alert(err)); }, }, { content: () => (!isMuted() ? 'ミュート' : 'ミュート解除'), onSelect: () => { if (!isMuted()) { addMutedPubkey(props.pubkey); } else { removeMutedPubkey(props.pubkey); } }, }, ]; const { events } = useSubscription(() => ({ relayUrls: config().relayUrls, filters: [ { kinds: [1, 6], authors: [props.pubkey], limit: 10, until: epoch(), }, ], continuous: false, })); return ( props.onClose?.()}>
loading}> } keyed> {(bannerUrl) => (
header
)}
{(pictureUrl) => ( user icon )}
読み込み中 更新中 {/* あなたです */}
フォローされています
0}>
{profile()?.display_name}
0}>
@{profile()?.name}
0}>
{nip05Identifier()?.ident} } >
{npub()}
0}>
{profile()?.about}
フォロー
読み込み中} > {userFollowingPubkeys().length}
フォロワー
setShowFollowers(true)} > 読み込む } keyed >
0}>
    {(website) => (
  • )}
); }; export default ProfileDisplay;