This commit is contained in:
2023-11-05 15:19:51 +07:00
parent dad388c6ab
commit 701712e7b8
21 changed files with 384 additions and 485 deletions

View File

@@ -1,5 +1,6 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools';
import { useCallback } from 'react';
import {
ArticleNote,
@@ -17,18 +18,21 @@ import { useEvent } from '@utils/hooks/useEvent';
export function ChildNote({ id, root }: { id: string; root?: string }) {
const { status, data } = useEvent(id);
const renderKind = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote content={event.content} />;
case NDKKind.Article:
return <ArticleNote event={event} />;
case 1063:
return <FileNote event={event} />;
default:
return <UnknownNote event={event} />;
}
};
const renderKind = useCallback(
(event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote content={event.content} />;
case NDKKind.Article:
return <ArticleNote event={event} />;
case 1063:
return <FileNote event={event} />;
default:
return <UnknownNote event={event} />;
}
},
[id]
);
if (status === 'pending') {
return (

View File

@@ -4,11 +4,10 @@ import { download } from '@tauri-apps/plugin-upload';
import {
MediaControlBar,
MediaController,
MediaFullscreenButton,
MediaMuteButton,
MediaPlayButton,
MediaTimeDisplay,
MediaTimeRange,
MediaVolumeRange,
} from 'media-chrome/dist/react';
import { memo } from 'react';
@@ -56,9 +55,8 @@ export function FileNote(props: { event?: NDKEvent }) {
<MediaControlBar>
<MediaPlayButton></MediaPlayButton>
<MediaTimeRange></MediaTimeRange>
<MediaTimeDisplay showDuration></MediaTimeDisplay>
<MediaMuteButton></MediaMuteButton>
<MediaVolumeRange></MediaVolumeRange>
<MediaFullscreenButton></MediaFullscreenButton>
</MediaControlBar>
</MediaController>
);

View File

@@ -36,18 +36,21 @@ export function Repost({ event }: { event: NDKEvent }) {
refetchOnWindowFocus: false,
});
const renderKind = useCallback((repostEvent: NDKEvent) => {
switch (repostEvent.kind) {
case NDKKind.Text:
return <TextNote content={repostEvent.content} />;
case NDKKind.Article:
return <ArticleNote event={repostEvent} />;
case 1063:
return <FileNote event={repostEvent} />;
default:
return <UnknownNote event={repostEvent} />;
}
}, []);
const renderKind = useCallback(
(repostEvent: NDKEvent) => {
switch (repostEvent.kind) {
case NDKKind.Text:
return <TextNote content={repostEvent.content} />;
case NDKKind.Article:
return <ArticleNote event={repostEvent} />;
case 1063:
return <FileNote event={repostEvent} />;
default:
return <UnknownNote event={repostEvent} />;
}
},
[event.id]
);
if (embedEvent) {
return (

View File

@@ -1,28 +1,21 @@
import { memo } from 'react';
import { ImagePreview, LinkPreview, VideoPreview } from '@shared/notes';
import { useRichContent } from '@utils/hooks/useRichContent';
export function TextNote(props: { content?: string; truncate?: boolean }) {
const { parsedContent, images, videos, linkPreview } = useRichContent(props.content);
const { parsedContent } = useRichContent(props.content);
if (props.truncate) {
return (
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert">
<div className="break-p select-text whitespace-pre-line leading-normal text-neutral-900 dark:text-neutral-100">
{props.content}
</div>
);
}
return (
<div className="flex flex-col gap-3">
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-img:mb-0 prose-img:mt-0">
{parsedContent}
</div>
{images.length ? <ImagePreview urls={images} /> : null}
{videos.length ? <VideoPreview urls={videos} /> : null}
{linkPreview ? <LinkPreview url={linkPreview} /> : null}
<div className="break-p select-text whitespace-pre-line leading-normal text-neutral-900 dark:text-neutral-100">
{parsedContent}
</div>
);
}

View File

@@ -5,7 +5,7 @@ import Zoom from 'react-medium-image-zoom';
import { DownloadIcon } from '@shared/icons';
export function ImagePreview({ urls }: { urls: string[] }) {
export function ImagePreview({ url }: { url: string }) {
const downloadImage = async (url: string) => {
const downloadDirPath = await downloadDir();
const filename = url.substring(url.lastIndexOf('/') + 1);
@@ -17,29 +17,25 @@ export function ImagePreview({ urls }: { urls: string[] }) {
};
return (
<div className="flex w-full flex-col gap-2">
{urls.map((url) => (
<Zoom key={url} zoomMargin={50}>
<div className="group relative">
<img
src={url}
alt={url}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
onError={fallback}
className="h-auto w-full rounded-lg border border-neutral-300 object-cover dark:border-neutral-700"
/>
<button
type="button"
onClick={() => downloadImage(url)}
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-xl bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
>
<DownloadIcon className="h-4 w-4 text-white" />
</button>
</div>
</Zoom>
))}
</div>
<Zoom key={url} zoomMargin={50}>
<div className="group relative mt-2">
<img
src={url}
alt={url}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
onError={fallback}
className="h-auto w-full rounded-lg border border-neutral-300/50 object-cover dark:border-neutral-700/50"
/>
<button
type="button"
onClick={() => downloadImage(url)}
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-lg bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
>
<DownloadIcon className="h-4 w-4 text-white" />
</button>
</div>
</Zoom>
);
}

View File

@@ -12,7 +12,7 @@ export function LinkPreview({ url }: { url: string }) {
if (status === 'pending') {
return (
<div className="flex w-full flex-col bg-neutral-200 dark:bg-neutral-800">
<div className="mt-2 flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
<div className="h-44 w-full animate-pulse bg-neutral-400 dark:bg-neutral-600" />
<div className="flex flex-col gap-2 px-3 py-3">
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-400 dark:bg-neutral-600" />
@@ -30,7 +30,7 @@ export function LinkPreview({ url }: { url: string }) {
to={url}
target="_blank"
rel="noreferrer"
className="flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
className="mt-2 flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
>
{isImage(data.image) ? (
<img
@@ -42,19 +42,19 @@ export function LinkPreview({ url }: { url: string }) {
<div className="flex flex-col items-start px-3 py-3">
<div className="flex flex-col items-start gap-1 text-left">
{data.title && (
<h5 className="line-clamp-1 text-base font-semibold text-neutral-900 dark:text-neutral-100">
<div className="line-clamp-1 text-base font-semibold text-neutral-900 dark:text-neutral-100">
{data.title}
</h5>
</div>
)}
{data.description ? (
<p className="mb-2.5 line-clamp-3 break-all text-sm text-neutral-700 dark:text-neutral-400">
<div className="mb-2 line-clamp-3 break-all text-sm text-neutral-700 dark:text-neutral-400">
{data.description}
</p>
</div>
) : null}
</div>
<span className="break-all text-sm text-neutral-600 dark:text-neutral-400">
<div className="break-all text-sm text-neutral-600 dark:text-neutral-400">
{domain.hostname}
</span>
</div>
</div>
</Link>
);

View File

@@ -1,28 +1,27 @@
import {
MediaControlBar,
MediaController,
MediaFullscreenButton,
MediaLoadingIndicator,
MediaMuteButton,
MediaPlayButton,
MediaTimeDisplay,
MediaTimeRange,
MediaVolumeRange,
} from 'media-chrome/dist/react';
export function VideoPreview({ urls }: { urls: string[] }) {
export function VideoPreview({ url }: { url: string }) {
return (
<div className="flex w-full flex-col gap-2">
{urls.map((url) => (
<MediaController key={url} className="aspect-video overflow-hidden rounded-lg">
<video slot="media" src={url} preload="metadata" muted />
<MediaControlBar>
<MediaPlayButton></MediaPlayButton>
<MediaTimeRange></MediaTimeRange>
<MediaTimeDisplay showDuration></MediaTimeDisplay>
<MediaMuteButton></MediaMuteButton>
<MediaVolumeRange></MediaVolumeRange>
</MediaControlBar>
</MediaController>
))}
</div>
<MediaController
key={url}
className="mt-2 aspect-video w-full overflow-hidden rounded-lg"
>
<video slot="media" src={url} preload="metadata" muted />
<MediaLoadingIndicator slot="centered-chrome"></MediaLoadingIndicator>
<MediaControlBar>
<MediaPlayButton></MediaPlayButton>
<MediaTimeRange></MediaTimeRange>
<MediaMuteButton></MediaMuteButton>
<MediaFullscreenButton></MediaFullscreenButton>
</MediaControlBar>
</MediaController>
);
}

View File

@@ -16,9 +16,12 @@ export function NoteWrapper({
return event.tags[0][1];
}
return event.tags.find((el) => el[3] === 'root')?.[1];
}, [event]);
}, [event.id]);
const reply = useMemo(() => event.tags.find((el) => el[3] === 'reply')?.[1], []);
const reply = useMemo(
() => event.tags.find((el) => el[3] === 'reply')?.[1],
[event.id]
);
return (
<div className="h-min w-full px-3 pb-3">