refactor note component & add support for kind 30023

This commit is contained in:
Ren Amamiya
2023-08-23 09:48:22 +07:00
parent 0912948b31
commit c97c685149
32 changed files with 714 additions and 593 deletions

View File

@@ -1,9 +1,18 @@
import { NoteActions, NoteContent, NoteSkeleton } from '@shared/notes';
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import {
ArticleNote,
FileNote,
NoteActions,
NoteSkeleton,
TextNote,
UnknownNote,
} from '@shared/notes';
import { User } from '@shared/user';
import { useEvent } from '@utils/hooks/useEvent';
export function SubNote({ id, root }: { id: string; root?: string }) {
export function ChildNote({ id, root }: { id: string; root?: string }) {
const { status, data } = useEvent(id);
if (status === 'loading') {
@@ -22,16 +31,29 @@ export function SubNote({ id, root }: { id: string; root?: string }) {
);
}
const renderKind = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote event={event} />;
case NDKKind.Article:
return <ArticleNote event={event} />;
case 1063:
return <FileNote event={event} />;
default:
return <UnknownNote event={event} />;
}
};
return (
<>
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />
<div className="mb-5 flex flex-col">
<User pubkey={data.event.pubkey} time={data.event.created_at} />
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="relative z-20 flex-1">
<NoteContent content={data.richContent} long={data.event.kind === 30023} />
<NoteActions id={data.event.id} pubkey={data.event.pubkey} root={root} />
{renderKind(data)}
<NoteActions id={data.id} pubkey={data.pubkey} root={root} />
</div>
</div>
</div>

View File

@@ -11,17 +11,19 @@ export * from './replies/form';
export * from './replies/item';
export * from './replies/list';
export * from './replies/sub';
export * from './kinds/kind1';
export * from './kinds/kind1063';
export * from './kinds/text';
export * from './kinds/file';
export * from './kinds/article';
export * from './kinds/unknown';
export * from './metadata';
export * from './users/mini';
export * from './users/repost';
export * from './users/thread';
export * from './kinds/thread';
export * from './kinds/repost';
export * from './kinds/sub';
export * from './child';
export * from './skeleton';
export * from './actions';
export * from './content';
export * from './mentions/hashtag';
export * from './stats';
export * from './wrapper';

View File

@@ -0,0 +1,52 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { Image } from '@shared/image';
export function ArticleNote({ event }: { event: NDKEvent }) {
const metadata = useMemo(() => {
const title = event.tags.find((tag) => tag[0] === 'title')?.[1];
const image = event.tags.find((tag) => tag[0] === 'image')?.[1];
const summary = event.tags.find((tag) => tag[0] === 'summary')?.[1];
let publishedAt: Date | string | number = event.tags.find(
(tag) => tag[0] === 'published_at'
)?.[1];
if (publishedAt) {
publishedAt = new Date(parseInt(publishedAt)).toLocaleDateString('en-US');
} else {
publishedAt = new Date(event.created_at * 1000).toLocaleDateString('en-US');
}
return {
title,
image,
publishedAt,
summary,
};
}, [event.id]);
return (
<div className="mb-2 mt-3 rounded-lg bg-white/10">
<div className="flex flex-col rounded-lg">
<Image
src={metadata.image}
alt={metadata.title}
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-white">
{metadata.title}
</h5>
<p className="line-clamp-3 break-all text-sm text-white/50">
{metadata.summary}
</p>
<span className="mt-2.5 text-sm leading-none text-white/50">
{metadata.publishedAt.toString()}
</span>
</div>
</div>
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { User } from '@shared/user';
import { isImage } from '@utils/isImage';
export function NoteKind_1063({ event }: { event: NDKEvent }) {
export function FileNote({ event }: { event: NDKEvent }) {
const url = event.tags.find((el) => el[0] === 'url')[1];
return (
@@ -20,7 +20,6 @@ export function NoteKind_1063({ event }: { event: NDKEvent }) {
{isImage(url) && (
<Image
src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>

View File

@@ -1,39 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { NoteActions, NoteContent, NoteMetadata } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
export function NoteKind_1({
event,
skipMetadata = false,
}: {
event: NDKEvent;
skipMetadata?: boolean;
}) {
const content = useMemo(() => parser(event), [event.id]);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="relative flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="relative z-20 flex-1">
<NoteContent content={content} />
<NoteActions id={event.id || event.id} pubkey={event.pubkey} />
</div>
</div>
{!skipMetadata ? (
<NoteMetadata id={event.id || event.id} />
) : (
<div className="pb-3" />
)}
</div>
</div>
</div>
);
}

View File

@@ -1,11 +1,14 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import {
ArticleNote,
FileNote,
NoteActions,
NoteContent,
NoteMetadata,
NoteSkeleton,
RepostUser,
TextNote,
UnknownNote,
} from '@shared/notes';
import { User } from '@shared/user';
@@ -40,23 +43,32 @@ export function Repost({ event }: { event: NDKEvent }) {
);
}
const renderKind = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote event={event} />;
case NDKKind.Article:
return <ArticleNote event={event} />;
case 1063:
return <FileNote event={event} />;
default:
return <UnknownNote event={event} />;
}
};
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="relative flex flex-col">
<div className="isolate flex flex-col -space-y-4">
<RepostUser pubkey={event.pubkey} />
<User
pubkey={data.event.pubkey}
time={data.event.created_at}
isRepost={true}
/>
<User pubkey={data.pubkey} time={data.created_at} isRepost={true} />
</div>
<div className="flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="relative z-20 flex-1">
<NoteContent content={data.richContent} />
<NoteActions id={repostID} pubkey={data.event.pubkey} />
{renderKind(data)}
<NoteActions id={repostID} pubkey={data.pubkey} />
</div>
</div>
<NoteMetadata id={repostID} />

View File

@@ -0,0 +1,43 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import {
Hashtag,
ImagePreview,
LinkPreview,
MentionNote,
MentionUser,
VideoPreview,
} from '@shared/notes';
import { parser } from '@utils/parser';
export function TextNote({ event }: { event: NDKEvent }) {
const content = useMemo(() => parser(event), [event.id]);
return (
<div>
<ReactMarkdown
className="markdown"
remarkPlugins={[remarkGfm]}
components={{
del: ({ children }) => {
const key = children[0] as string;
if (key.startsWith('pub') && key.length > 50 && key.length < 100)
return <MentionUser pubkey={key.replace('pub-', '')} />;
if (key.startsWith('tag')) return <Hashtag tag={key.replace('tag-', '')} />;
},
}}
>
{content?.parsed}
</ReactMarkdown>
{content?.images?.length > 0 && <ImagePreview urls={content.images} />}
{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} />)}
</div>
);
}

View File

@@ -0,0 +1,19 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
export function UnknownNote({ event }: { event: NDKEvent }) {
return (
<div className="mb-3 mt-3 flex w-full flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2">
<span className="text-sm font-medium leading-none text-white/50">
Unknown kind: {event.kind}
</span>
<p className="text-sm leading-none text-white">
Lume isn&apos;t fully support this kind
</p>
</div>
<div className="select-text whitespace-pre-line break-all text-white">
<p>{event.content.toString()}</p>
</div>
</div>
);
}

View File

@@ -1,36 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { NoteActions, NoteMetadata } from '@shared/notes';
import { User } from '@shared/user';
export function NoteKindUnsupport({ event }: { event: NDKEvent }) {
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="relative z-20 flex-1">
<div className="mt-3 flex w-full flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2">
<span className="text-sm font-medium leading-none text-white/50">
Kind: {event.kind}
</span>
<p className="text-sm leading-none text-white">
Lume isn&apos;t fully support this kind
</p>
</div>
<div className="select-text whitespace-pre-line break-all text-white">
<p>{event.content.toString()}</p>
</div>
</div>
<NoteActions id={event.id} pubkey={event.pubkey} />
</div>
</div>
<NoteMetadata id={event.id} />
</div>
</div>
</div>
);
}

View File

@@ -1,21 +1,24 @@
import { memo, useCallback } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { memo } from 'react';
import { useStorage } from '@libs/storage/provider';
import { Image } from '@shared/image';
import { MentionUser, NoteSkeleton } from '@shared/notes';
import {
ArticleNote,
FileNote,
NoteSkeleton,
TextNote,
UnknownNote,
} from '@shared/notes';
import { User } from '@shared/user';
import { WidgetKinds, useWidgets } from '@stores/widgets';
import { useEvent } from '@utils/hooks/useEvent';
import { isImage } from '@utils/isImage';
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
const { db } = useStorage();
const { status, data, error } = useEvent(id);
const { status, data } = useEvent(id);
const setWidget = useWidgets((state) => state.setWidget);
@@ -28,55 +31,6 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
}
};
const renderItem = useCallback(() => {
if (!data) return;
switch (data.event.kind) {
case 1: {
return (
<ReactMarkdown
className="markdown"
remarkPlugins={[remarkGfm]}
components={{
del: ({ children }) => {
const key = children[0] as string;
if (key.startsWith('pub')) return <MentionUser pubkey={key.slice(3)} />;
if (key.startsWith('tag'))
return (
<button
type="button"
className="font-normal text-orange-400 no-underline hover:text-orange-500"
>
{key.slice(3)}
</button>
);
},
}}
>
{data.richContent.parsed.length > 160
? data.richContent.parsed.substring(0, 160) + '...'
: data.richContent.parsed}
</ReactMarkdown>
);
}
case 1063: {
const url = data.event.tags.find((el) => el[0] === 'url')[1];
return (
<div>
{isImage(url) && (
<Image
src={url}
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>
)}
</div>
);
}
default:
break;
}
}, [data]);
if (status === 'loading') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3">
@@ -85,7 +39,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
);
}
if (error) {
if (status === 'error') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3">
<p>Can&apos;t get event from relay</p>
@@ -93,6 +47,19 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
);
}
const renderKind = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote event={event} />;
case NDKKind.Article:
return <ArticleNote event={event} />;
case 1063:
return <FileNote event={event} />;
default:
return <UnknownNote event={event} />;
}
};
return (
<div
onClick={(e) => openThread(e, id)}
@@ -101,8 +68,8 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
tabIndex={0}
className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3"
>
<User pubkey={data.event.pubkey} time={data.event.created_at} size="small" />
<div className="mt-2">{renderItem()}</div>
<User pubkey={data.pubkey} time={data.created_at} size="small" />
<div>{renderKind(data)}</div>
</div>
);
});

