mirror of
https://github.com/lumehq/lume.git
synced 2025-07-28 18:22:19 +02:00
use new note parser for note component
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,3 +28,4 @@ pnpm-lock.yaml
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/.gtm/
|
||||
|
@@ -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} />
|
||||
|
@@ -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} />
|
||||
|
@@ -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>
|
||||
)}
|
||||
|
@@ -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} />
|
||||
|
@@ -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}
|
||||
|
@@ -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}</>`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
31
src/app/note/components/preview/image.tsx
Normal file
31
src/app/note/components/preview/image.tsx
Normal 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>
|
||||
);
|
||||
}
|
11
src/app/note/components/preview/video.tsx
Normal file
11
src/app/note/components/preview/video.tsx
Normal 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>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user