rome -> eslint + prettier
This commit is contained in:
@@ -1,41 +1,40 @@
|
||||
import { MentionNote } from "@shared/notes/mentions/note";
|
||||
import { ImagePreview } from "@shared/notes/preview/image";
|
||||
import { LinkPreview } from "@shared/notes/preview/link";
|
||||
import { VideoPreview } from "@shared/notes/preview/video";
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { MentionNote } from '@shared/notes/mentions/note';
|
||||
import { ImagePreview } from '@shared/notes/preview/image';
|
||||
import { LinkPreview } from '@shared/notes/preview/link';
|
||||
import { VideoPreview } from '@shared/notes/preview/video';
|
||||
|
||||
export function Kind1({
|
||||
content,
|
||||
truncate = false,
|
||||
content,
|
||||
truncate = false,
|
||||
}: {
|
||||
content: {
|
||||
original: string;
|
||||
parsed: ReactNode[];
|
||||
notes: string[];
|
||||
images: string[];
|
||||
videos: string[];
|
||||
links: string[];
|
||||
};
|
||||
truncate?: boolean;
|
||||
content: {
|
||||
original: string;
|
||||
parsed: ReactNode[];
|
||||
notes: string[];
|
||||
images: string[];
|
||||
videos: string[];
|
||||
links: string[];
|
||||
};
|
||||
truncate?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
|
||||
truncate ? "line-clamp-3" : ""
|
||||
}`}
|
||||
>
|
||||
{content.parsed}
|
||||
</div>
|
||||
{content.images.length > 0 && (
|
||||
<ImagePreview urls={content.images} truncate={truncate} />
|
||||
)}
|
||||
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||
{content.notes.length > 0 &&
|
||||
content.notes.map((note: string) => (
|
||||
<MentionNote key={note} id={note} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
|
||||
truncate ? 'line-clamp-3' : ''
|
||||
}`}
|
||||
>
|
||||
{content.parsed}
|
||||
</div>
|
||||
{content.images.length > 0 && (
|
||||
<ImagePreview urls={content.images} truncate={truncate} />
|
||||
)}
|
||||
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||
{content.notes.length > 0 &&
|
||||
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { NDKTag } from "@nostr-dev-kit/ndk";
|
||||
import { Image } from "@shared/image";
|
||||
import { NDKTag } from '@nostr-dev-kit/ndk';
|
||||
|
||||
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: NDKTag[] }) {
|
||||
const url = metadata[0][1];
|
||||
const url = metadata[0][1];
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{isImage(url) && (
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className="h-auto w-full rounded-lg object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{isImage(url) && (
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className="h-auto w-full rounded-lg object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,70 +1,73 @@
|
||||
import { createBlock } from "@libs/storage";
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { memo } from "react";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { createBlock } from '@libs/storage';
|
||||
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
|
||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { status, data } = useEvent(id);
|
||||
const queryClient = useQueryClient();
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["blocks"] });
|
||||
},
|
||||
});
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blocks'] });
|
||||
},
|
||||
});
|
||||
|
||||
const openThread = (event: any, thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: "Thread", content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
const openThread = (event: any, thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => openThread(e, id)}
|
||||
onKeyDown={(e) => openThread(e, id)}
|
||||
className="mt-3 rounded-lg bg-zinc-800/50 border-t border-zinc-700/50 px-3 py-3"
|
||||
>
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||
<div className="mt-2">
|
||||
{data.kind === 1 && (
|
||||
<Kind1 content={data.content} truncate={true} />
|
||||
)}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => openThread(e, id)}
|
||||
onKeyDown={(e) => openThread(e, id)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="mt-3 rounded-lg border-t border-zinc-700/50 bg-zinc-800/50 px-3 py-3"
|
||||
>
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||
<div className="mt-2">
|
||||
{data.kind === 1 && <Kind1 content={data.content} truncate={true} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
export function MentionUser({ pubkey }: { pubkey: string }) {
|
||||
const { user } = useProfile(pubkey);
|
||||
const { user } = useProfile(pubkey);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="text-fuchsia-500 hover:text-fuchsia-600 no-underline font-normal"
|
||||
>
|
||||
@{user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600"
|
||||
>
|
||||
@{user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
import { createBlock, createReplyNote } from "@libs/storage";
|
||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from "@shared/icons";
|
||||
import { ThreadIcon } from "@shared/icons/thread";
|
||||
import { NoteReply } from "@shared/notes/metadata/reply";
|
||||
import { NoteRepost } from "@shared/notes/metadata/repost";
|
||||
import { NoteZap } from "@shared/notes/metadata/zap";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { decode } from "light-bolt11-decoder";
|
||||
import { useContext } from "react";
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { decode } from 'light-bolt11-decoder';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { createBlock, createReplyNote } from '@libs/storage';
|
||||
|
||||
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
|
||||
import { ThreadIcon } from '@shared/icons/thread';
|
||||
import { NoteReply } from '@shared/notes/metadata/reply';
|
||||
import { NoteRepost } from '@shared/notes/metadata/repost';
|
||||
import { NoteZap } from '@shared/notes/metadata/zap';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
export function NoteMetadata({
|
||||
id,
|
||||
rootID,
|
||||
eventPubkey,
|
||||
id,
|
||||
rootID,
|
||||
eventPubkey,
|
||||
}: {
|
||||
id: string;
|
||||
rootID?: string;
|
||||
eventPubkey: string;
|
||||
id: string;
|
||||
rootID?: string;
|
||||
eventPubkey: string;
|
||||
}) {
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { status, data } = useQuery(["note-metadata", id], async () => {
|
||||
let replies = 0;
|
||||
let reposts = 0;
|
||||
let zap = 0;
|
||||
const { status, data } = useQuery(['note-metadata', id], async () => {
|
||||
let replies = 0;
|
||||
let reposts = 0;
|
||||
let zap = 0;
|
||||
|
||||
const filter: NDKFilter = {
|
||||
"#e": [id],
|
||||
kinds: [1, 6, 9735],
|
||||
};
|
||||
const filter: NDKFilter = {
|
||||
'#e': [id],
|
||||
kinds: [1, 6, 9735],
|
||||
};
|
||||
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
events.forEach((event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
replies += 1;
|
||||
createReplyNote(
|
||||
id,
|
||||
event.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
reposts += 1;
|
||||
break;
|
||||
case 9735: {
|
||||
const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")[1];
|
||||
if (bolt11) {
|
||||
const decoded = decode(bolt11);
|
||||
const amount = decoded.sections.find(
|
||||
(item) => item.name === "amount",
|
||||
);
|
||||
const sats = amount.value / 1000;
|
||||
zap += sats;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
events.forEach((event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
replies += 1;
|
||||
createReplyNote(
|
||||
id,
|
||||
event.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
reposts += 1;
|
||||
break;
|
||||
case 9735: {
|
||||
const bolt11 = event.tags.find((tag) => tag[0] === 'bolt11')[1];
|
||||
if (bolt11) {
|
||||
const decoded = decode(bolt11);
|
||||
const amount = decoded.sections.find((item) => item.name === 'amount');
|
||||
const sats = amount.value / 1000;
|
||||
zap += sats;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return { replies, reposts, zap };
|
||||
});
|
||||
return { replies, reposts, zap };
|
||||
});
|
||||
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["blocks"] });
|
||||
},
|
||||
});
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blocks'] });
|
||||
},
|
||||
});
|
||||
|
||||
const openThread = (thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: "Thread", content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
const openThread = (thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="inline-flex items-center w-full h-12 mt-2">
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<ReplyIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<RepostIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<ZapIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="mt-2 inline-flex h-12 w-full items-center">
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<ReplyIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<RepostIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<ZapIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<div className="inline-flex items-center justify-between w-full h-12 mt-2">
|
||||
<div className="inline-flex justify-between items-center">
|
||||
<NoteReply
|
||||
id={id}
|
||||
rootID={rootID}
|
||||
pubkey={eventPubkey}
|
||||
replies={data.replies}
|
||||
/>
|
||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
||||
<NoteZap zaps={data.zap} />
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openThread(id)}
|
||||
className="w-6 h-6 inline-flex items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
|
||||
>
|
||||
<ThreadIcon className="w-4 h-4 text-zinc-400" />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Open thread
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<div className="mt-2 inline-flex h-12 w-full items-center justify-between">
|
||||
<div className="inline-flex items-center justify-between">
|
||||
<NoteReply
|
||||
id={id}
|
||||
rootID={rootID}
|
||||
pubkey={eventPubkey}
|
||||
replies={data.replies}
|
||||
/>
|
||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
||||
<NoteZap zaps={data.zap} />
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openThread(id)}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
|
||||
>
|
||||
<ThreadIcon className="h-4 w-4 text-zinc-400" />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={5}
|
||||
>
|
||||
Open thread
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { ReplyIcon } from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { ReplyIcon } from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteReply({
|
||||
id,
|
||||
rootID,
|
||||
pubkey,
|
||||
replies,
|
||||
}: { id: string; rootID?: string; pubkey: string; replies: number }) {
|
||||
const setReply = useComposer((state) => state.setReply);
|
||||
id,
|
||||
rootID,
|
||||
pubkey,
|
||||
replies,
|
||||
}: {
|
||||
id: string;
|
||||
rootID?: string;
|
||||
pubkey: string;
|
||||
replies: number;
|
||||
}) {
|
||||
const setReply = useComposer((state) => state.setReply);
|
||||
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReply(id, rootID, pubkey)}
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<ReplyIcon className="w-4 h-4 text-zinc-400 group-hover:text-green-500" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(replies)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Quick reply
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReply(id, rootID, pubkey)}
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<ReplyIcon className="h-4 w-4 text-zinc-400 group-hover:text-green-500" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(replies)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={5}
|
||||
>
|
||||
Quick reply
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,40 +1,47 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { RepostIcon } from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { RepostIcon } from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteRepost({
|
||||
id,
|
||||
pubkey,
|
||||
reposts,
|
||||
}: { id: string; pubkey: string; reposts: number }) {
|
||||
const setRepost = useComposer((state) => state.setRepost);
|
||||
id,
|
||||
pubkey,
|
||||
reposts,
|
||||
}: {
|
||||
id: string;
|
||||
pubkey: string;
|
||||
reposts: number;
|
||||
}) {
|
||||
const setRepost = useComposer((state) => state.setRepost);
|
||||
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRepost(id, pubkey)}
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<RepostIcon className="w-4 h-4 text-zinc-400 group-hover:text-blue-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(reposts)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Repost
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRepost(id, pubkey)}
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<RepostIcon className="h-4 w-4 text-zinc-400 group-hover:text-blue-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(reposts)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={5}
|
||||
>
|
||||
Repost
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { ZapIcon } from "@shared/icons";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { ZapIcon } from '@shared/icons';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteZap({ zaps }: { zaps: number }) {
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<ZapIcon className="w-4 h-4 text-zinc-400 group-hover:text-orange-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(zaps)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Coming Soon
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<ZapIcon className="h-4 w-4 text-zinc-400 group-hover:text-orange-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(zaps)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={5}
|
||||
>
|
||||
Coming Soon
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,89 +1,86 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteParent } from "@shared/notes/parent";
|
||||
import { Repost } from "@shared/notes/repost";
|
||||
import { User } from "@shared/user";
|
||||
import { parser } from "@utils/parser";
|
||||
import { LumeEvent } from "@utils/types";
|
||||
import { useMemo } from "react";
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteParent } from '@shared/notes/parent';
|
||||
import { Repost } from '@shared/notes/repost';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
|
||||
interface Note {
|
||||
event: LumeEvent;
|
||||
block?: number;
|
||||
event: LumeEvent;
|
||||
}
|
||||
|
||||
export function Note({ event, block }: Note) {
|
||||
const isRepost = event.kind === 6;
|
||||
export function Note({ event }: Note) {
|
||||
const isRepost = event.kind === 6;
|
||||
|
||||
const renderParent = useMemo(() => {
|
||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
||||
return <NoteParent id={event.parent_id} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.parent_id]);
|
||||
const renderParent = useMemo(() => {
|
||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
||||
return <NoteParent id={event.parent_id} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.parent_id]);
|
||||
|
||||
const renderRepost = useMemo(() => {
|
||||
if (isRepost) {
|
||||
return <Repost event={event} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.kind]);
|
||||
const renderRepost = useMemo(() => {
|
||||
if (isRepost) {
|
||||
return <Repost event={event} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.kind]);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
const content = parser(event);
|
||||
return <Kind1 content={content} />;
|
||||
}
|
||||
case 6:
|
||||
return null;
|
||||
case 1063:
|
||||
return <Kind1063 metadata={event.tags} />;
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {event.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{event.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [event.kind]);
|
||||
const renderContent = useMemo(() => {
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
const content = parser(event);
|
||||
return <Kind1 content={content} />;
|
||||
}
|
||||
case 6:
|
||||
return null;
|
||||
case 1063:
|
||||
return <Kind1063 metadata={event.tags} />;
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {event.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{event.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [event.kind]);
|
||||
|
||||
return (
|
||||
<div className="h-min w-full px-3 py-1.5">
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||
{renderParent}
|
||||
<div className="flex flex-col">
|
||||
<User
|
||||
pubkey={event.pubkey}
|
||||
time={event.created_at}
|
||||
repost={isRepost}
|
||||
/>
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{renderContent}
|
||||
{!isRepost && (
|
||||
<NoteMetadata
|
||||
id={event.event_id}
|
||||
rootID={event.parent_id}
|
||||
eventPubkey={event.pubkey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{renderRepost}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="h-min w-full px-3 py-1.5">
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||
{renderParent}
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={event.pubkey} time={event.created_at} repost={isRepost} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{renderContent}
|
||||
{!isRepost && (
|
||||
<NoteMetadata
|
||||
id={event.event_id}
|
||||
rootID={event.parent_id}
|
||||
eventPubkey={event.pubkey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{renderRepost}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,48 +1,46 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
|
||||
export function NoteParent({ id }: { id: string }) {
|
||||
const { status, data } = useEvent(id);
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col pb-6">
|
||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata
|
||||
id={data.event_id || data.id}
|
||||
eventPubkey={data.pubkey}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative flex flex-col pb-6">
|
||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
export function ImagePreview({
|
||||
urls,
|
||||
truncate,
|
||||
}: { urls: string[]; truncate?: boolean }) {
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden">
|
||||
<div className="flex flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="min-w-0 grow-0 shrink-0 basis-full">
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className={`${
|
||||
truncate ? "h-auto max-h-[300px]" : "h-auto"
|
||||
} w-full border border-zinc-800/50 rounded-lg object-cover`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export function ImagePreview({ urls, truncate }: { urls: string[]; truncate?: boolean }) {
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden">
|
||||
<div className="flex flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="min-w-0 shrink-0 grow-0 basis-full">
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className={`${
|
||||
truncate ? 'h-auto max-h-[300px]' : 'h-auto'
|
||||
} w-full rounded-lg border border-zinc-800/50 object-cover`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,62 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { useOpenGraph } from "@utils/hooks/useOpenGraph";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useOpenGraph } from '@utils/hooks/useOpenGraph';
|
||||
|
||||
export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
const domain = new URL(urls[0]);
|
||||
const { status, data, error } = useOpenGraph(urls[0]);
|
||||
const domain = new URL(urls[0]);
|
||||
const { status, data, error } = useOpenGraph(urls[0]);
|
||||
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
|
||||
{status === "loading" ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full h-44 bg-zinc-700 animate-pulse" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="w-2/3 h-3 rounded bg-zinc-700 animate-pulse" />
|
||||
<div className="w-3/4 h-3 rounded bg-zinc-700 animate-pulse" />
|
||||
<span className="mt-2.5 leading-none text-sm text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
className="flex flex-col rounded-lg border border-zinc-800/50"
|
||||
href={urls[0]}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{error ? (
|
||||
<div className="px-3 py-3">
|
||||
<p className="text-sm text-zinc-400 break-all line-clamp-3">
|
||||
Can't fetch open graph, click to open webpage
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
src={
|
||||
data.images?.[0] ||
|
||||
"https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt={urls[0]}
|
||||
className="w-full h-44 object-cover rounded-t-lg"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<h5 className="leading-none font-medium text-zinc-200 line-clamp-1">
|
||||
{data.title}
|
||||
</h5>
|
||||
{data.description && (
|
||||
<p className="text-sm text-zinc-400 break-all line-clamp-3">
|
||||
{data.description}
|
||||
</p>
|
||||
)}
|
||||
<span className="mt-2.5 leading-none text-sm text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
|
||||
{status === 'loading' ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="h-44 w-full animate-pulse bg-zinc-700" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-zinc-700" />
|
||||
<div className="h-3 w-3/4 animate-pulse rounded bg-zinc-700" />
|
||||
<span className="mt-2.5 text-sm leading-none text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
className="flex flex-col rounded-lg border border-zinc-800/50"
|
||||
href={urls[0]}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{error ? (
|
||||
<div className="px-3 py-3">
|
||||
<p className="line-clamp-3 break-all text-sm text-zinc-400">
|
||||
Can't fetch open graph, click to open webpage
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
src={data.images?.[0] || 'https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW'}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt={urls[0]}
|
||||
className="h-44 w-full rounded-t-lg object-cover"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<h5 className="line-clamp-1 font-medium leading-none text-zinc-200">
|
||||
{data.title}
|
||||
</h5>
|
||||
{data.description && (
|
||||
<p className="line-clamp-3 break-all text-sm text-zinc-400">
|
||||
{data.description}
|
||||
</p>
|
||||
)}
|
||||
<span className="mt-2.5 text-sm leading-none text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import ReactPlayer from "react-player/es6";
|
||||
import ReactPlayer from 'react-player/es6';
|
||||
|
||||
export function VideoPreview({ urls }: { urls: string[] }) {
|
||||
return (
|
||||
<div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<ReactPlayer
|
||||
key={url}
|
||||
url={url}
|
||||
width="100%"
|
||||
height="auto"
|
||||
className="!h-auto object-fill rounded-lg overflow-hidden"
|
||||
controls={true}
|
||||
pip={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative mt-3 flex w-full max-w-[420px] flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<ReactPlayer
|
||||
key={url}
|
||||
url={url}
|
||||
width="100%"
|
||||
height="auto"
|
||||
className="!h-auto overflow-hidden rounded-lg object-fill"
|
||||
controls={true}
|
||||
pip={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,77 +1,82 @@
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { Button } from "@shared/button";
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR, FULL_RELAYS } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { useState } from "react";
|
||||
import { useState } from 'react';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR, FULL_RELAYS } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
export function NoteReplyForm({
|
||||
rootID,
|
||||
userPubkey,
|
||||
}: { rootID: string; userPubkey: string }) {
|
||||
const publish = usePublish();
|
||||
const { status, user } = useProfile(userPubkey);
|
||||
const [value, setValue] = useState("");
|
||||
rootID,
|
||||
userPubkey,
|
||||
}: {
|
||||
rootID: string;
|
||||
userPubkey: string;
|
||||
}) {
|
||||
const publish = usePublish();
|
||||
const { status, user } = useProfile(userPubkey);
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
const submit = () => {
|
||||
const tags = [["e", rootID, FULL_RELAYS[0], "root"]];
|
||||
const submit = () => {
|
||||
const tags = [['e', rootID, FULL_RELAYS[0], 'root']];
|
||||
|
||||
// publish event
|
||||
publish({ content: value, kind: 1, tags });
|
||||
// publish event
|
||||
publish({ content: value, kind: 1, tags });
|
||||
|
||||
// reset form
|
||||
setValue("");
|
||||
};
|
||||
// reset form
|
||||
setValue('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative w-full flex-1 overflow-hidden">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this thread..."
|
||||
className="relative h-20 w-full resize-none rounded-md px-5 py-3 text-base bg-transparent !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-zinc-800 w-full py-3 px-5">
|
||||
{status === "loading" ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="relative h-9 w-9 shrink-0 rounded">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={userPubkey}
|
||||
className="h-9 w-9 rounded-md bg-white object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-px leading-none text-sm text-zinc-400">
|
||||
Reply as
|
||||
</p>
|
||||
<p className="leading-none text-sm font-medium text-zinc-100">
|
||||
{user.nip05 || user.name || shortenKey(userPubkey)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => submit()}
|
||||
disabled={value.length === 0 ? true : false}
|
||||
preset="publish"
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative w-full flex-1 overflow-hidden">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this thread..."
|
||||
className="relative h-20 w-full resize-none rounded-md bg-transparent px-5 py-3 text-base !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-t border-zinc-800 px-5 py-3">
|
||||
{status === 'loading' ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="relative h-9 w-9 shrink-0 rounded">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={userPubkey}
|
||||
className="h-9 w-9 rounded-md bg-white object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-px text-sm leading-none text-zinc-400">Reply as</p>
|
||||
<p className="text-sm font-medium leading-none text-zinc-100">
|
||||
{user.nip05 || user.name || shortenKey(userPubkey)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => submit()}
|
||||
disabled={value.length === 0 ? true : false}
|
||||
preset="publish"
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { User } from "@shared/user";
|
||||
import { parser } from "@utils/parser";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
|
||||
export function Reply({ data }: { data: any }) {
|
||||
const content = parser(data);
|
||||
const content = parser(data);
|
||||
|
||||
return (
|
||||
<div className="flex h-min min-h-min w-full select-text flex-col px-3 pt-5 mb-3 rounded-md bg-zinc-900">
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-[20px] pl-[50px]">
|
||||
<Kind1 content={content} />
|
||||
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mb-3 flex h-min min-h-min w-full select-text flex-col rounded-md bg-zinc-900 px-3 pt-5">
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-[20px] pl-[50px]">
|
||||
<Kind1 content={content} />
|
||||
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { getReplies } from "@libs/storage";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { Reply } from "@shared/notes/replies/item";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { getReplies } from '@libs/storage';
|
||||
|
||||
import { Reply } from '@shared/notes/replies/item';
|
||||
|
||||
export function RepliesList({ parent_id }: { parent_id: string }) {
|
||||
const { status, data } = useQuery(["replies", parent_id], async () => {
|
||||
return await getReplies(parent_id);
|
||||
});
|
||||
const { status, data } = useQuery(['replies', parent_id], async () => {
|
||||
return await getReplies(parent_id);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="mb-2">
|
||||
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{status === "loading" ? (
|
||||
<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-base">
|
||||
<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>
|
||||
) : data.length === 0 ? (
|
||||
<div className="px=3">
|
||||
<div className="w-full flex items-center justify-center rounded-md bg-zinc-900">
|
||||
<div className="py-6 flex flex-col items-center justify-center gap-2">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-zinc-400">
|
||||
Share your thought on it...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
|
||||
)}
|
||||
</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="flex flex-col">
|
||||
{status === 'loading' ? (
|
||||
<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-base">
|
||||
<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>
|
||||
) : data.length === 0 ? (
|
||||
<div className="px=3">
|
||||
<div className="flex w-full items-center justify-center rounded-md bg-zinc-900">
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-zinc-400">Share your thought on it...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { getRepostID } from "@utils/transform";
|
||||
import { LumeEvent } from "@utils/types";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { getRepostID } from '@utils/transform';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
|
||||
export function Repost({ event }: { event: LumeEvent }) {
|
||||
const repostID = getRepostID(event.tags);
|
||||
const { status, data } = useEvent(repostID);
|
||||
const repostID = getRepostID(event.tags);
|
||||
const { status, data } = useEvent(repostID);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col mt-12">
|
||||
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata
|
||||
id={data.event_id || data.id}
|
||||
eventPubkey={data.pubkey}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative mt-12 flex flex-col">
|
||||
<div className="absolute -top-10 left-[18px] h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
export function NoteSkeleton() {
|
||||
return (
|
||||
<div className="flex h-min flex-col pb-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="h-4 w-20 rounded bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 animate-pulse pl-[49px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="h-4 w-full rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 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-start gap-3">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="h-4 w-20 rounded bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 animate-pulse pl-[49px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="h-4 w-full rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-1/2 rounded-sm bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user