View File

@@ -3,8 +3,6 @@ import { useState } from 'react';
import { Button } from '@shared/button';
import { Image } from '@shared/image';
import { FULL_RELAYS } from '@stores/constants';
import { useNostr } from '@utils/hooks/useNostr';
import { useProfile } from '@utils/hooks/useProfile';
import { displayNpub } from '@utils/shortenKey';
@@ -16,7 +14,7 @@ export function NoteReplyForm({ id, pubkey }: { id: string; pubkey: string }) {
const [value, setValue] = useState('');
const submit = () => {
const tags = [['e', id, FULL_RELAYS[0], 'reply']];
const tags = [['e', id, '', 'reply']];
// publish event
publish({ content: value, kind: 1, tags });

View File

@@ -4,9 +4,9 @@ import { NoteActions, NoteContent, SubReply } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
import { LumeEvent } from '@utils/types';
import { NDKEventWithReplies } from '@utils/types';
export function Reply({ event, root }: { event: LumeEvent; root?: string }) {
export function Reply({ event, root }: { event: NDKEventWithReplies; root?: string }) {
const content = useMemo(() => parser(event), [event]);
return (

View File

@@ -1,23 +1,20 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useNDK } from '@libs/ndk/provider';
import { NoteSkeleton, Reply } from '@shared/notes';
interface ReplyEvent extends NDKEvent {
replies: Array<NDKEvent>;
}
import { NDKEventWithReplies } from '@utils/types';
export function RepliesList({ id }: { id: string }) {
const { ndk } = useNDK();
const { status, data } = useQuery(['thread', id], async () => {
const { status, data } = useQuery(['note-replies', id], async () => {
const events = await ndk.fetchEvents({
kinds: [1],
'#e': [id],
});
const array = [...events] as unknown as ReplyEvent[];
const array = [...events] as unknown as NDKEventWithReplies[];
if (array.length > 0) {
const replies = new Set();
@@ -74,7 +71,9 @@ export function RepliesList({ id }: { id: string }) {
) : (
data
.reverse()
.map((event: NDKEvent) => <Reply key={event.id} event={event} root={id} />)
.map((event: NDKEventWithReplies) => (
<Reply key={event.id} event={event} root={id} />
))
)}
</div>
</div>

View File

@@ -1,12 +1,12 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { NoteActions, NoteContent } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
import { LumeEvent } from '@utils/types';
export function SubReply({ event }: { event: LumeEvent }) {
export function SubReply({ event }: { event: NDKEvent }) {
const content = useMemo(() => parser(event), [event]);
return (

View File

@@ -1,37 +1,38 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { ReactNode } from 'react';
import { NoteActions, NoteContent, NoteMetadata, SubNote } from '@shared/notes';
import { ChildNote, NoteActions, NoteMetadata } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
export function NoteThread({
export function NoteWrapper({
event,
children,
meta = true,
root,
reply,
}: {
event: NDKEvent;
root: string;
reply: string;
children: ReactNode;
repost?: boolean;
meta?: boolean;
root?: string;
reply?: string;
}) {
const content = useMemo(() => parser(event), [event.id]);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="relative">{root && <SubNote id={root} />}</div>
<div className="relative">{reply && <SubNote id={reply} root={root} />}</div>
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="relative">{root && <ChildNote id={root} />}</div>
<div className="relative">{reply && <ChildNote id={reply} root={root} />}</div>
<div className="relative flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="relative z-20 flex-1">
<NoteContent content={content} />
{children}
<NoteActions id={event.id} pubkey={event.pubkey} />
</div>
</div>
<NoteMetadata id={event.id} />
{meta ? <NoteMetadata id={event.id} /> : <div className="pb-3" />}
</div>
</div>
</div>