feat: user list

This commit is contained in:
Shusui MOYATANI
2023-06-28 01:24:41 +09:00
parent eedacfadd7
commit 1a4c9dc49b
4 changed files with 169 additions and 8 deletions

View File

@@ -0,0 +1,64 @@
import { Component, Show, Switch, Match } from 'solid-js';
import useProfile from '@/nostr/useProfile';
import npubEncodeFallback from '@/utils/npubEncodeFallback';
export type ProfileListItemProps = {
pubkey: string;
onShowProfile?: () => void;
};
const ProfileListItem: Component<ProfileListItemProps> = (props) => {
const { profile, query } = useProfile(() => ({
pubkey: props.pubkey,
}));
return (
<div class="flex w-full items-center gap-1">
<button
type="button"
class="profile-icon h-6 w-6 shrink-0 overflow-hidden"
onClick={(ev) => {
ev.preventDefault();
props.onShowProfile?.();
}}
>
<Show when={profile()?.picture} keyed>
{(url) => <img src={url} alt="icon" class="h-full w-full rounded object-cover" />}
</Show>
</button>
<div class="min-w-0 flex-auto">
<div class="flex justify-between gap-1 text-xs">
<button
type="button"
class="profile flex min-w-0 truncate hover:text-blue-500"
onClick={(ev) => {
ev.preventDefault();
props?.onShowProfile?.();
}}
>
<span class="profile flex min-w-0 truncate hover:text-blue-500">
<Show when={(profile()?.display_name?.length ?? 0) > 0}>
<div class="profile-name truncate pr-1 font-bold hover:underline">
{profile()?.display_name}
</div>
</Show>
<div class="profile-username truncate text-zinc-600">
<Show
when={profile()?.name}
fallback={`@${npubEncodeFallback(props.pubkey)}`}
keyed
>
{(name) => `@${name}`}
</Show>
{/* TODO <Match when={profile()?.nip05 != null}>@{author()?.nip05}</Match> */}
</div>
</span>
</button>
</div>
</div>
</div>
);
};
export default ProfileListItem;

View File

@@ -1,4 +1,13 @@
import { Show, For, createSignal, createMemo, type JSX, type Component } from 'solid-js';
import {
Show,
For,
createSignal,
createMemo,
type JSX,
type Component,
Switch,
Match,
} from 'solid-js';
import { createMutation } from '@tanstack/solid-query';
import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg';
@@ -17,6 +26,7 @@ import ContentWarningDisplay from '@/components/event/textNote/ContentWarningDis
import GeneralUserMentionDisplay from '@/components/event/textNote/GeneralUserMentionDisplay';
import TextNoteContentDisplay from '@/components/event/textNote/TextNoteContentDisplay';
import EventDebugModal from '@/components/modal/EventDebugModal';
import UserList from '@/components/modal/UserList';
import NotePostForm from '@/components/NotePostForm';
import Post from '@/components/Post';
import { useTimelineContext } from '@/components/timeline/TimelineContext';
@@ -95,8 +105,10 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
const [reacted, setReacted] = createSignal(false);
const [reposted, setReposted] = createSignal(false);
const [showReplyForm, setShowReplyForm] = createSignal(false);
const [showEventDebug, setShowEventDebug] = createSignal(false);
const [modal, setModal] = createSignal<'EventDebugModal' | 'Reactions' | 'Reposts' | null>(null);
const closeReplyForm = () => setShowReplyForm(false);
const closeModal = () => setModal(null);
const event = createMemo(() => textNote(props.event));
@@ -190,7 +202,19 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
{
content: () => 'JSONを確認',
onSelect: () => {
setShowEventDebug(true);
setModal('EventDebugModal');
},
},
{
content: () => 'リポスト一覧',
onSelect: () => {
setModal('Reposts');
},
},
{
content: () => 'リアクション一覧',
onSelect: () => {
setModal('Reactions');
},
},
{
@@ -437,9 +461,33 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
timelineContext?.setTimeline({ type: 'Replies', event: props.event });
}}
/>
<Show when={showEventDebug()}>
<EventDebugModal event={props.event} onClose={() => setShowEventDebug(false)} />
</Show>
<Switch>
<Match when={modal() === 'EventDebugModal'}>
<EventDebugModal event={props.event} onClose={closeModal} />
</Match>
<Match when={modal() === 'Reactions'}>
<UserList
data={reactions()}
pubkeyExtractor={(ev) => ev.pubkey}
renderInfo={({ content }) => (
<div class="w-6">
<Show
when={content === '+'}
fallback={<span class="truncate text-base">{content}</span>}
>
<span class="inline-block h-3 w-3 pt-[1px] text-rose-400">
<HeartSolid />
</span>
</Show>
</div>
)}
onClose={closeModal}
/>
</Match>
<Match when={modal() === 'Reposts'}>
<UserList data={reposts()} pubkeyExtractor={(ev) => ev.pubkey} onClose={closeModal} />
</Match>
</Switch>
</div>
);
};

