use new note parser for note component

This commit is contained in:
Ren Amamiya
2023-05-02 12:19:00 +07:00
parent e951d268a3
commit b66525d148
9 changed files with 127 additions and 45 deletions

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ pnpm-lock.yaml
*.njsproj
*.sln
*.sw?
/.gtm/

View File

@@ -1,21 +1,14 @@
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
import NoteMetadata from '@lume/app/newsfeed/components/note/metadata';
import { NoteParent } from '@lume/app/newsfeed/components/note/parent';
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
import { noteParser } from '@lume/app/note/components/parser';
import ImagePreview from '@lume/app/note/components/preview/image';
import VideoPreview from '@lume/app/note/components/preview/video';
import { navigate } from 'vite-plugin-ssr/client/router';
export default function NoteBase({ event }: { event: any }) {
const content = contentParser(event.content, event.tags);
const parentNote = () => {
if (event.parent_id) {
if (event.parent_id !== event.event_id && !event.content.includes('#[0]')) {
return <NoteParent key={event.parent_id} id={event.parent_id} />;
}
}
return <></>;
};
const content = noteParser(event);
const openNote = (e) => {
const selection = window.getSelection();
@@ -31,11 +24,15 @@ export default function NoteBase({ event }: { event: any }) {
onClick={(e) => openNote(e)}
className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20"
>
{parentNote()}
{event.parent_id ? <NoteParent key={event.parent_id} id={event.parent_id} /> : <></>}
<div className="relative z-10 flex flex-col">
<NoteDefaultUser pubkey={event.pubkey} time={event.created_at} />
<div className="mt-1 pl-[52px]">
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">{content}</div>
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
{content.parsed}
</div>
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata id={event.event_id} eventPubkey={event.pubkey} />

View File

@@ -1,6 +1,8 @@
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
import NoteMetadata from '@lume/app/newsfeed/components/note/metadata';
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
import { noteParser } from '@lume/app/note/components/parser';
import ImagePreview from '@lume/app/note/components/preview/image';
import VideoPreview from '@lume/app/note/components/preview/video';
import { RelayContext } from '@lume/shared/relayProvider';
import { READONLY_RELAYS } from '@lume/stores/constants';
@@ -33,31 +35,36 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
};
});
const content = !error && data ? noteParser(data) : null;
return (
<div className="relative pb-5">
{error && <div>failed to load</div>}
{!data ? (
<div className="animated-pulse">
<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 className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<div className="animated-pulse relative z-10">
<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-5 pl-[52px]">
<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="-mt-5 pl-[52px]">
<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="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
@@ -65,8 +72,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
{contentParser(data.content, data.tags)}
{content ? content.parsed : ''}
</div>
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />

View File

@@ -1,5 +1,7 @@
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
import { noteParser } from '@lume/app/note/components/parser';
import ImagePreview from '@lume/app/note/components/preview/image';
import VideoPreview from '@lume/app/note/components/preview/video';
import { RelayContext } from '@lume/shared/relayProvider';
import { READONLY_RELAYS } from '@lume/stores/constants';
@@ -42,6 +44,8 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
}
};
const content = !error && data ? noteParser(data) : null;
return (
<div
onClick={(e) => openNote(e)}
@@ -55,8 +59,10 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
{contentParser(data.content, data.tags)}
{content ? content.parsed : ''}
</div>
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
</div>
</div>
)}

View File

