mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 03:03:31 +00:00
add note page
This commit is contained in:
parent
a30cf66c2e
commit
6590ea29e2
@ -12,11 +12,10 @@
|
||||
"format": "prettier ./src --write"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/*.{ts, tsx}": "eslint --fix",
|
||||
"src/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
||||
"**/*.{ts, tsx}": "eslint --fix",
|
||||
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.23.1",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@nostr-dev-kit/ndk": "0.6.0",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
|
@ -1,9 +1,6 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
dependencies:
|
||||
'@floating-ui/react':
|
||||
specifier: ^0.23.1
|
||||
version: 0.23.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@headlessui/react':
|
||||
specifier: ^1.7.15
|
||||
version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -547,17 +544,6 @@ packages:
|
||||
'@floating-ui/core': 1.3.1
|
||||
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):
|
||||
resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==}
|
||||
peerDependencies:
|
||||
@ -569,19 +555,6 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
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):
|
||||
resolution: {integrity: sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==}
|
||||
engines: {node: '>=10'}
|
||||
@ -5167,10 +5140,6 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
dev: false
|
||||
|
||||
/tailwind-merge@1.13.2:
|
||||
resolution: {integrity: sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==}
|
||||
dev: false
|
||||
|
@ -13,6 +13,7 @@ import { WelcomeScreen } from '@app/auth/welcome';
|
||||
import { ChannelScreen } from '@app/channel';
|
||||
import { ChatScreen } from '@app/chat';
|
||||
import { ErrorScreen } from '@app/error';
|
||||
import { NoteScreen } from '@app/note';
|
||||
import { Root } from '@app/root';
|
||||
import { AccountSettingsScreen } from '@app/settings/account';
|
||||
import { GeneralSettingsScreen } from '@app/settings/general';
|
||||
@ -74,6 +75,7 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
{ path: 'space', element: <SpaceScreen /> },
|
||||
{ path: 'trending', element: <TrendingScreen /> },
|
||||
{ path: 'note/:id', element: <NoteScreen /> },
|
||||
{ path: 'user/:pubkey', element: <UserScreen /> },
|
||||
{ path: 'chat/:pubkey', element: <ChatScreen /> },
|
||||
{ path: 'channel/:id', element: <ChannelScreen /> },
|
||||
|
@ -4,7 +4,6 @@ import { Fragment, useContext, useState } from 'react';
|
||||
|
||||
import { CancelIcon, HideIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
import { Tooltip } from '@shared/tooltip_dep';
|
||||
|
||||
import { useChannelMessages } from '@stores/channels';
|
||||
|
||||
@ -51,15 +50,13 @@ export function MessageHideButton({ id }: { id: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip message="Hide this message">
|
||||
<button
|
||||
type="button"
|
||||
onClick={openModal}
|
||||
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" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openModal}
|
||||
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" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
|
@ -4,7 +4,6 @@ import { Fragment, useContext, useState } from 'react';
|
||||
|
||||
import { CancelIcon, MuteIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
import { Tooltip } from '@shared/tooltip_dep';
|
||||
|
||||
import { useChannelMessages } from '@stores/channels';
|
||||
|
||||
@ -51,15 +50,13 @@ export function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip message="Mute this user">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
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" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
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" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ReplyMessageIcon } from '@shared/icons';
|
||||
import { Tooltip } from '@shared/tooltip_dep';
|
||||
|
||||
import { useChannelMessages } from '@stores/channels';
|
||||
|
||||
@ -19,14 +18,12 @@ export function MessageReplyButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip message="Reply to message">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createReply()}
|
||||
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" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createReply()}
|
||||
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" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
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 { Link } from 'react-router-dom';
|
||||
|
||||
import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
||||
|
||||
@ -58,6 +59,7 @@ export function ThreadBlock({ params }: { params: any }) {
|
||||
id={data.event_id || params.content}
|
||||
eventPubkey={data.pubkey}
|
||||
/>
|
||||
<Link to={`/app/note/${params.content}`}>Focus</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 rounded-md bg-zinc-900">
|
||||
|
@ -143,13 +143,18 @@ export function UserScreen() {
|
||||
<button
|
||||
type="button"
|
||||
className={`${
|
||||
selected
|
||||
? 'border-fuchsia-500 text-fuchsia-500'
|
||||
: 'border-transparent text-zinc-200'
|
||||
} inline-flex h-10 items-center gap-2 border-t font-medium`}
|
||||
selected ? 'border-fuchsia-500' : 'border-transparent'
|
||||
} inline-flex h-16 items-start gap-2 border-t pt-4 font-medium`}
|
||||
>
|
||||
<ThreadsIcon className="h-4 w-4" />
|
||||
Activities from 48 hours ago
|
||||
<ThreadsIcon className="h-3.5 w-3.5" />
|
||||
<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>
|
||||
)}
|
||||
</Tab>
|
||||
|
@ -81,12 +81,7 @@ export function NoteMetadata({
|
||||
});
|
||||
|
||||
const openThread = (thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
};
|
||||
|
||||
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