mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 18:00:47 +00:00
update thread widget
This commit is contained in:
parent
0de72eb009
commit
e1e54c1a98
@ -76,13 +76,14 @@
|
|||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-router-dom": "^6.17.0",
|
"react-router-dom": "^6.17.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
|
"react-xarrows": "^2.0.2",
|
||||||
"reactflow": "^11.9.4",
|
"reactflow": "^11.9.4",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"sonner": "^1.0.3",
|
"sonner": "^1.0.3",
|
||||||
"tailwind-scrollbar": "^3.0.5",
|
"tailwind-scrollbar": "^3.0.5",
|
||||||
"tauri-controls": "^0.2.0",
|
"tauri-controls": "^0.2.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"virtua": "^0.13.0",
|
"virtua": "^0.14.0",
|
||||||
"zustand": "^4.4.3"
|
"zustand": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -179,6 +179,9 @@ dependencies:
|
|||||||
react-string-replace:
|
react-string-replace:
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
|
react-xarrows:
|
||||||
|
specifier: ^2.0.2
|
||||||
|
version: 2.0.2(react@18.2.0)
|
||||||
reactflow:
|
reactflow:
|
||||||
specifier: ^11.9.4
|
specifier: ^11.9.4
|
||||||
version: 11.9.4(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.9.4(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -198,8 +201,8 @@ dependencies:
|
|||||||
specifier: ^6.3.7
|
specifier: ^6.3.7
|
||||||
version: 6.3.7
|
version: 6.3.7
|
||||||
virtua:
|
virtua:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.14.0
|
||||||
version: 0.13.0(react-dom@18.2.0)(react@18.2.0)
|
version: 0.14.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3(@types/react@18.2.29)(react@18.2.0)
|
version: 4.4.3(@types/react@18.2.29)(react@18.2.0)
|
||||||
@ -4688,7 +4691,6 @@ packages:
|
|||||||
|
|
||||||
/lodash@4.17.21:
|
/lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/log-update@5.0.1:
|
/log-update@5.0.1:
|
||||||
resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==}
|
resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==}
|
||||||
@ -5947,6 +5949,17 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-xarrows@2.0.2(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-tDlAqaxHNmy0vegW/6NdhoWyXJq1LANX/WUAlHyzoHe9BwFVnJPPDghmDjYeVr7XWFmBrVTUrHsrW7GKYI6HtQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
'@types/prop-types': 15.7.9
|
||||||
|
lodash: 4.17.21
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react@18.2.0:
|
/react@18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -6728,8 +6741,8 @@ packages:
|
|||||||
vfile-message: 3.1.4
|
vfile-message: 3.1.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/virtua@0.13.0(react-dom@18.2.0)(react@18.2.0):
|
/virtua@0.14.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-NiM+3lhl/XMLWsT+Fc+rcMQrsAe7PDRvncu6CjP5UEgDtulIo05KAaugrJAr/ptBofP/iAnlZK/X0Bjd+UkjIQ==}
|
resolution: {integrity: sha512-+g3fxgFuQCqw6PpU5qzTRKhbSUGOeMEap0VbPaIRB1RiK5MfLiGXIMwID1iX1DmvUC/SqsBsJfVvlUaPNGWSVQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.14.0'
|
react: '>=16.14.0'
|
||||||
react-dom: '>=16.14.0'
|
react-dom: '>=16.14.0'
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
NoteStats,
|
NoteStats,
|
||||||
UnknownNote,
|
UnknownNote,
|
||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { RepliesList } from '@shared/notes/replies/list';
|
import { ReplyList } from '@shared/notes/replies/list';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ export function ArticleNoteScreen() {
|
|||||||
</div>
|
</div>
|
||||||
<div ref={replyRef} className="px-3">
|
<div ref={replyRef} className="px-3">
|
||||||
<NoteReplyForm id={data.id} pubkey={db.account.pubkey} />
|
<NoteReplyForm id={data.id} pubkey={db.account.pubkey} />
|
||||||
<RepliesList id={data.id} />
|
<ReplyList id={data.id} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
TextNote,
|
TextNote,
|
||||||
UnknownNote,
|
UnknownNote,
|
||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { RepliesList } from '@shared/notes/replies/list';
|
import { ReplyList } from '@shared/notes/replies/list';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
@ -119,7 +119,7 @@ export function TextNoteScreen() {
|
|||||||
)}
|
)}
|
||||||
<div ref={replyRef} className="px-3">
|
<div ref={replyRef} className="px-3">
|
||||||
<NoteReplyForm id={id} pubkey={db.account.pubkey} />
|
<NoteReplyForm id={id} pubkey={db.account.pubkey} />
|
||||||
<RepliesList id={id} />
|
<ReplyList id={id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1" />
|
<div className="col-span-1" />
|
||||||
|
@ -7,6 +7,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { AccountMoreActions } from '@shared/accounts/more';
|
import { AccountMoreActions } from '@shared/accounts/more';
|
||||||
|
import { NetworkStatusIndicator } from '@shared/networkStatusIndicator';
|
||||||
|
|
||||||
import { useActivities } from '@stores/activities';
|
import { useActivities } from '@stores/activities';
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ export function ActiveAccount() {
|
|||||||
/>
|
/>
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
<span className="absolute bottom-0 right-0 block h-2 w-2 rounded-full bg-teal-500 ring-2 ring-neutral-100 dark:ring-neutral-900" />
|
<NetworkStatusIndicator />
|
||||||
</Link>
|
</Link>
|
||||||
<AccountMoreActions pubkey={db.account.pubkey} />
|
<AccountMoreActions pubkey={db.account.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
|
||||||
|
|
||||||
export function Button({
|
|
||||||
preset,
|
|
||||||
children,
|
|
||||||
disabled = false,
|
|
||||||
onClick = undefined,
|
|
||||||
}: {
|
|
||||||
preset: 'small' | 'publish' | 'large' | 'large-alt';
|
|
||||||
children: ReactNode;
|
|
||||||
disabled?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
}) {
|
|
||||||
let preClass: string;
|
|
||||||
switch (preset) {
|
|
||||||
case 'small':
|
|
||||||
preClass =
|
|
||||||
'w-min h-9 px-4 bg-neutral-400 dark:bg-neutral-600 rounded-md text-sm font-medium text-white hover:bg-blue-600';
|
|
||||||
break;
|
|
||||||
case 'publish':
|
|
||||||
preClass =
|
|
||||||
'w-min h-9 px-4 bg-blue-500 rounded-md text-sm font-medium text-white hover:bg-blue-600';
|
|
||||||
break;
|
|
||||||
case 'large':
|
|
||||||
preClass =
|
|
||||||
'h-11 w-full bg-blue-500 rounded-lg font-medium text-white hover:bg-blue-600';
|
|
||||||
break;
|
|
||||||
case 'large-alt':
|
|
||||||
preClass =
|
|
||||||
'h-11 w-full bg-neutral-400 dark:bg-neutral-600 rounded-lg font-medium text-white hover:bg-white/20';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={disabled}
|
|
||||||
className={twMerge(
|
|
||||||
'inline-flex transform items-center justify-center gap-1 leading-none focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50',
|
|
||||||
preClass
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { useNetworkStatus } from '@utils/hooks/useNetworkStatus';
|
import { useNetworkStatus } from '@utils/hooks/useNetworkStatus';
|
||||||
|
|
||||||
export function NetworkStatusIndicator() {
|
export function NetworkStatusIndicator() {
|
||||||
@ -5,9 +7,10 @@ export function NetworkStatusIndicator() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`absolute right-0 top-0 block h-2 w-2 -translate-y-1/2 translate-x-1/2 transform rounded-full ${
|
className={twMerge(
|
||||||
isOnline ? 'bg-green-400' : 'bg-red-400'
|
'absolute bottom-0 right-0 block h-2 w-2 rounded-full ring-2 ring-neutral-100 dark:ring-neutral-900',
|
||||||
} ring-2 ring-black`}
|
isOnline ? 'bg-teal-500' : 'bg-red-500'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Button } from '@shared/button';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
import { useProfile } from '@utils/hooks/useProfile';
|
|
||||||
import { displayNpub } from '@utils/shortenKey';
|
|
||||||
|
|
||||||
export function NoteReplyForm({ id, pubkey }: { id: string; pubkey: string }) {
|
export function NoteReplyForm({ id }: { id: string }) {
|
||||||
const { publish } = useNostr();
|
const { publish } = useNostr();
|
||||||
const { status, user } = useProfile(pubkey);
|
const { db } = useStorage();
|
||||||
|
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
@ -23,47 +23,23 @@ export function NoteReplyForm({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-3 flex flex-col rounded-xl bg-neutral-200 dark:bg-neutral-800">
|
<div className="mt-3 flex gap-3">
|
||||||
<div className="relative w-full flex-1 overflow-hidden">
|
<User pubkey={db.account.pubkey} variant="miniavatar" />
|
||||||
|
<div className="relative flex flex-1 flex-col rounded-xl bg-neutral-100 dark:bg-neutral-900">
|
||||||
<textarea
|
<textarea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
placeholder="Reply to this thread..."
|
placeholder="Reply to this thread..."
|
||||||
className=" relative h-24 w-full resize-none rounded-md bg-transparent px-3 py-3 text-base text-neutral-900 !outline-none placeholder:text-neutral-600 dark:text-neutral-100 dark:placeholder:text-neutral-400"
|
className="relative h-24 w-full resize-none bg-transparent p-3 text-base text-neutral-900 !outline-none placeholder:text-neutral-600 dark:text-neutral-100 dark:placeholder:text-neutral-400"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
<button
|
||||||
<div className="w-full border-t border-neutral-300 px-3 py-3 dark:border-neutral-700">
|
onClick={() => submit()}
|
||||||
{status === 'loading' ? (
|
disabled={value.length === 0 ? true : false}
|
||||||
<div>Loading</div>
|
className="mb-2 ml-auto mr-2 h-9 w-20 rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50"
|
||||||
) : (
|
>
|
||||||
<div className="flex w-full items-center justify-between">
|
Reply
|
||||||
<div className="inline-flex items-center gap-2">
|
</button>
|
||||||
<div className="relative h-11 w-11 shrink-0 rounded">
|
|
||||||
<img
|
|
||||||
src={user?.picture || user?.image}
|
|
||||||
alt={pubkey}
|
|
||||||
className="h-11 w-11 rounded-lg bg-white object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">Reply as</p>
|
|
||||||
<p className="font-semibold text-neutral-900 dark:text-neutral-100">
|
|
||||||
{user?.name || displayNpub(pubkey, 16)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => submit()}
|
|
||||||
disabled={value.length === 0 ? true : false}
|
|
||||||
preset="publish"
|
|
||||||
>
|
|
||||||
Reply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,34 +1,53 @@
|
|||||||
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
import { NavArrowDownIcon } from '@shared/icons';
|
||||||
import { MemoizedTextNote, NoteActions, SubReply } from '@shared/notes';
|
import { MemoizedTextNote, NoteActions, SubReply } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { NDKEventWithReplies } from '@utils/types';
|
import { NDKEventWithReplies } from '@utils/types';
|
||||||
|
|
||||||
export function Reply({ event, root }: { event: NDKEventWithReplies; root?: string }) {
|
export function Reply({ event, root }: { event: NDKEventWithReplies; root?: string }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-min w-full">
|
<div className="relative">
|
||||||
<div className="relative z-10">
|
<div className="relative flex flex-col">
|
||||||
<div className="relative flex flex-col">
|
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
||||||
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
<div className="-mt-4 flex items-start gap-3">
|
||||||
<div className="-mt-4 flex items-start gap-3">
|
<div className="w-10 shrink-0" />
|
||||||
<div className="w-10 shrink-0" />
|
<div className="flex-1">
|
||||||
<div className="flex-1">
|
<MemoizedTextNote content={event.content} />
|
||||||
<MemoizedTextNote content={event.content} />
|
<NoteActions
|
||||||
<NoteActions
|
id={event.id}
|
||||||
id={event.id}
|
pubkey={event.pubkey}
|
||||||
pubkey={event.pubkey}
|
root={root}
|
||||||
root={root}
|
extraButtons={false}
|
||||||
extraButtons={false}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-14">
|
</div>
|
||||||
{event.replies ? (
|
<div className="pl-[48px]">
|
||||||
event.replies.map((sub) => <SubReply key={sub.id} event={sub} />)
|
<Collapsible.Root open={open} onOpenChange={setOpen}>
|
||||||
) : (
|
{event.replies?.length > 0 ? (
|
||||||
<div className="pb-3" />
|
<div>
|
||||||
)}
|
<Collapsible.Trigger asChild>
|
||||||
</div>
|
<div className="inline-flex h-10 items-center gap-1 font-semibold text-blue-500">
|
||||||
|
<NavArrowDownIcon
|
||||||
|
className={twMerge('h-3 w-3', open ? 'rotate-180 transform' : '')}
|
||||||
|
/>
|
||||||
|
{event.replies?.length +
|
||||||
|
' ' +
|
||||||
|
(event.replies?.length === 1 ? 'reply' : 'replies')}
|
||||||
|
</div>
|
||||||
|
</Collapsible.Trigger>
|
||||||
|
<Collapsible.Content>
|
||||||
|
{event.replies?.map((sub) => <SubReply key={sub.id} event={sub} />)}
|
||||||
|
</Collapsible.Content>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Collapsible.Root>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { NoteSkeleton, Reply } from '@shared/notes';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
import { Reply } from '@shared/notes';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
import { NDKEventWithReplies } from '@utils/types';
|
import { NDKEventWithReplies } from '@utils/types';
|
||||||
|
|
||||||
export function RepliesList({ id }: { id: string }) {
|
export function ReplyList({ id }: { id: string }) {
|
||||||
const { fetchAllReplies, sub } = useNostr();
|
const { fetchAllReplies, sub } = useNostr();
|
||||||
const [data, setData] = useState<null | NDKEventWithReplies[]>(null);
|
const [data, setData] = useState<null | NDKEventWithReplies[]>(null);
|
||||||
|
|
||||||
@ -36,33 +37,28 @@ export function RepliesList({ id }: { id: string }) {
|
|||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-5 pb-10">
|
<div className="mt-3">
|
||||||
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
<div className="flex h-16 items-center justify-center rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||||
<NoteSkeleton />
|
<LoaderIcon className="h-5 w-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-5 pb-10">
|
<div className="mt-3 flex flex-col gap-5">
|
||||||
<h5 className="mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
{data?.length === 0 ? (
|
||||||
{data?.length || 0} replies
|
<div className="mt-2 flex w-full items-center justify-center rounded-xl bg-neutral-400 dark:bg-neutral-600">
|
||||||
</h5>
|
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||||
<div className="flex flex-col gap-2">
|
<h3 className="text-3xl">👋</h3>
|
||||||
{data?.length === 0 ? (
|
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
<div className="mt-2 flex w-full items-center justify-center rounded-xl bg-neutral-400 dark:bg-neutral-600">
|
Share your thought on it...
|
||||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
</p>
|
||||||
<h3 className="text-3xl">👋</h3>
|
|
||||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
|
||||||
Share your thought on it...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
data.map((event) => <Reply key={event.id} event={event} root={id} />)
|
) : (
|
||||||
)}
|
data.map((event) => <Reply key={event.id} event={event} root={id} />)
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { User } from '@shared/user';
|
|||||||
|
|
||||||
export function SubReply({ event }: { event: NDKEvent }) {
|
export function SubReply({ event }: { event: NDKEvent }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 mb-3 mt-5 flex flex-col">
|
<div className="mb-3 flex flex-col">
|
||||||
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
||||||
<div className="-mt-4 flex items-start gap-3">
|
<div className="-mt-4 flex items-start gap-3">
|
||||||
<div className="w-10 shrink-0" />
|
<div className="w-10 shrink-0" />
|
||||||
|
@ -32,6 +32,7 @@ export const User = memo(function User({
|
|||||||
| 'chat'
|
| 'chat'
|
||||||
| 'large'
|
| 'large'
|
||||||
| 'thread'
|
| 'thread'
|
||||||
|
| 'miniavatar'
|
||||||
| 'avatar'
|
| 'avatar'
|
||||||
| 'stacked'
|
| 'stacked'
|
||||||
| 'ministacked';
|
| 'ministacked';
|
||||||
@ -207,6 +208,28 @@ export const User = memo(function User({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (variant === 'miniavatar') {
|
||||||
|
return (
|
||||||
|
<Avatar.Root>
|
||||||
|
<Avatar.Image
|
||||||
|
src={user?.picture || user?.image}
|
||||||
|
alt={pubkey}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
style={{ contentVisibility: 'auto' }}
|
||||||
|
className="h-10 w-10 rounded-lg"
|
||||||
|
/>
|
||||||
|
<Avatar.Fallback delayMs={300}>
|
||||||
|
<img
|
||||||
|
src={svgURI}
|
||||||
|
alt={pubkey}
|
||||||
|
className="h-10 w-10 rounded-lg bg-black dark:bg-white"
|
||||||
|
/>
|
||||||
|
</Avatar.Fallback>
|
||||||
|
</Avatar.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (variant === 'stacked') {
|
if (variant === 'stacked') {
|
||||||
return (
|
return (
|
||||||
<Avatar.Root>
|
<Avatar.Root>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MemoizedArticleNote,
|
MemoizedArticleNote,
|
||||||
MemoizedFileNote,
|
MemoizedFileNote,
|
||||||
@ -12,8 +11,7 @@ import {
|
|||||||
NoteStats,
|
NoteStats,
|
||||||
UnknownNote,
|
UnknownNote,
|
||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { RepliesList } from '@shared/notes/replies/list';
|
import { ReplyList } from '@shared/notes/replies/list';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
@ -22,7 +20,6 @@ import { useEvent } from '@utils/hooks/useEvent';
|
|||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function LocalThreadWidget({ params }: { params: Widget }) {
|
export function LocalThreadWidget({ params }: { params: Widget }) {
|
||||||
const { db } = useStorage();
|
|
||||||
const { status, data } = useEvent(params.content);
|
const { status, data } = useEvent(params.content);
|
||||||
|
|
||||||
const renderKind = useCallback(
|
const renderKind = useCallback(
|
||||||
@ -44,31 +41,22 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div className="h-full overflow-y-auto scrollbar-none">
|
<div className="h-full overflow-y-auto px-3 scrollbar-none">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-16 items-center justify-center rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||||
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
<LoaderIcon className="h-5 w-5 animate-spin" />
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-min w-full px-3">
|
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||||
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
<User pubkey={data.pubkey} time={data.created_at} variant="thread" />
|
||||||
<User pubkey={data.pubkey} time={data.created_at} variant="thread" />
|
<div className="mt-2">{renderKind(data)}</div>
|
||||||
<div className="mt-2">{renderKind(data)}</div>
|
<NoteActions id={params.content} pubkey={data.pubkey} extraButtons={false} />
|
||||||
<NoteActions
|
|
||||||
id={params.content}
|
|
||||||
pubkey={data.pubkey}
|
|
||||||
extraButtons={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="px-3">
|
<NoteStats id={params.content} />
|
||||||
<NoteStats id={params.content} />
|
<hr className="my-4 h-px w-full border-none bg-neutral-100" />
|
||||||
<NoteReplyForm id={params.content} pubkey={db.account.pubkey} />
|
<NoteReplyForm id={params.content} />
|
||||||
<RepliesList id={params.content} />
|
<ReplyList id={params.content} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { create } from 'zustand';
|
|
||||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
||||||
|
|
||||||
interface SidebarState {
|
|
||||||
feeds: boolean;
|
|
||||||
chats: boolean;
|
|
||||||
communities: boolean;
|
|
||||||
integrations: boolean;
|
|
||||||
toggleFeeds: () => void;
|
|
||||||
toggleChats: () => void;
|
|
||||||
toggleCommunities: () => void;
|
|
||||||
toggleIntegrations: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSidebar = create<SidebarState>()(
|
|
||||||
persist(
|
|
||||||
(set) => ({
|
|
||||||
feeds: true,
|
|
||||||
chats: false,
|
|
||||||
communities: true,
|
|
||||||
integrations: true,
|
|
||||||
toggleFeeds: () => set((state) => ({ feeds: !state.feeds })),
|
|
||||||
toggleChats: () => set((state) => ({ chats: !state.chats })),
|
|
||||||
toggleCommunities: () => set((state) => ({ communities: !state.communities })),
|
|
||||||
toggleIntegrations: () => set((state) => ({ integrations: !state.integrations })),
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: 'sidebar',
|
|
||||||
storage: createJSONStorage(() => localStorage),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
Loading…
Reference in New Issue
Block a user