new parser
This commit is contained in:
@@ -60,7 +60,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
|
||||
Lume cannot find this post with your current relay set, but you can view
|
||||
it via njump.me
|
||||
</div>
|
||||
<LinkPreview urls={[noteLink]} />
|
||||
<LinkPreview url={noteLink} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
|
||||
<User pubkey={data.pubkey} time={data.created_at} eventId={data.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
<div className="relative z-20 min-w-0 flex-1">
|
||||
{renderKind(data)}
|
||||
<NoteActions id={data.id} pubkey={data.pubkey} root={root} />
|
||||
</div>
|
||||
|
||||
@@ -52,13 +52,7 @@ export function FileNote(props: { event?: NDKEvent }) {
|
||||
key={url}
|
||||
className="mt-2 aspect-video w-full overflow-hidden rounded-lg"
|
||||
>
|
||||
<video
|
||||
slot="media"
|
||||
src={url}
|
||||
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
|
||||
preload="none"
|
||||
muted
|
||||
/>
|
||||
<video slot="media" src={url} preload="metadata" muted />
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
@@ -72,7 +66,7 @@ export function FileNote(props: { event?: NDKEvent }) {
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<LinkPreview urls={[url]} />
|
||||
<LinkPreview url={url} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className="h-min w-full px-3 pb-3">
|
||||
<div className="w-full px-3 pb-3">
|
||||
<div className="relative overflow-hidden rounded-xl border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
||||
Lume cannot find this post with your current relay set, but you can view
|
||||
it via njump.me
|
||||
</div>
|
||||
<LinkPreview urls={[noteLink]} />
|
||||
<LinkPreview url={noteLink} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
||||
<User pubkey={data.pubkey} time={data.created_at} eventId={data.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
<div className="relative z-20 min-w-0 flex-1">
|
||||
{renderKind(data)}
|
||||
<NoteActions id={data.id} pubkey={data.pubkey} />
|
||||
</div>
|
||||
|
||||
@@ -1,67 +1,28 @@
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import { memo } from 'react';
|
||||
|
||||
import {
|
||||
Boost,
|
||||
Hashtag,
|
||||
ImagePreview,
|
||||
Invoice,
|
||||
LinkPreview,
|
||||
MentionNote,
|
||||
MentionUser,
|
||||
VideoPreview,
|
||||
} from '@shared/notes';
|
||||
import { ImagePreview, LinkPreview, VideoPreview } from '@shared/notes';
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
import { useRichContent } from '@utils/hooks/useRichContent';
|
||||
|
||||
export function TextNote(props: { content?: string; truncate?: boolean }) {
|
||||
const richContent = parser(props.content);
|
||||
const { parsedContent, images, videos, linkPreview } = useRichContent(props.content);
|
||||
|
||||
if (props.truncate) {
|
||||
return (
|
||||
<div className="break-p prose prose-neutral line-clamp-4 max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-600 prose-a:hover:underline">
|
||||
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert">
|
||||
{props.content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<Markdown
|
||||
options={{
|
||||
overrides: {
|
||||
Hashtag: {
|
||||
component: Hashtag,
|
||||
},
|
||||
Boost: {
|
||||
component: Boost,
|
||||
},
|
||||
MentionUser: {
|
||||
component: MentionUser,
|
||||
},
|
||||
Invoice: {
|
||||
component: Invoice,
|
||||
},
|
||||
a: {
|
||||
props: {
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
},
|
||||
slugify: (str) => str,
|
||||
forceBlock: true,
|
||||
enforceAtxHeadings: true,
|
||||
}}
|
||||
className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-600 prose-a:hover:underline"
|
||||
>
|
||||
{richContent.parsed}
|
||||
</Markdown>
|
||||
{richContent.images.length ? <ImagePreview urls={richContent.images} /> : null}
|
||||
{richContent.videos.length ? <VideoPreview urls={richContent.videos} /> : null}
|
||||
{richContent.links.length ? <LinkPreview urls={richContent.links} /> : null}
|
||||
{richContent.notes.map((note: string) => (
|
||||
<MentionNote key={note} id={note} />
|
||||
))}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
export function UnknownNote(props: { event?: NDKEvent }) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="inline-flex flex-col rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">
|
||||
<div className="mt-2 flex w-full flex-col gap-2">
|
||||
<div className="inline-flex flex-col rounded-md border border-neutral-300 bg-neutral-200 px-2 py-2 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<span className="text-sm font-medium text-neutral-500 dark:text-neutral-400">
|
||||
Kind: {props.event.kind}
|
||||
</span>
|
||||
@@ -12,7 +12,7 @@ export function UnknownNote(props: { event?: NDKEvent }) {
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-neutral-800 dark:text-neutral-200">
|
||||
<p>{props.event.content.toString()}</p>
|
||||
{props.event.content.toString()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className="w-full cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<div className="my-2 w-full cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
);
|
||||
@@ -58,17 +58,15 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
if (status === 'error') {
|
||||
const noteLink = `https://njump.me/${nip19.noteEncode(id)}`;
|
||||
return (
|
||||
<div className="w-full rounded-lg bg-neutral-200 px-3 py-3 dark:bg-neutral-800">
|
||||
<div className="my-2 w-full rounded-lg bg-neutral-200 px-3 py-3 dark:bg-neutral-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="inline-flex h-6 w-6 items-end justify-center rounded bg-black pb-1">
|
||||
<img src="/lume.png" alt="lume" className="h-auto w-1/3" />
|
||||
</div>
|
||||
<h5 className="truncate font-semibold leading-none text-white">
|
||||
<div className="inline-flex h-6 w-6 items-end justify-center rounded-md bg-black pb-1"></div>
|
||||
<h5 className="truncate font-semibold">
|
||||
Lume <span className="text-green-500">(System)</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="mt-1.5">
|
||||
<LinkPreview urls={[noteLink]} />
|
||||
<LinkPreview url={noteLink} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -79,7 +77,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
<div
|
||||
role="button"
|
||||
onClick={(e) => openThread(e, id)}
|
||||
className="w-full cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
className="my-2 w-full cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
>
|
||||
<User pubkey={data.pubkey} time={data.created_at} variant="mention" />
|
||||
<div className="mt-1 text-left">{renderKind(data)}</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { downloadDir } from '@tauri-apps/api/path';
|
||||
import { download } from '@tauri-apps/plugin-upload';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import Zoom from 'react-medium-image-zoom';
|
||||
|
||||
import { DownloadIcon } from '@shared/icons';
|
||||
|
||||
@@ -10,23 +12,33 @@ export function ImagePreview({ urls }: { urls: string[] }) {
|
||||
return await download(url, downloadDirPath + `/${filename}`);
|
||||
};
|
||||
|
||||
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
|
||||
event.currentTarget.src = '/fallback-image.jpg';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="group relative">
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
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-lg bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
|
||||
>
|
||||
<DownloadIcon className="h-5 w-5 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,9 @@ function isImage(url: string) {
|
||||
return /^https?:\/\/.+\.(jpg|jpeg|png|webp|avif)$/.test(url);
|
||||
}
|
||||
|
||||
export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
const { status, data } = useOpenGraph(urls[0]);
|
||||
const domain = new URL(urls[0]);
|
||||
export function LinkPreview({ url }: { url: string }) {
|
||||
const domain = new URL(url);
|
||||
const { status, data } = useOpenGraph(url);
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
@@ -27,7 +27,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={urls[0]}
|
||||
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"
|
||||
@@ -35,7 +35,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
src={data.image}
|
||||
alt={urls[0]}
|
||||
alt={url}
|
||||
className="h-44 w-full rounded-t-lg bg-white object-cover"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -7,20 +7,13 @@ import {
|
||||
MediaTimeRange,
|
||||
MediaVolumeRange,
|
||||
} from 'media-chrome/dist/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const VideoPreview = memo(function VideoPreview({ urls }: { urls: string[] }) {
|
||||
export function VideoPreview({ urls }: { urls: 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}
|
||||
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
|
||||
preload="none"
|
||||
muted
|
||||
/>
|
||||
<video slot="media" src={url} preload="metadata" muted />
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
@@ -32,4 +25,4 @@ export const VideoPreview = memo(function VideoPreview({ urls }: { urls: string[
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function NoteWrapper({
|
||||
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
<div className="relative z-20 min-w-0 flex-1">
|
||||
{cloneElement(
|
||||
children,
|
||||
event.kind === 1 ? { content: event.content } : { event: event }
|
||||
|
||||
Reference in New Issue
Block a user