add user page

This commit is contained in:
Ren Amamiya
2023-06-28 17:23:36 +07:00
parent 3fe601cfc6
commit ec1ff9ab87
27 changed files with 492 additions and 491 deletions

View File

@@ -61,15 +61,16 @@ const ImagePreview = ({
export function Post() {
const publish = usePublish();
const [repost, toggle] = useComposer((state) => [
state.repost,
state.toggleModal,
]);
const editor = useMemo(
() => withReact(withImages(withHistory(createEditor()))),
[],
);
const [repost, reply, toggle] = useComposer((state) => [
state.repost,
state.reply,
state.toggleModal,
]);
const [content, setContent] = useState<Node[]>([
{
children: [
@@ -84,6 +85,18 @@ export function Post() {
return nodes.map((n) => Node.string(n)).join("\n");
}, []);
const getRef = () => {
if (repost.id) {
return repost.id;
} else if (reply.id) {
return reply.id;
} else {
return null;
}
};
const refID = getRef();
const submit = () => {
let tags: string[][] = [];
let kind: number;
@@ -94,6 +107,20 @@ export function Post() {
["e", repost.id, FULL_RELAYS[0], "root"],
["p", repost.pubkey],
];
} else if (reply.id && reply.pubkey) {
kind = 1;
if (reply.root && reply.root !== reply.id) {
tags = [
["e", reply.id, FULL_RELAYS[0], "root"],
["e", reply.root, FULL_RELAYS[0], "reply"],
["p", reply.pubkey],
];
} else {
tags = [
["e", reply.id, FULL_RELAYS[0], "root"],
["p", reply.pubkey],
];
}
} else {
kind = 1;
tags = [];
@@ -130,14 +157,14 @@ export function Post() {
<div className="w-full">
<Editable
autoFocus
placeholder="What's on your mind?"
placeholder={
refID ? "Share your thoughts on it" : "What's on your mind?"
}
spellCheck="false"
className={`${
repost.id ? "!min-h-42" : "!min-h-[86px]"
} markdown`}
className={`${refID ? "!min-h-42" : "!min-h-[86px]"} markdown`}
renderElement={renderElement}
/>
{repost.id && <MentionNote id={repost.id} />}
{refID && <MentionNote id={refID} />}
</div>
</div>
<div className="mt-4 flex items-center justify-between">

View File

@@ -2,11 +2,22 @@ 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";
export function Kind1({
content,
truncate = false,
}: { content: any; truncate?: boolean }) {
}: {
content: {
original: string;
parsed: ReactNode[];
notes: string[];
images: string[];
videos: string[];
links: string[];
};
truncate?: boolean;
}) {
return (
<>
<div
@@ -16,28 +27,15 @@ export function Kind1({
>
{content.parsed}
</div>
{Array.isArray(content.images) && content.images.length ? (
{content.images.length > 0 && (
<ImagePreview urls={content.images} truncate={truncate} />
) : (
<></>
)}
{Array.isArray(content.videos) && content.videos.length ? (
<VideoPreview urls={content.videos} />
) : (
<></>
)}
{Array.isArray(content.links) && content.links.length ? (
<LinkPreview urls={content.links} />
) : (
<></>
)}
{Array.isArray(content.notes) && content.notes.length ? (
{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} />
))
) : (
<></>
)}
))}
</>
);
}

View File

@@ -8,12 +8,8 @@ import { useEvent } from "@utils/hooks/useEvent";
import { memo } from "react";
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
const { status, data } = useEvent(id);
const kind1 = data?.kind === 1 ? data.content : null;
const kind1063 = data?.kind === 1063 ? data.tags : null;
const queryClient = useQueryClient();
const { status, data } = useEvent(id);
const block = useMutation({
mutationFn: (data: any) => {
@@ -37,17 +33,19 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
<div
onClick={(e) => openThread(e, id)}
onKeyDown={(e) => openThread(e, id)}
className="mt-3 rounded-lg bg-zinc-800 border-t border-zinc-700/50 px-3 py-3"
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">
{kind1 && <Kind1 content={kind1} truncate={true} />}
{kind1063 && <Kind1063 metadata={kind1063} />}
{!kind1 && !kind1063 && (
{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">
@@ -64,6 +62,8 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
)}
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);

View File

@@ -11,12 +11,12 @@ import { useContext } from "react";
export function NoteMetadata({
id,
rootID,
eventPubkey,
currentBlock,
}: {
id: string;
rootID?: string;
eventPubkey: string;
currentBlock?: number;
}) {
const ndk = useContext(RelayContext);
const { status, data } = useQuery(["note-metadata", id], async () => {
@@ -112,8 +112,9 @@ export function NoteMetadata({
<>
<NoteReply
id={id}
rootID={rootID}
pubkey={eventPubkey}
replies={data.replies}
currentBlock={currentBlock}
/>
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
<NoteZap zaps={data.zap} />

View File

@@ -1,36 +1,19 @@
import { createBlock } from "@libs/storage";
import { ReplyIcon } from "@shared/icons";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useComposer } from "@stores/composer";
import { compactNumber } from "@utils/number";
export function NoteReply({
id,
rootID,
pubkey,
replies,
}: { id: string; replies: number; currentBlock?: number }) {
const queryClient = useQueryClient();
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();
}
};
}: { id: string; rootID?: string; pubkey: string; replies: number }) {
const setReply = useComposer((state) => state.setReply);
return (
<button
type="button"
onClick={(e) => openThread(e, id)}
onClick={() => setReply(id, rootID, pubkey)}
className="w-20 group inline-flex items-center gap-1.5"
>
<ReplyIcon

View File

@@ -7,7 +7,7 @@ export function NoteRepost({
pubkey,
reposts,
}: { id: string; pubkey: string; reposts: number }) {
const setRepost = useComposer((state: any) => state.setRepost);
const setRepost = useComposer((state) => state.setRepost);
return (
<button

View File

@@ -18,7 +18,7 @@ export function Note({ event, block }: Note) {
const renderParent = useMemo(() => {
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
return <NoteParent id={event.parent_id} currentBlock={block} />;
return <NoteParent id={event.parent_id} />;
} else {
return null;
}
@@ -26,7 +26,7 @@ export function Note({ event, block }: Note) {
const renderRepost = useMemo(() => {
if (isRepost) {
return <Repost event={event} currentBlock={block} />;
return <Repost event={event} />;
} else {
return null;
}
@@ -71,13 +71,13 @@ export function Note({ event, block }: Note) {
time={event.created_at}
repost={isRepost}
/>
<div className="relative -mt-6 pl-[49px]">
<div className="-mt-6 pl-[49px]">
{renderContent}
{!isRepost && (
<NoteMetadata
id={event.event_id}
rootID={event.parent_id}
eventPubkey={event.pubkey}
currentBlock={block || 1}
/>
)}
</div>

View File

@@ -5,21 +5,18 @@ import { NoteSkeleton } from "@shared/notes/skeleton";
import { User } from "@shared/user";
import { useEvent } from "@utils/hooks/useEvent";
export function NoteParent({
id,
currentBlock,
}: { id: string; currentBlock: number }) {
export function NoteParent({ id }: { id: string }) {
const { status, data } = useEvent(id);
return (
<div className="relative overflow-hidden flex flex-col pb-6">
<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="z-10 relative -mt-6 pl-[49px]">
<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 && (
@@ -40,10 +37,11 @@ export function NoteParent({
<NoteMetadata
id={data.event_id || data.id}
eventPubkey={data.pubkey}
currentBlock={currentBlock}
/>
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);

View File

@@ -20,7 +20,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
</div>
) : (
<a
className="flex flex-col rounded-lg border border-zinc-800/50 hover:border-fuchsia-900"
className="flex flex-col rounded-lg border border-zinc-800/50"
href={urls[0]}
target="_blank"
rel="noreferrer"

View File

@@ -7,10 +7,7 @@ import { useEvent } from "@utils/hooks/useEvent";
import { getRepostID } from "@utils/transform";
import { LumeEvent } from "@utils/types";
export function Repost({
event,
currentBlock,
}: { event: LumeEvent; currentBlock?: number }) {
export function Repost({ event }: { event: LumeEvent }) {
const repostID = getRepostID(event.tags);
const { status, data } = useEvent(repostID);
@@ -19,10 +16,10 @@ export function Repost({
<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="z-10 relative -mt-6 pl-[49px]">
<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 && (
@@ -43,10 +40,11 @@ export function Repost({
<NoteMetadata
id={data.event_id || data.id}
eventPubkey={data.pubkey}
currentBlock={currentBlock}
/>
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);

View File

@@ -20,12 +20,31 @@ export function User({
repost?: boolean;
isChat?: boolean;
}) {
const { user } = useProfile(pubkey);
const { status, user } = useProfile(pubkey);
const createdAt = formatCreatedAt(time, isChat);
const avatarWidth = size === "small" ? "w-6" : "w-11";
const avatarHeight = size === "small" ? "h-6" : "h-11";
if (status === "loading") {
return (
<div
className={`relative flex gap-3 ${
size === "small" ? "items-center" : "items-start"
}`}
>
<div
className={`${avatarWidth} ${avatarHeight} ${
size === "small" ? "rounded" : "rounded-lg"
} relative z-10 bg-zinc-800 animate-pulse shrink-0 overflow-hidden`}
/>
<div className="flex flex-wrap items-baseline gap-1">
<div className="w-36 h-3.5 rounded bg-zinc-800 animate-pulse" />
</div>
</div>
);
}
return (
<Popover
className={`relative flex gap-3 ${
@@ -47,10 +66,10 @@ export function User({
<div className="flex flex-wrap items-baseline gap-1">
<h5
className={`text-zinc-100 font-semibold leading-none truncate ${
size === "small" ? "max-w-[7rem]" : "max-w-[10rem]"
size === "small" ? "max-w-[8rem]" : "max-w-[15rem]"
}`}
>
{user?.nip05 || user?.name || shortenKey(pubkey)}
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
</h5>
{repost && (
<span className="font-semibold leading-none text-fuchsia-500">
@@ -70,8 +89,8 @@ export function User({
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 top-10 z-50 mt-3">
<div className="w-full max-w-xs overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
<Popover.Panel className="absolute z-50 top-10 mt-3">
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={user?.image}
@@ -81,7 +100,7 @@ export function User({
/>
<div className="flex-1 flex flex-col gap-2">
<div className="inline-flex flex-col gap-1">
<h5 className="font-semibold leading-none">
<h5 className="font-semibold text-sm leading-none">
{user?.displayName || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
@@ -91,7 +110,7 @@ export function User({
</span>
</div>
<div>
<p className="line-clamp-3 break-words text-sm leading-tight text-zinc-100">
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
{user?.about}
</p>
</div>