replace eslint/prettier with rome

This commit is contained in:
Ren Amamiya
2023-05-14 17:05:53 +07:00
parent 48d690d33a
commit 409a625dcc
154 changed files with 7639 additions and 8525 deletions

View File

@@ -1 +1 @@
export { LayoutNewsfeed as Layout } from './layout';
export { LayoutNewsfeed as Layout } from "./layout";

View File

@@ -1,38 +1,41 @@
import { Kind1 } from '@app/note/components/kind1';
import { Kind1063 } from '@app/note/components/kind1063';
import NoteMetadata from '@app/note/components/metadata';
import { NoteParent } from '@app/note/components/parent';
import { NoteDefaultUser } from '@app/note/components/user/default';
import { NoteWrapper } from '@app/note/components/wrapper';
import { Kind1 } from "@app/note/components/kind1";
import { Kind1063 } from "@app/note/components/kind1063";
import NoteMetadata from "@app/note/components/metadata";
import { NoteParent } from "@app/note/components/parent";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { NoteWrapper } from "@app/note/components/wrapper";
import { noteParser } from '@utils/parser';
import { isTagsIncludeID } from '@utils/transform';
import { noteParser } from "@utils/parser";
import { isTagsIncludeID } from "@utils/transform";
import { useMemo } from 'react';
import { useMemo } from "react";
export function NoteBase({ event }: { event: any }) {
const content = useMemo(() => noteParser(event), [event]);
const checkParentID = isTagsIncludeID(event.parent_id, event.tags);
const content = useMemo(() => noteParser(event), [event]);
const checkParentID = isTagsIncludeID(event.parent_id, event.tags);
const href = event.parent_id ? `/app/note?id=${event.parent_id}` : `/app/note?id=${event.event_id}`;
const href = event.parent_id
? `/app/note?id=${event.parent_id}`
: `/app/note?id=${event.event_id}`;
return (
<NoteWrapper href={href} className="h-min w-full px-3 py-1.5">
<div className="rounded-md border border-zinc-800 bg-zinc-900 px-3 pt-3 shadow-input shadow-black/20">
{event.parent_id && (event.parent_id !== event.event_id || checkParentID) ? (
<NoteParent id={event.parent_id} />
) : (
<></>
)}
<div className="flex flex-col">
<NoteDefaultUser pubkey={event.pubkey} time={event.created_at} />
<div className="mt-3 pl-[46px]">
{event.kind === 1 && <Kind1 content={content} />}
{event.kind === 1063 && <Kind1063 metadata={event.tags} />}
<NoteMetadata id={event.event_id} eventPubkey={event.pubkey} />
</div>
</div>
</div>
</NoteWrapper>
);
return (
<NoteWrapper href={href} className="h-min w-full px-3 py-1.5">
<div className="rounded-md border border-zinc-800 bg-zinc-900 px-3 pt-3 shadow-input shadow-black/20">
{event.parent_id &&
(event.parent_id !== event.event_id || checkParentID) ? (
<NoteParent id={event.parent_id} />
) : (
<></>
)}
<div className="flex flex-col">
<NoteDefaultUser pubkey={event.pubkey} time={event.created_at} />
<div className="mt-3 pl-[46px]">
{event.kind === 1 && <Kind1 content={content} />}
{event.kind === 1063 && <Kind1063 metadata={event.tags} />}
<NoteMetadata id={event.event_id} eventPubkey={event.pubkey} />
</div>
</div>
</div>
</NoteWrapper>
);
}

View File

@@ -1,31 +1,41 @@
import { MentionNote } from '@app/note/components/mentions/note';
import { MentionUser } from '@app/note/components/mentions/user';
import ImagePreview from '@app/note/components/preview/image';
import VideoPreview from '@app/note/components/preview/video';
import { MentionNote } from "@app/note/components/mentions/note";
import { MentionUser } from "@app/note/components/mentions/user";
import ImagePreview from "@app/note/components/preview/image";
import VideoPreview from "@app/note/components/preview/video";
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
export function Kind1({ content }: { content: any }) {
return (
<>
<ReactMarkdown
remarkPlugins={[[remarkGfm]]}
linkTarget="_blank"
className="prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight"
components={{
em: ({ ...props }) => <MentionUser {...props} />,
}}
>
{content.parsed}
</ReactMarkdown>
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
{Array.isArray(content.notes) && content.notes.length ? (
content.notes.map((note: string) => <MentionNote key={note} id={note} />)
) : (
<></>
)}
</>
);
return (
<>
<ReactMarkdown
remarkPlugins={[[remarkGfm]]}
linkTarget="_blank"
className="prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight"
components={{
em: ({ ...props }) => <MentionUser {...props} />,
}}
>
{content.parsed}
</ReactMarkdown>
{Array.isArray(content.images) && content.images.length ? (
<ImagePreview urls={content.images} />
) : (
<></>
)}
{Array.isArray(content.videos) && content.videos.length ? (
<VideoPreview urls={content.videos} />
) : (
<></>
)}
{Array.isArray(content.notes) && content.notes.length ? (
content.notes.map((note: string) => (
<MentionNote key={note} id={note} />
))
) : (
<></>
)}
</>
);
}

View File

@@ -1,15 +1,21 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
function isImage(url: string) {
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
}
export function Kind1063({ metadata }: { metadata: string[] }) {
const url = metadata[0][1];
const url = metadata[0][1];
return (
<div className="mt-3">
{isImage(url) && <Image src={url} alt="image" className="h-auto w-full rounded-lg object-cover" />}
</div>
);
return (
<div className="mt-3">
{isImage(url) && (
<Image
src={url}
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>
)}
</div>
);
}

View File

