mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
add note page
This commit is contained in:
parent
a30cf66c2e
commit
6590ea29e2
@ -12,11 +12,10 @@
|
|||||||
"format": "prettier ./src --write"
|
"format": "prettier ./src --write"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"src/*.{ts, tsx}": "eslint --fix",
|
"**/*.{ts, tsx}": "eslint --fix",
|
||||||
"src/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.23.1",
|
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@nostr-dev-kit/ndk": "0.6.0",
|
"@nostr-dev-kit/ndk": "0.6.0",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react':
|
|
||||||
specifier: ^0.23.1
|
|
||||||
version: 0.23.1(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@headlessui/react':
|
'@headlessui/react':
|
||||||
specifier: ^1.7.15
|
specifier: ^1.7.15
|
||||||
version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
|
version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -547,17 +544,6 @@ packages:
|
|||||||
'@floating-ui/core': 1.3.1
|
'@floating-ui/core': 1.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@floating-ui/react-dom@1.3.0(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16.8.0'
|
|
||||||
react-dom: '>=16.8.0'
|
|
||||||
dependencies:
|
|
||||||
'@floating-ui/dom': 1.4.3
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0):
|
/@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==}
|
resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -569,19 +555,6 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@floating-ui/react@0.23.1(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-OCc2ViQOBUKOGcE9NLAbpyqB+8Zz92IKIhxgz7XAkynKkVzcVSKtkWOcgyvO4SAzB2OybgRwk3WdzdzDRdh2QQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16.8.0'
|
|
||||||
react-dom: '>=16.8.0'
|
|
||||||
dependencies:
|
|
||||||
'@floating-ui/react-dom': 1.3.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
aria-hidden: 1.2.3
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
tabbable: 6.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@headlessui/react@1.7.15(react-dom@18.2.0)(react@18.2.0):
|
/@headlessui/react@1.7.15(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==}
|
resolution: {integrity: sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -5167,10 +5140,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
/tabbable@6.2.0:
|
|
||||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tailwind-merge@1.13.2:
|
/tailwind-merge@1.13.2:
|
||||||
resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
|
resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -13,6 +13,7 @@ import { WelcomeScreen } from '@app/auth/welcome';
|
|||||||
import { ChannelScreen } from '@app/channel';
|
import { ChannelScreen } from '@app/channel';
|
||||||
import { ChatScreen } from '@app/chat';
|
import { ChatScreen } from '@app/chat';
|
||||||
import { ErrorScreen } from '@app/error';
|
import { ErrorScreen } from '@app/error';
|
||||||
|
import { NoteScreen } from '@app/note';
|
||||||
import { Root } from '@app/root';
|
import { Root } from '@app/root';
|
||||||
import { AccountSettingsScreen } from '@app/settings/account';
|
import { AccountSettingsScreen } from '@app/settings/account';
|
||||||
import { GeneralSettingsScreen } from '@app/settings/general';
|
import { GeneralSettingsScreen } from '@app/settings/general';
|
||||||
@ -74,6 +75,7 @@ const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{ path: 'space', element: <SpaceScreen /> },
|
{ path: 'space', element: <SpaceScreen /> },
|
||||||
{ path: 'trending', element: <TrendingScreen /> },
|
{ path: 'trending', element: <TrendingScreen /> },
|
||||||
|
{ path: 'note/:id', element: <NoteScreen /> },
|
||||||
{ path: 'user/:pubkey', element: <UserScreen /> },
|
{ path: 'user/:pubkey', element: <UserScreen /> },
|
||||||
{ path: 'chat/:pubkey', element: <ChatScreen /> },
|
{ path: 'chat/:pubkey', element: <ChatScreen /> },
|
||||||
{ path: 'channel/:id', element: <ChannelScreen /> },
|
{ path: 'channel/:id', element: <ChannelScreen /> },
|
||||||
|
@ -4,7 +4,6 @@ import { Fragment, useContext, useState } from 'react';
|
|||||||
|
|
||||||
import { CancelIcon, HideIcon } from '@shared/icons';
|
import { CancelIcon, HideIcon } from '@shared/icons';
|
||||||
import { RelayContext } from '@shared/relayProvider';
|
import { RelayContext } from '@shared/relayProvider';
|
||||||
import { Tooltip } from '@shared/tooltip_dep';
|
|
||||||
|
|
||||||
import { useChannelMessages } from '@stores/channels';
|
import { useChannelMessages } from '@stores/channels';
|
||||||
|
|
||||||
@ -51,15 +50,13 @@ export function MessageHideButton({ id }: { id: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip message="Hide this message">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={openModal}
|
||||||
onClick={openModal}
|
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
||||||
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
>
|
||||||
>
|
<HideIcon width={16} height={16} className="text-zinc-200" />
|
||||||
<HideIcon width={16} height={16} className="text-zinc-200" />
|
</button>
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -4,7 +4,6 @@ import { Fragment, useContext, useState } from 'react';
|
|||||||
|
|
||||||
import { CancelIcon, MuteIcon } from '@shared/icons';
|
import { CancelIcon, MuteIcon } from '@shared/icons';
|
||||||
import { RelayContext } from '@shared/relayProvider';
|
import { RelayContext } from '@shared/relayProvider';
|
||||||
import { Tooltip } from '@shared/tooltip_dep';
|
|
||||||
|
|
||||||
import { useChannelMessages } from '@stores/channels';
|
import { useChannelMessages } from '@stores/channels';
|
||||||
|
|
||||||
@ -51,15 +50,13 @@ export function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip message="Mute this user">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => openModal()}
|
||||||
onClick={() => openModal()}
|
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
||||||
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
>
|
||||||
>
|
<MuteIcon width={16} height={16} className="text-zinc-200" />
|
||||||
<MuteIcon width={16} height={16} className="text-zinc-200" />
|
</button>
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ReplyMessageIcon } from '@shared/icons';
|
import { ReplyMessageIcon } from '@shared/icons';
|
||||||
import { Tooltip } from '@shared/tooltip_dep';
|
|
||||||
|
|
||||||
import { useChannelMessages } from '@stores/channels';
|
import { useChannelMessages } from '@stores/channels';
|
||||||
|
|
||||||
@ -19,14 +18,12 @@ export function MessageReplyButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip message="Reply to message">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => createReply()}
|
||||||
onClick={() => createReply()}
|
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
||||||
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800"
|
>
|
||||||
>
|
<ReplyMessageIcon width={16} height={16} className="text-zinc-200" />
|
||||||
<ReplyMessageIcon width={16} height={16} className="text-zinc-200" />
|
</button>
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
60
src/app/note/index.tsx
Normal file
60
src/app/note/index.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
||||||
|
|
||||||
|
import { getNoteByID } from '@libs/storage';
|
||||||
|
|
||||||
|
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||||
|
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||||
|
import { NoteMetadata } from '@shared/notes/metadata';
|
||||||
|
import { NoteReplyForm } from '@shared/notes/replies/form';
|
||||||
|
import { RepliesList } from '@shared/notes/replies/list';
|
||||||
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { useAccount } from '@utils/hooks/useAccount';
|
||||||
|
import { parser } from '@utils/parser';
|
||||||
|
|
||||||
|
export function NoteScreen() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { account } = useAccount();
|
||||||
|
const { status, data } = useQuery(['thread', id], async () => {
|
||||||
|
const res = await getNoteByID(id);
|
||||||
|
res['content'] = parser(res);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
useLiveThread(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-[600px]">
|
||||||
|
<div className="scrollbar-hide flex h-full w-full flex-col gap-1.5 overflow-y-auto pb-20 pt-16">
|
||||||
|
{status === 'loading' ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="shadow-input rounded-md bg-zinc-900 px-3 py-3 shadow-black/20">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="rounded-md bg-zinc-900 px-5 pt-5">
|
||||||
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
|
<div className="mt-3">
|
||||||
|
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||||
|
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||||
|
<NoteMetadata id={data.event_id || id} eventPubkey={data.pubkey} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 rounded-md bg-zinc-900">
|
||||||
|
{account && <NoteReplyForm rootID={id} userPubkey={account.pubkey} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="px-3">
|
||||||
|
<RepliesList parent_id={id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ export function ThreadBlock({ params }: { params: any }) {
|
|||||||
id={data.event_id || params.content}
|
id={data.event_id || params.content}
|
||||||
eventPubkey={data.pubkey}
|
eventPubkey={data.pubkey}
|
||||||
/>
|
/>
|
||||||
|
<Link to={`/app/note/${params.content}`}>Focus</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 rounded-md bg-zinc-900">
|
<div className="mt-3 rounded-md bg-zinc-900">
|
||||||
|
@ -143,13 +143,18 @@ export function UserScreen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${
|
className={`${
|
||||||
selected
|
selected ? 'border-fuchsia-500' : 'border-transparent'
|
||||||
? 'border-fuchsia-500 text-fuchsia-500'
|
} inline-flex h-16 items-start gap-2 border-t pt-4 font-medium`}
|
||||||
: 'border-transparent text-zinc-200'
|
|
||||||
} inline-flex h-10 items-center gap-2 border-t font-medium`}
|
|
||||||
>
|
>
|
||||||
<ThreadsIcon className="h-4 w-4" />
|
<ThreadsIcon className="h-3.5 w-3.5" />
|
||||||
Activities from 48 hours ago
|
<div className="flex flex-col justify-start gap-0.5 text-start">
|
||||||
|
<p className="text-sm font-medium leading-none text-zinc-200">
|
||||||
|
Activities
|
||||||
|
</p>
|
||||||
|
<span className="text-sm leading-none text-zinc-500">
|
||||||
|
48 hours ago
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -81,12 +81,7 @@ export function NoteMetadata({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openThread = (thread: string) => {
|
const openThread = (thread: string) => {
|
||||||
const selection = window.getSelection();
|
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||||
if (selection.toString().length === 0) {
|
|
||||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
|
||||||
} else {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import {
|
|
||||||
autoUpdate,
|
|
||||||
offset,
|
|
||||||
shift,
|
|
||||||
useFloating,
|
|
||||||
useFocus,
|
|
||||||
useHover,
|
|
||||||
useInteractions,
|
|
||||||
} from '@floating-ui/react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export function Tooltip({
|
|
||||||
children,
|
|
||||||
message,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
message: string;
|
|
||||||
}) {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const { x, y, strategy, refs, context } = useFloating({
|
|
||||||
open: isOpen,
|
|
||||||
onOpenChange: setIsOpen,
|
|
||||||
placement: 'top',
|
|
||||||
middleware: [offset(8), shift()],
|
|
||||||
whileElementsMounted(...args) {
|
|
||||||
const cleanup = autoUpdate(...args, { animationFrame: true });
|
|
||||||
return cleanup;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const hover = useHover(context);
|
|
||||||
const focus = useFocus(context);
|
|
||||||
|
|
||||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div ref={refs.setReference} {...getReferenceProps()}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
{isOpen && (
|
|
||||||
<div
|
|
||||||
ref={refs.setFloating}
|
|
||||||
className="w-max select-none rounded-md border border-zinc-700 bg-zinc-800 px-4 py-2 text-sm font-medium leading-none text-zinc-100"
|
|
||||||
style={{
|
|
||||||
position: strategy,
|
|
||||||
top: y ?? 0,
|
|
||||||
left: x ?? 0,
|
|
||||||
}}
|
|
||||||
{...getFloatingProps()}
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user