@@ -1,6 +1,8 @@
import { contentParser } from '@lume/app/newsfeed/components/contentParser';
import NoteMetadata from '@lume/app/newsfeed/components/note/metadata';
import { NoteDefaultUser } from '@lume/app/newsfeed/components/user/default';
import { noteParser } from '@lume/app/note/components/parser';
import ImagePreview from '@lume/app/note/components/preview/image';
import VideoPreview from '@lume/app/note/components/preview/video';
import { RelayContext } from '@lume/shared/relayProvider';
import { READONLY_RELAYS } from '@lume/stores/constants';
@@ -44,14 +46,28 @@ export const RootNote = memo(function RootNote({ id, fallback }: { id: string; f
}
};
const content = !error && data ? noteParser(parseFallback) : null;
if (parseFallback) {
const contentFallback = noteParser(parseFallback);
return (
<div onClick={(e) => openNote(e)} className="relative z-10 flex flex-col">
<NoteDefaultUser pubkey={parseFallback.pubkey} time={parseFallback.created_at} />
<div className="mt-1 pl-[52px]">
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
{contentParser(parseFallback.content, parseFallback.tags)}
{contentFallback.parsed}
</div>
{Array.isArray(contentFallback.images) && contentFallback.images.length ? (
<ImagePreview urls={contentFallback.images} />
) : (
<></>
)}
{Array.isArray(contentFallback.videos) && contentFallback.videos.length ? (
<VideoPreview urls={contentFallback.videos} />
) : (
<></>
)}
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata id={parseFallback.id} eventPubkey={parseFallback.pubkey} />
@@ -90,8 +106,10 @@ export const RootNote = memo(function RootNote({ id, fallback }: { id: string; f
<NoteDefaultUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-1 pl-[52px]">
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100">
{contentParser(data.content, data.tags)}
{content ? content.parsed : ''}
</div>
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata id={data.id} eventPubkey={data.pubkey} />

View File

@@ -11,10 +11,10 @@ export const NoteDefaultUser = ({ pubkey, time }: { pubkey: string; time: number
const { user, isError, isLoading } = useProfile(pubkey);
return (
<div className="group flex h-11 items-center gap-2">
<div className="group relative z-10 flex h-11 items-center gap-2">
{isError || isLoading ? (
<>
<div className="relative h-11 w-11 shrink animate-pulse overflow-hidden rounded-md bg-white bg-zinc-800"></div>
<div className="h-11 w-11 shrink animate-pulse overflow-hidden rounded-md bg-white bg-zinc-800"></div>
<div className="flex w-full flex-1 items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-baseline gap-2">
@@ -26,7 +26,7 @@ export const NoteDefaultUser = ({ pubkey, time }: { pubkey: string; time: number
</>
) : (
<>
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-white">
<div className="h-11 w-11 shrink overflow-hidden rounded-md bg-white">
<img
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${user?.picture ? user.picture : DEFAULT_AVATAR}`}
alt={pubkey}

View File

@@ -9,9 +9,12 @@ export const noteParser = (event: Event) => {
const references = parseReferences(event);
const content = { original: event.content, parsed: event.content, images: [], videos: [], others: [] };
// remove extra whitespace
content.parsed = content.parsed.replace(/\s+/g, ' ').trim();
// handle media
content.original.match(getURLs)?.forEach((url) => {
if (url.match(/\.(jpg|jpeg|gif|png|webp|avif)$/i)) {
if (url.match(/\.(jpg|jpeg|gif|png|webp|avif)$/im)) {
// image url
content.images.push(url.trim());
// remove url from original content
@@ -21,7 +24,7 @@ export const noteParser = (event: Event) => {
content.videos.push(url.trim());
// remove url from original content
content.parsed = content.parsed.replace(url, '');
} else if (url.match(/\.(mp4|webm|mov)$/i)) {
} else if (url.match(/\.(mp4|webm|mov)$/im)) {
// video
content.videos.push(url.trim());
// remove url from original content
@@ -34,13 +37,19 @@ export const noteParser = (event: Event) => {
// handle note references
references?.forEach((reference) => {
if (reference?.profile) {
content.parsed.replace(reference.text, `<NoteMentionUser pubkey="${reference.profile.pubkey}" />`);
content.parsed = content.parsed.replace(
reference.text,
`<NoteMentionUser pubkey="${reference.profile.pubkey}" />`
);
}
if (reference?.event) {
content.parsed.replace(reference.text, `<NoteQuote id="${reference.event.id}" />;`);
content.parsed = content.parsed.replace(reference.text, `<NoteQuote id="${reference.event.id}" />;`);
}
if (reference?.address) {
content.parsed.replace(reference.text, `<a href="${reference.address}" target="_blank" />`);
content.parsed = content.parsed.replace(
reference.text,
`<a href="${reference.address}" target="_blank">${reference.address}</>`
);
}
});

View File

@@ -0,0 +1,31 @@
export default function ImagePreview({ urls }: { urls: string[] }) {
return (
<div className="mt-2 grid h-full w-full grid-cols-2">
{urls.length === 1 ? (
<div className="col-span-2">
<img
src={urls[0]}
alt="image"
className="h-auto w-full rounded-lg object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
/>
</div>
) : (
urls.map((url: string) => (
<div key={url} className="col-span-1">
<img
src={url}
alt="image"
className="h-auto w-full rounded-lg object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
/>
</div>
))
)}
</div>
);
}

View File

@@ -0,0 +1,11 @@
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 rounded-lg bg-zinc-950">
<MediaPlayer src={urls[0]} poster="" controls>
<MediaOutlet />
</MediaPlayer>
</div>
);
}