View File

@@ -10,6 +10,7 @@ import uniq from 'lodash/uniq';
import ContextMenu, { MenuItem } from '@/components/ContextMenu';
import BasicModal from '@/components/modal/BasicModal';
import UserList from '@/components/modal/UserList';
import Timeline from '@/components/timeline/Timeline';
import SafeLink from '@/components/utils/SafeLink';
import useConfig from '@/core/useConfig';
@@ -51,6 +52,8 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
const [updatingContacts, setUpdatingContacts] = createSignal(false);
const [hoverFollowButton, setHoverFollowButton] = createSignal(false);
const [showFollowers, setShowFollowers] = createSignal(false);
const [modal, setModal] = createSignal<'Following' | null>(false);
const closeModal = () => setModal(null);
const { profile, query: profileQuery } = useProfile(() => ({
pubkey: props.pubkey,
@@ -341,7 +344,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</div>
</Show>
<div class="flex border-t px-4 py-2">
<div class="flex flex-1 flex-col items-start">
<button class="flex flex-1 flex-col items-start" onClick={() => setModal('Following')}>
<div class="text-sm"></div>
<div class="text-xl">
<Show
@@ -351,7 +354,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
{userFollowingPubkeys().length}
</Show>
</div>
</div>
</button>
<Show when={!config().hideCount}>
<div class="flex flex-1 flex-col items-start">
<div class="text-sm"></div>
@@ -389,6 +392,11 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</ul>
</Show>
</Show>
<Switch>
<Match when={modal() === 'Following'}>
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
</Match>
</Switch>
<ul class="border-t p-1">
<Timeline events={events()} />
</ul>

View File

@@ -0,0 +1,41 @@
import { For, JSX, Show } from 'solid-js';
import ProfileListItem from '@/components/event/ProfileListItem';
import BasicModal from '@/components/modal/BasicModal';
import useModalState from '@/hooks/useModalState';
export type UserListProps<T> = {
data: T[];
pubkeyExtractor: (e: T) => string;
renderInfo?: (e: T) => JSX.Element;
onClose: () => void;
};
const UserList = <T,>(props: UserListProps<T>): JSX.Element => {
const { showProfile } = useModalState();
return (
<BasicModal onClose={props.onClose}>
<div class="px-4 py-2">
<div>{props.data.length} </div>
<div>
<For each={props.data}>
{(e) => {
const pubkey = () => props.pubkeyExtractor(e);
return (
<div class="flex border-t py-1">
<Show when={props.renderInfo} keyed>
{(render) => render(e)}
</Show>
<ProfileListItem pubkey={pubkey()} onShowProfile={() => showProfile(pubkey())} />
</div>
);
}}
</For>
</div>
</div>
</BasicModal>
);
};
export default UserList;