@@ -1,60 +1,66 @@
import { Kind1 } from '@app/note/components/kind1';
import { Kind1063 } from '@app/note/components/kind1063';
import { NoteSkeleton } from '@app/note/components/skeleton';
import { NoteDefaultUser } from '@app/note/components/user/default';
import { NoteWrapper } from '@app/note/components/wrapper';
import { Kind1 } from "@app/note/components/kind1";
import { Kind1063 } from "@app/note/components/kind1063";
import { NoteSkeleton } from "@app/note/components/skeleton";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { NoteWrapper } from "@app/note/components/wrapper";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { noteParser } from '@utils/parser';
import { noteParser } from "@utils/parser";
import { memo, useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
import { memo, useContext } from "react";
import useSWRSubscription from "swr/subscription";
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const pool: any = useContext(RelayContext);
const { data, error } = useSWRSubscription(id ? id : null, (key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
}
);
const { data, error } = useSWRSubscription(
id ? id : null,
(key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
return (
<NoteWrapper href={`/app/note?id=${id}`} className="mt-3 rounded-lg border border-zinc-800 px-3 py-3">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
</div>
</>
) : (
<NoteSkeleton />
)}
</NoteWrapper>
);
return (
<NoteWrapper
href={`/app/note?id=${id}`}
className="mt-3 rounded-lg border border-zinc-800 px-3 py-3"
>
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
</div>
</>
) : (
<NoteSkeleton />
)}
</NoteWrapper>
);
});

View File

@@ -1,9 +1,13 @@
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
export function MentionUser(props: { children: any[] }) {
const pubkey = props.children[0];
const { user } = useProfile(pubkey);
const pubkey = props.children[0];
const { user } = useProfile(pubkey);
return <span className="text-fuchsia-500">@{user?.name || user?.display_name || shortenKey(pubkey)}</span>;
return (
<span className="text-fuchsia-500">
@{user?.name || user?.display_name || shortenKey(pubkey)}
</span>
);
}

View File

@@ -1,67 +1,79 @@
import NoteLike from '@app/note/components/metadata/like';
import NoteReply from '@app/note/components/metadata/reply';
import NoteRepost from '@app/note/components/metadata/repost';
import NoteLike from "@app/note/components/metadata/like";
import NoteReply from "@app/note/components/metadata/reply";
import NoteRepost from "@app/note/components/metadata/repost";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import ZapIcon from '@icons/zap';
import ZapIcon from "@icons/zap";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { useContext, useState } from 'react';
import useSWRSubscription from 'swr/subscription';
import { useContext, useState } from "react";
import useSWRSubscription from "swr/subscription";
export default function NoteMetadata({ id, eventPubkey }: { id: string; eventPubkey: string }) {
const pool: any = useContext(RelayContext);
export default function NoteMetadata({
id,
eventPubkey,
}: { id: string; eventPubkey: string }) {
const pool: any = useContext(RelayContext);
const [replies, setReplies] = useState(0);
const [reposts, setReposts] = useState(0);
const [likes, setLikes] = useState(0);
const [replies, setReplies] = useState(0);
const [reposts, setReposts] = useState(0);
const [likes, setLikes] = useState(0);
useSWRSubscription(id ? ['note-metadata', id] : null, ([, key], {}) => {
const unsubscribe = pool.subscribe(
[
{
'#e': [key],
since: 0,
kinds: [1, 6, 7],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
case 1:
setReplies((replies) => replies + 1);
break;
case 6:
setReposts((reposts) => reposts + 1);
break;
case 7:
if (event.content === '🤙' || event.content === '+') {
setLikes((likes) => likes + 1);
}
break;
default:
break;
}
}
);
useSWRSubscription(id ? ["note-metadata", id] : null, ([, key]) => {
const unsubscribe = pool.subscribe(
[
{
"#e": [key],
since: 0,
kinds: [1, 6, 7],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
case 1:
setReplies((replies) => replies + 1);
break;
case 6:
setReposts((reposts) => reposts + 1);
break;
case 7:
if (event.content === "🤙" || event.content === "+") {
setLikes((likes) => likes + 1);
}
break;
default:
break;
}
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
});
return (
<div className="mt-4 flex h-12 items-center gap-16 border-t border-zinc-800/50">
<NoteReply id={id} replies={replies} />
<NoteLike id={id} pubkey={eventPubkey} likes={likes} />
<NoteRepost id={id} pubkey={eventPubkey} reposts={reposts} />
<button className="group inline-flex w-min items-center gap-1.5">
<ZapIcon width={20} height={20} className="text-zinc-400 group-hover:text-orange-400" />
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">{0}</span>
</button>
</div>
);
return (
<div className="mt-4 flex h-12 items-center gap-16 border-t border-zinc-800/50">
<NoteReply id={id} replies={replies} />
<NoteLike id={id} pubkey={eventPubkey} likes={likes} />
<NoteRepost id={id} pubkey={eventPubkey} reposts={reposts} />
<button
type="button"
className="group inline-flex w-min items-center gap-1.5"
>
<ZapIcon
width={20}
height={20}
className="text-zinc-400 group-hover:text-orange-400"
/>
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">
{0}
</span>
</button>
</div>
);
}

View File

@@ -1,54 +1,68 @@
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import LikeIcon from '@icons/like';
import LikeIcon from "@icons/like";
import { WRITEONLY_RELAYS } from '@stores/constants';
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from '@utils/date';
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { dateToUnix } from "@utils/date";
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { getEventHash, signEvent } from 'nostr-tools';
import { useContext, useEffect, useState } from 'react';
import { getEventHash, signEvent } from "nostr-tools";
import { useContext, useEffect, useState } from "react";
export default function NoteLike({ id, pubkey, likes }: { id: string; pubkey: string; likes: number }) {
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
export default function NoteLike({
id,
pubkey,
likes,
}: { id: string; pubkey: string; likes: number }) {
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
const [count, setCount] = useState(0);
const [count, setCount] = useState(0);
const submitEvent = (e: any) => {
e.stopPropagation();
const submitEvent = (e: any) => {
e.stopPropagation();
if (!isLoading && !isError && account) {
const event: any = {
content: '+',
kind: 7,
tags: [
['e', id],
['p', pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// update state
setCount(count + 1);
} else {
console.log('error');
}
};
if (!isLoading && !isError && account) {
const event: any = {
content: "+",
kind: 7,
tags: [
["e", id],
["p", pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// update state
setCount(count + 1);
} else {
console.log("error");
}
};
useEffect(() => {
setCount(likes);
}, [likes]);
useEffect(() => {
setCount(likes);
}, [likes]);
return (
<button type="button" onClick={(e) => submitEvent(e)} className="group inline-flex w-min items-center gap-1.5">
<LikeIcon width={16} height={16} className="text-zinc-400 group-hover:text-rose-400" />
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">{count}</span>
</button>
);
return (
<button
type="button"
onClick={(e) => submitEvent(e)}
className="group inline-flex w-min items-center gap-1.5"
>
<LikeIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-rose-400"
/>
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">
{count}
</span>
</button>
);
}

View File

@@ -1,132 +1,150 @@
import { Image } from '@shared/image';
import { RelayContext } from '@shared/relayProvider';
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import ReplyIcon from '@icons/reply';
import ReplyIcon from "@icons/reply";
import { WRITEONLY_RELAYS } from '@stores/constants';
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from '@utils/date';
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { dateToUnix } from "@utils/date";
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { Dialog, Transition } from '@headlessui/react';
import { getEventHash, signEvent } from 'nostr-tools';
import { Fragment, useContext, useEffect, useState } from 'react';
import { Dialog, Transition } from "@headlessui/react";
import { getEventHash, signEvent } from "nostr-tools";
import { Fragment, useContext, useEffect, useState } from "react";
export default function NoteReply({ id, replies }: { id: string; replies: number }) {
const pool: any = useContext(RelayContext);
export default function NoteReply({
id,
replies,
}: { id: string; replies: number }) {
const pool: any = useContext(RelayContext);
const [count, setCount] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState('');
const [count, setCount] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState("");
const { account, isLoading, isError } = useActiveAccount();
const profile = account ? JSON.parse(account.metadata) : null;
const { account, isLoading, isError } = useActiveAccount();
const profile = account ? JSON.parse(account.metadata) : null;
const closeModal = () => {
setIsOpen(false);
};
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openModal = () => {
setIsOpen(true);
};
const submitEvent = () => {
if (!isLoading && !isError && account) {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [['e', id]],
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
const submitEvent = () => {
if (!isLoading && !isError && account) {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [["e", id]],
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish event
pool.publish(event, WRITEONLY_RELAYS);
// close modal
setIsOpen(false);
setCount(count + 1);
} else {
console.log('error');
}
};
// publish event
pool.publish(event, WRITEONLY_RELAYS);
// close modal
setIsOpen(false);
setCount(count + 1);
} else {
console.log("error");
}
};
useEffect(() => {
setCount(replies);
}, [replies]);
useEffect(() => {
setCount(replies);
}, [replies]);
return (
<>
<button type="button" onClick={() => openModal()} className="group inline-flex w-min items-center gap-1.5">
<ReplyIcon width={16} height={16} className="text-zinc-400 group-hover:text-green-400" />
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">{count}</span>
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border border-zinc-800 bg-zinc-900 p-3">
{/* root note */}
{/* comment form */}
<div className="flex gap-2">
<div>
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
<Image src={profile?.picture} alt="user's avatar" className="h-11 w-11 rounded-md object-cover" />
</div>
</div>
<div className="relative h-24 w-full flex-1 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
<div>
<textarea
name="content"
onChange={(e) => setValue(e.target.value)}
placeholder="Send your comment"
className="relative h-24 w-full resize-none rounded-md border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="absolute bottom-2 w-full px-2">
<div className="flex w-full items-center justify-between bg-zinc-800">
<div className="flex items-center gap-2 divide-x divide-zinc-700">
<div className="flex items-center gap-2 pl-2"></div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => submitEvent()}
disabled={value.length === 0 ? true : false}
className="inline-flex h-8 w-16 items-center justify-center rounded-md bg-fuchsia-500 px-4 text-sm font-medium shadow-md shadow-fuchsia-900/50 hover:bg-fuchsia-600"
>
<span className="text-white drop-shadow">Send</span>
</button>
</div>
</div>
</div>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<button
type="button"
onClick={() => openModal()}
className="group inline-flex w-min items-center gap-1.5"
>
<ReplyIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">
{count}
</span>
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border border-zinc-800 bg-zinc-900 p-3">
{/* root note */}
{/* comment form */}
<div className="flex gap-2">
<div>
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
<Image
src={profile?.picture}
alt="user's avatar"
className="h-11 w-11 rounded-md object-cover"
/>
</div>
</div>
<div className="relative h-24 w-full flex-1 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
<div>
<textarea
name="content"
onChange={(e) => setValue(e.target.value)}
placeholder="Send your comment"
className="relative h-24 w-full resize-none rounded-md border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="absolute bottom-2 w-full px-2">
<div className="flex w-full items-center justify-between bg-zinc-800">
<div className="flex items-center gap-2 divide-x divide-zinc-700">
<div className="flex items-center gap-2 pl-2" />
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => submitEvent()}
disabled={value.length === 0 ? true : false}
className="inline-flex h-8 w-16 items-center justify-center rounded-md bg-fuchsia-500 px-4 text-sm font-medium shadow-md shadow-fuchsia-900/50 hover:bg-fuchsia-600"
>
<span className="text-white drop-shadow">Send</span>
</button>
</div>
</div>
</div>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,54 +1,68 @@
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import RepostIcon from '@icons/repost';
import RepostIcon from "@icons/repost";
import { WRITEONLY_RELAYS } from '@stores/constants';
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from '@utils/date';
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { dateToUnix } from "@utils/date";
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { getEventHash, signEvent } from 'nostr-tools';
import { useContext, useEffect, useState } from 'react';
import { getEventHash, signEvent } from "nostr-tools";
import { useContext, useEffect, useState } from "react";
export default function NoteRepost({ id, pubkey, reposts }: { id: string; pubkey: string; reposts: number }) {
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
export default function NoteRepost({
id,
pubkey,
reposts,
}: { id: string; pubkey: string; reposts: number }) {
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
const [count, setCount] = useState(0);
const [count, setCount] = useState(0);
const submitEvent = (e: any) => {
e.stopPropagation();
const submitEvent = (e: any) => {
e.stopPropagation();
if (!isLoading && !isError && account) {
const event: any = {
content: '',
kind: 6,
tags: [
['e', id],
['p', pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// update state
setCount(count + 1);
} else {
console.log('error');
}
};
if (!isLoading && !isError && account) {
const event: any = {
content: "",
kind: 6,
tags: [
["e", id],
["p", pubkey],
],
created_at: dateToUnix(),
pubkey: account.pubkey,
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish event to all relays
pool.publish(event, WRITEONLY_RELAYS);
// update state
setCount(count + 1);
} else {
console.log("error");
}
};
useEffect(() => {
setCount(reposts);
}, [reposts]);
useEffect(() => {
setCount(reposts);
}, [reposts]);
return (
<button type="button" onClick={(e) => submitEvent(e)} className="group inline-flex w-min items-center gap-1.5">
<RepostIcon width={16} height={16} className="text-zinc-400 group-hover:text-blue-400" />
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">{count}</span>
</button>
);
return (
<button
type="button"
onClick={(e) => submitEvent(e)}
className="group inline-flex w-min items-center gap-1.5"
>
<RepostIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-blue-400"
/>
<span className="text-sm leading-none text-zinc-400 group-hover:text-zinc-200">
{count}
</span>
</button>
);
}

View File

@@ -1,62 +1,65 @@
import { Kind1 } from '@app/note/components/kind1';
import { Kind1063 } from '@app/note/components/kind1063';
import NoteMetadata from '@app/note/components/metadata';
import { NoteSkeleton } from '@app/note/components/skeleton';
import { NoteDefaultUser } from '@app/note/components/user/default';
import { Kind1 } from "@app/note/components/kind1";
import { Kind1063 } from "@app/note/components/kind1063";
import NoteMetadata from "@app/note/components/metadata";
import { NoteSkeleton } from "@app/note/components/skeleton";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { noteParser } from '@utils/parser';
import { noteParser } from "@utils/parser";
import { memo, useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
import { memo, useContext } from "react";
import useSWRSubscription from "swr/subscription";
export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const pool: any = useContext(RelayContext);
const { data, error } = useSWRSubscription(id ? id : null, (key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
}
);
const { data, error } = useSWRSubscription(
id ? id : null,
(key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
return (
<div className="relative flex flex-col pb-6">
<div className="absolute left-[16px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<NoteSkeleton />
)}
</div>
);
return (
<div className="relative flex flex-col pb-6">
<div className="absolute left-[16px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<NoteSkeleton />
)}
</div>
);
});

View File

@@ -1,11 +1,15 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
export default function ImagePreview({ urls }: { urls: string[] }) {
return (
<div className="mt-3 grid h-full w-full grid-cols-3">
<div className="col-span-3">
<Image src={urls[0]} alt="image" className="h-auto w-full rounded-lg object-cover" />
</div>
</div>
);
return (
<div className="mt-3 grid h-full w-full grid-cols-3">
<div className="col-span-3">
<Image
src={urls[0]}
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>
</div>
</div>
);
}

View File

@@ -1,14 +1,14 @@
import { MediaOutlet, MediaPlayer } from '@vidstack/react';
import { MediaOutlet, MediaPlayer } from "@vidstack/react";
export default function VideoPreview({ urls }: { urls: string[] }) {
return (
<div
onClick={(e) => e.stopPropagation()}
className="relative mt-2 flex w-full flex-col overflow-hidden rounded-lg bg-zinc-950"
>
<MediaPlayer src={urls[0]} poster="" controls>
<MediaOutlet />
</MediaPlayer>
</div>
);
return (
<div
onKeyDown={(e) => e.stopPropagation()}
className="relative mt-2 flex w-full flex-col overflow-hidden rounded-lg bg-zinc-950"
>
<MediaPlayer src={urls[0]} poster="" controls>
<MediaOutlet />
</MediaPlayer>
</div>
);
}

View File

@@ -1,21 +1,24 @@
import { RootNote } from '@app/note/components/rootNote';
import { NoteRepostUser } from '@app/note/components/user/repost';
import { NoteWrapper } from '@app/note/components/wrapper';
import { RootNote } from "@app/note/components/rootNote";
import { NoteRepostUser } from "@app/note/components/user/repost";
import { NoteWrapper } from "@app/note/components/wrapper";
import { getQuoteID } from '@utils/transform';
import { getQuoteID } from "@utils/transform";
export function NoteQuoteRepost({ event }: { event: any }) {
const rootID = getQuoteID(event.tags);
const rootID = getQuoteID(event.tags);
return (
<NoteWrapper href={`/app/note?id=${rootID}`} className="h-min w-full px-3 py-1.5">
<div className="rounded-md border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
<div className="relative px-3 pb-5 pt-3">
<div className="absolute left-[29px] top-[20px] h-[70px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<NoteRepostUser pubkey={event.pubkey} time={event.created_at} />
</div>
<RootNote id={rootID} fallback={event.content} />
</div>
</NoteWrapper>
);
return (
<NoteWrapper
href={`/app/note?id=${rootID}`}
className="h-min w-full px-3 py-1.5"
>
<div className="rounded-md border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
<div className="relative px-3 pb-5 pt-3">
<div className="absolute left-[29px] top-[20px] h-[70px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
<NoteRepostUser pubkey={event.pubkey} time={event.created_at} />
</div>
<RootNote id={rootID} fallback={event.content} />
</div>
</NoteWrapper>
);
}

View File

@@ -1,74 +1,79 @@
import { Image } from '@shared/image';
import { RelayContext } from '@shared/relayProvider';
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { WRITEONLY_RELAYS } from '@stores/constants';
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from '@utils/date';
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { dateToUnix } from "@utils/date";
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { getEventHash, signEvent } from 'nostr-tools';
import { useContext, useState } from 'react';
import { getEventHash, signEvent } from "nostr-tools";
import { useContext, useState } from "react";
export default function NoteReplyForm({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
const pool: any = useContext(RelayContext);
const { account, isLoading, isError } = useActiveAccount();
const [value, setValue] = useState('');
const profile = account ? JSON.parse(account.metadata) : null;
const [value, setValue] = useState("");
const profile = account ? JSON.parse(account.metadata) : null;
const submitEvent = () => {
if (!isLoading && !isError && account) {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [['e', id]],
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
const submitEvent = () => {
if (!isLoading && !isError && account) {
const event: any = {
content: value,
created_at: dateToUnix(),
kind: 1,
pubkey: account.pubkey,
tags: [["e", id]],
};
event.id = getEventHash(event);
event.sig = signEvent(event, account.privkey);
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// reset form
setValue('');
} else {
console.log('error');
}
};
// publish note
pool.publish(event, WRITEONLY_RELAYS);
// reset form
setValue("");
} else {
console.log("error");
}
};
return (
<div className="flex gap-2.5 px-3 py-4">
<div>
<div className="relative h-9 w-9 shrink-0 overflow-hidden rounded-md">
<Image src={profile?.picture} alt={account?.pubkey} className="h-9 w-9 rounded-md object-cover" />
</div>
</div>
<div className="relative h-24 w-full flex-1 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20">
<div>
<textarea
name="content"
onChange={(e) => setValue(e.target.value)}
placeholder="Reply to this thread..."
className="relative h-24 w-full resize-none rounded-md border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="absolute bottom-2 w-full px-2">
<div className="flex w-full items-center justify-between bg-zinc-800">
<div className="flex items-center gap-2 divide-x divide-zinc-700"></div>
<div className="flex items-center gap-2">
<button
onClick={() => submitEvent()}
disabled={value.length === 0 ? true : false}
className="inline-flex h-8 w-16 items-center justify-center rounded-md bg-fuchsia-500 px-4 text-sm font-medium shadow-button hover:bg-fuchsia-600 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
>
Reply
</button>
</div>
</div>
</div>
</div>
</div>
);
return (
<div className="flex gap-2.5 px-3 py-4">
<div>
<div className="relative h-9 w-9 shrink-0 overflow-hidden rounded-md">
<Image
src={profile?.picture}
alt={account?.pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
</div>
<div className="relative h-24 w-full flex-1 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20">
<div>
<textarea
name="content"
onChange={(e) => setValue(e.target.value)}
placeholder="Reply to this thread..."
className="relative h-24 w-full resize-none rounded-md border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="absolute bottom-2 w-full px-2">
<div className="flex w-full items-center justify-between bg-zinc-800">
<div className="flex items-center gap-2 divide-x divide-zinc-700" />
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => submitEvent()}
disabled={value.length === 0 ? true : false}
className="inline-flex h-8 w-16 items-center justify-center rounded-md bg-fuchsia-500 px-4 text-sm font-medium shadow-button hover:bg-fuchsia-600 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
>
Reply
</button>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,19 +1,19 @@
import { Kind1 } from '@app/note/components/kind1';
import NoteReplyUser from '@app/note/components/user/reply';
import { Kind1 } from "@app/note/components/kind1";
import NoteReplyUser from "@app/note/components/user/reply";
import { noteParser } from '@utils/parser';
import { noteParser } from "@utils/parser";
export default function Reply({ data }: { data: any }) {
const content = noteParser(data);
const content = noteParser(data);
return (
<div className="flex h-min min-h-min w-full select-text flex-col px-3 py-3">
<div className="flex flex-col">
<NoteReplyUser pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-[18px] pl-[46px]">
<Kind1 content={content} />
</div>
</div>
</div>
);
return (
<div className="flex h-min min-h-min w-full select-text flex-col px-3 py-3">
<div className="flex flex-col">
<NoteReplyUser pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-[18px] pl-[46px]">
<Kind1 content={content} />
</div>
</div>
</div>
);
}

View File

@@ -1,66 +1,69 @@
import NoteReplyForm from '@app/note/components/replies/form';
import Reply from '@app/note/components/replies/item';
import NoteReplyForm from "@app/note/components/replies/form";
import Reply from "@app/note/components/replies/item";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { sortEvents } from '@utils/transform';
import { sortEvents } from "@utils/transform";
import { useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
import { useContext } from "react";
import useSWRSubscription from "swr/subscription";
export default function RepliesList({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const pool: any = useContext(RelayContext);
const { data, error } = useSWRSubscription(id ? ['note-replies', id] : null, ([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
'#e': [key],
since: 0,
kinds: [1, 1063],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
}
);
const { data, error } = useSWRSubscription(
id ? ["note-replies", id] : null,
([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
"#e": [key],
since: 0,
kinds: [1, 1063],
limit: 20,
},
],
READONLY_RELAYS,
(event: any) => {
next(null, (prev: any) => (prev ? [...prev, event] : [event]));
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
return (
<div className="mt-5">
<div className="mb-2">
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
</div>
<div className="rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
<div className="flex flex-col divide-y divide-zinc-800">
<NoteReplyForm id={id} />
{error && <div>failed to load</div>}
{!data ? (
<div className="flex gap-2 px-3 py-4">
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800"></div>
<div className="flex w-full flex-1 flex-col justify-center gap-1">
<div className="flex items-baseline gap-2 text-sm">
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800"></div>
</div>
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800"></div>
</div>
</div>
) : (
sortEvents(data).map((event: any) => {
return <Reply key={event.id} data={event} />;
})
)}
</div>
</div>
</div>
);
return (
<div className="mt-5">
<div className="mb-2">
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
</div>
<div className="rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
<div className="flex flex-col divide-y divide-zinc-800">
<NoteReplyForm id={id} />
{error && <div>failed to load</div>}
{!data ? (
<div className="flex gap-2 px-3 py-4">
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800" />
<div className="flex w-full flex-1 flex-col justify-center gap-1">
<div className="flex items-baseline gap-2 text-sm">
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800" />
</div>
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800" />
</div>
</div>
) : (
sortEvents(data).map((event: any) => {
return <Reply key={event.id} data={event} />;
})
)}
</div>
</div>
</div>
);
}

View File

@@ -1,95 +1,107 @@
import { Kind1 } from '@app/note/components/kind1';
import { Kind1063 } from '@app/note/components/kind1063';
import NoteMetadata from '@app/note/components/metadata';
import { NoteSkeleton } from '@app/note/components/skeleton';
import { NoteDefaultUser } from '@app/note/components/user/default';
import { Kind1 } from "@app/note/components/kind1";
import { Kind1063 } from "@app/note/components/kind1063";
import NoteMetadata from "@app/note/components/metadata";
import { NoteSkeleton } from "@app/note/components/skeleton";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { noteParser } from '@utils/parser';
import { noteParser } from "@utils/parser";
import { memo, useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
import { navigate } from 'vite-plugin-ssr/client/router';
import { memo, useContext } from "react";
import useSWRSubscription from "swr/subscription";
import { navigate } from "vite-plugin-ssr/client/router";
function isJSON(str: string) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
export const RootNote = memo(function RootNote({ id, fallback }: { id: string; fallback?: any }) {
const pool: any = useContext(RelayContext);
const parseFallback = isJSON(fallback) ? JSON.parse(fallback) : null;
export const RootNote = memo(function RootNote({
id,
fallback,
}: { id: string; fallback?: any }) {
const pool: any = useContext(RelayContext);
const parseFallback = isJSON(fallback) ? JSON.parse(fallback) : null;
const { data, error } = useSWRSubscription(parseFallback ? null : id, (key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
}
);
const { data, error } = useSWRSubscription(
parseFallback ? null : id,
(key, { next }) => {
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
undefined,
undefined,
{
unsubscribeOnEose: true,
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
const openNote = (e) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
navigate(`/app/note?id=${id}`);
} else {
e.stopPropagation();
}
};
const openNote = (e) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
navigate(`/app/note?id=${id}`);
} else {
e.stopPropagation();
}
};
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
const kind1 = !error && data?.kind === 1 ? noteParser(data) : null;
const kind1063 = !error && data?.kind === 1063 ? data.tags : null;
if (parseFallback) {
const contentFallback = noteParser(parseFallback);
if (parseFallback) {
const contentFallback = noteParser(parseFallback);
return (
<div onClick={(e) => openNote(e)} className="flex flex-col px-3">
<NoteDefaultUser pubkey={parseFallback.pubkey} time={parseFallback.created_at} />
<div className="mt-3 pl-[46px]">
<Kind1 content={contentFallback} />
<NoteMetadata id={parseFallback.id} eventPubkey={parseFallback.pubkey} />
</div>
</div>
);
}
return (
<div onKeyDown={(e) => openNote(e)} className="flex flex-col px-3">
<NoteDefaultUser
pubkey={parseFallback.pubkey}
time={parseFallback.created_at}
/>
<div className="mt-3 pl-[46px]">
<Kind1 content={contentFallback} />
<NoteMetadata
id={parseFallback.id}
eventPubkey={parseFallback.pubkey}
/>
</div>
</div>
);
}
return (
<div onClick={(e) => openNote(e)} className="flex flex-col px-3">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<NoteSkeleton />
)}
</div>
);
return (
<div onKeyDown={(e) => openNote(e)} className="flex flex-col px-3">
{data ? (
<>
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3 pl-[46px]">
{kind1 && <Kind1 content={kind1} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<NoteSkeleton />
)}
</div>
);
});

View File

@@ -1,20 +1,20 @@
export function NoteSkeleton() {
return (
<div className="flex h-min flex-col pb-3">
<div className="flex items-center gap-2.5">
<div className="relative h-9 w-9 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex flex-col gap-0.5">
<div className="h-3 w-20 rounded-sm bg-zinc-700" />
<div className="h-2 w-12 rounded-sm bg-zinc-700" />
</div>
</div>
<div className="mt-3 animate-pulse pl-[46px]">
<div className="flex flex-col gap-1">
<div className="h-4 w-full rounded-sm bg-zinc-700" />
<div className="h-4 w-2/3 rounded-sm bg-zinc-700" />
<div className="h-4 w-1/2 rounded-sm bg-zinc-700" />
</div>
</div>
</div>
);
return (
<div className="flex h-min flex-col pb-3">
<div className="flex items-center gap-2.5">
<div className="relative h-9 w-9 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex flex-col gap-0.5">
<div className="h-3 w-20 rounded-sm bg-zinc-700" />
<div className="h-2 w-12 rounded-sm bg-zinc-700" />
</div>
</div>
<div className="mt-3 animate-pulse pl-[46px]">
<div className="flex flex-col gap-1">
<div className="h-4 w-full rounded-sm bg-zinc-700" />
<div className="h-4 w-2/3 rounded-sm bg-zinc-700" />
<div className="h-4 w-1/2 rounded-sm bg-zinc-700" />
</div>
</div>
</div>
);
}

View File

@@ -1,94 +1,105 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from '@stores/constants';
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Popover, Transition } from '@headlessui/react';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { Fragment } from 'react';
import { Popover, Transition } from "@headlessui/react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { Fragment } from "react";
dayjs.extend(relativeTime);
export function NoteDefaultUser({ pubkey, time }: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
export function NoteDefaultUser({
pubkey,
time,
}: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
return (
<Popover className="relative flex items-center gap-2.5">
<Popover.Button className="h-9 w-9 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-9 w-9 object-cover"
/>
</Popover.Button>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || <div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700"></div>}
</h5>
<div className="flex items-baseline gap-1.5 text-sm leading-none text-zinc-500">
<span>{user?.nip05 || shortenKey(pubkey)}</span>
<span></span>
<span>{dayjs().to(dayjs.unix(time), true)}</span>
</div>
</div>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 top-8 z-10 mt-3 w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl">
<div
onClick={(e) => e.stopPropagation()}
className="w-full max-w-xs overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 shadow-input ring-1 ring-black ring-opacity-5"
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700"></div>
)}
</h5>
<span className="truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 text-sm leading-tight text-zinc-100">{user?.about}</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<a
href={`/app/user?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
View full profile
</a>
<a
href={`/app/chat?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
Message
</a>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
return (
<Popover className="relative flex items-center gap-2.5">
<Popover.Button className="h-9 w-9 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
alt={pubkey}
className="h-9 w-9 object-cover"
/>
</Popover.Button>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
<div className="flex items-baseline gap-1.5 text-sm leading-none text-zinc-500">
<span>{user?.nip05 || shortenKey(pubkey)}</span>
<span></span>
<span>{dayjs().to(dayjs.unix(time), true)}</span>
</div>
</div>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 top-8 z-10 mt-3 w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl">
<div
onKeyDown={(e) => e.stopPropagation()}
className="w-full max-w-xs overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 shadow-input ring-1 ring-black ring-opacity-5"
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
<span className="truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 text-sm leading-tight text-zinc-100">
{user?.about}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<a
href={`/app/user?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
View full profile
</a>
<a
href={`/app/chat?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
Message
</a>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
}

View File

@@ -1,36 +1,43 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from '@stores/constants';
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
export default function NoteReplyUser({ pubkey, time }: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
export default function NoteReplyUser({
pubkey,
time,
}: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
return (
<div className="group flex items-start gap-2.5">
<div className="relative h-9 w-9 shrink-0 rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex items-baseline gap-2 text-sm">
<span className="font-semibold leading-none text-zinc-200 group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">{dayjs().to(dayjs.unix(time), true)}</span>
</div>
</div>
</div>
);
return (
<div className="group flex items-start gap-2.5">
<div className="relative h-9 w-9 shrink-0 rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex items-baseline gap-2 text-sm">
<span className="font-semibold leading-none text-zinc-200 group-hover:underline">
{user?.display_name || user?.name || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">
{dayjs().to(dayjs.unix(time), true)}
</span>
</div>
</div>
</div>
);
}

View File

@@ -1,93 +1,106 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from '@stores/constants';
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Popover, Transition } from '@headlessui/react';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { Fragment } from 'react';
import { Popover, Transition } from "@headlessui/react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { Fragment } from "react";
dayjs.extend(relativeTime);
export function NoteRepostUser({ pubkey, time }: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
export function NoteRepostUser({
pubkey,
time,
}: { pubkey: string; time: number }) {
const { user } = useProfile(pubkey);
return (
<Popover className="relative flex items-center gap-2.5">
<Popover.Button className="h-9 w-9 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</Popover.Button>
<div className="flex items-baseline gap-1.5 text-sm">
<h5 className="font-semibold leading-tight group-hover:underline">
{user?.display_name || user?.name || <div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700"></div>}
<span className="bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-transparent">
{' '}
reposted
</span>
</h5>
<span className="leading-tight text-zinc-500">·</span>
<span className="text-zinc-500">{dayjs().to(dayjs.unix(time), true)}</span>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 top-8 z-10 mt-3 w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl">
<div
onClick={(e) => e.stopPropagation()}
className="w-full max-w-xs overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 shadow-input ring-1 ring-black ring-opacity-5"
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700"></div>
)}
</h5>
<span className="truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 text-sm leading-tight text-zinc-100">{user?.about}</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<a
href={`/app/user?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
View full profile
</a>
<a
href={`/app/chat?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
Message
</a>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
return (
<Popover className="relative flex items-center gap-2.5">
<Popover.Button className="h-9 w-9 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>
</Popover.Button>
<div className="flex items-baseline gap-1.5 text-sm">
<h5 className="font-semibold leading-tight group-hover:underline">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
<span className="bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-transparent">
{" "}
reposted
</span>
</h5>
<span className="leading-tight text-zinc-500">·</span>
<span className="text-zinc-500">
{dayjs().to(dayjs.unix(time), true)}
</span>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 top-8 z-10 mt-3 w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl">
<div
onKeyDown={(e) => e.stopPropagation()}
className="w-full max-w-xs overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900 shadow-input ring-1 ring-black ring-opacity-5"
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<div className="flex w-full flex-1 flex-col gap-2">
<div className="inline-flex w-2/3 flex-col gap-0.5">
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
<span className="truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 text-sm leading-tight text-zinc-100">
{user?.about}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<a
href={`/app/user?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
View full profile
</a>
<a
href={`/app/chat?pubkey=${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-800 text-sm font-medium"
>
Message
</a>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
}

View File

@@ -1,26 +1,26 @@
import { navigate } from 'vite-plugin-ssr/client/router';
import { navigate } from "vite-plugin-ssr/client/router";
export function NoteWrapper({
children,
href,
className,
children,
href,
className,
}: {
children: React.ReactNode;
href: string;
className: string;
children: React.ReactNode;
href: string;
className: string;
}) {
const openThread = (event: any, href: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
navigate(href, { keepScrollPosition: true });
} else {
event.stopPropagation();
}
};
const openThread = (event: any, href: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
navigate(href, { keepScrollPosition: true });
} else {
event.stopPropagation();
}
};
return (
<div onClick={(event) => openThread(event, href)} className={className}>
{children}
</div>
);
return (
<div onKeyDown={(event) => openThread(event, href)} className={className}>
{children}
</div>
);
}

View File

@@ -1,29 +1,31 @@
import AppHeader from '@shared/appHeader';
import MultiAccounts from '@shared/multiAccounts';
import Navigation from '@shared/navigation';
import AppHeader from "@shared/appHeader";
import MultiAccounts from "@shared/multiAccounts";
import Navigation from "@shared/navigation";
export function LayoutNewsfeed({ children }: { children: React.ReactNode }) {
return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
<div className="flex h-screen w-full flex-col">
<div
data-tauri-drag-region
className="relative h-9 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
>
<AppHeader />
</div>
<div className="relative flex min-h-0 w-full flex-1">
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
<MultiAccounts />
</div>
<div className="grid w-full grid-cols-4 xl:grid-cols-5">
<div className="scrollbar-hide col-span-1 overflow-y-auto overflow-x-hidden border-r border-zinc-900">
<Navigation />
</div>
<div className="col-span-3 overflow-hidden xl:col-span-4">{children}</div>
</div>
</div>
</div>
</div>
);
return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
<div className="flex h-screen w-full flex-col">
<div
data-tauri-drag-region
className="relative h-9 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
>
<AppHeader />
</div>
<div className="relative flex min-h-0 w-full flex-1">
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
<MultiAccounts />
</div>
<div className="grid w-full grid-cols-4 xl:grid-cols-5">
<div className="scrollbar-hide col-span-1 overflow-y-auto overflow-x-hidden border-r border-zinc-900">
<Navigation />
</div>
<div className="col-span-3 overflow-hidden xl:col-span-4">
{children}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,86 +1,89 @@
import { Kind1 } from '@app/note/components/kind1';
import NoteMetadata from '@app/note/components/metadata';
import RepliesList from '@app/note/components/replies/list';
import { NoteDefaultUser } from '@app/note/components/user/default';
import { Kind1 } from "@app/note/components/kind1";
import NoteMetadata from "@app/note/components/metadata";
import RepliesList from "@app/note/components/replies/list";
import { NoteDefaultUser } from "@app/note/components/user/default";
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { usePageContext } from '@utils/hooks/usePageContext';
import { noteParser } from '@utils/parser';
import { usePageContext } from "@utils/hooks/usePageContext";
import { noteParser } from "@utils/parser";
import { useContext } from 'react';
import useSWRSubscription from 'swr/subscription';
import { useContext } from "react";
import useSWRSubscription from "swr/subscription";
export function Page() {
const pool: any = useContext(RelayContext);
const pageContext = usePageContext();
const searchParams: any = pageContext.urlParsed.search;
const pool: any = useContext(RelayContext);
const pageContext = usePageContext();
const searchParams: any = pageContext.urlParsed.search;
const noteID = searchParams.id;
const noteID = searchParams.id;
const { data, error } = useSWRSubscription(noteID ? ['note', noteID] : null, ([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
}
);
const { data, error } = useSWRSubscription(
noteID ? ["note", noteID] : null,
([, key], { next }) => {
// subscribe to note
const unsubscribe = pool.subscribe(
[
{
ids: [key],
},
],
READONLY_RELAYS,
(event: any) => {
next(null, event);
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
const content = !error && data ? noteParser(data) : null;
const content = !error && data ? noteParser(data) : null;
return (
<div className="scrollbar-hide h-full w-full overflow-y-auto">
<div className="p-3">
<div className="relative w-full rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
{!data || error ? (
<div className="animated-pulse p-3">
<div className="flex items-start gap-2">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<div className="h-4 w-16 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
<div className="mt-3">
<div className="flex flex-col gap-6">
<div className="h-16 w-full rounded bg-zinc-700" />
<div className="flex items-center gap-8">
<div className="h-4 w-12 rounded bg-zinc-700" />
<div className="h-4 w-12 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
) : (
<div className="relative z-10 flex flex-col">
<div className="px-3 pt-3">
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3">
<Kind1 content={content} />
<NoteMetadata id={noteID} eventPubkey={data.pubkey} />
</div>
</div>
</div>
)}
</div>
<RepliesList id={noteID} />
</div>
</div>
);
return (
<div className="scrollbar-hide h-full w-full overflow-y-auto">
<div className="p-3">
<div className="relative w-full rounded-lg border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
{!data || error ? (
<div className="animated-pulse p-3">
<div className="flex items-start gap-2">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<div className="h-4 w-16 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
<div className="mt-3">
<div className="flex flex-col gap-6">
<div className="h-16 w-full rounded bg-zinc-700" />
<div className="flex items-center gap-8">
<div className="h-4 w-12 rounded bg-zinc-700" />
<div className="h-4 w-12 rounded bg-zinc-700" />
</div>
</div>
</div>
</div>
) : (
<div className="relative z-10 flex flex-col">
<div className="px-3 pt-3">
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3">
<Kind1 content={content} />
<NoteMetadata id={noteID} eventPubkey={data.pubkey} />
</div>
</div>
</div>
)}
</div>
<RepliesList id={noteID} />
</div>
</div>
);
}