add markdown support
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { ContentMarkdown } from '@lume/app/note/components/markdown';
|
||||
import NoteMetadata from '@lume/app/note/components/metadata';
|
||||
import { NoteParent } from '@lume/app/note/components/parent';
|
||||
import { noteParser } from '@lume/app/note/components/parser';
|
||||
@@ -32,9 +33,7 @@ export default function NoteBase({ event }: { event: any }) {
|
||||
<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.parsed}
|
||||
</div>
|
||||
<ContentMarkdown content={content.parsed} />
|
||||
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
|
||||
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
|
||||
</div>
|
||||
|
||||
22
src/app/note/components/markdown.tsx
Normal file
22
src/app/note/components/markdown.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { NoteQuote } from '@lume/app/note/components/quote';
|
||||
import { NoteMentionUser } from '@lume/app/note/components/user/mention';
|
||||
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
export const ContentMarkdown = ({ content }: { content: any }) => {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[[remarkGfm]]}
|
||||
linkTarget="_blank"
|
||||
className="prose prose-zinc max-w-none break-words dark:prose-invert prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline prose-a:hover:text-fuchsia-600 prose-a:hover:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight"
|
||||
components={{
|
||||
h5: ({ ...props }) => <NoteMentionUser pubkey={props.content} />,
|
||||
h6: ({ ...props }) => <NoteQuote id={props.content} />,
|
||||
em: ({ ...props }) => <span className="text-fuchsia-500 hover:text-fuchsia-600" {...props} />,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContentMarkdown } from '@lume/app/note/components/markdown';
|
||||
import NoteMetadata from '@lume/app/note/components/metadata';
|
||||
import { noteParser } from '@lume/app/note/components/parser';
|
||||
import ImagePreview from '@lume/app/note/components/preview/image';
|
||||
@@ -71,9 +72,8 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
|
||||
<div className="relative z-10 flex flex-col">
|
||||
<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">
|
||||
{content ? content.parsed : ''}
|
||||
</div>
|
||||
<div className="whitespace-pre-line break-words text-[15px] leading-tight text-zinc-100"></div>
|
||||
<ContentMarkdown content={content.parsed} />
|
||||
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
|
||||
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { NoteQuote } from '@lume/app/note/components/quote';
|
||||
import { NoteMentionUser } from '@lume/app/note/components/user/mention';
|
||||
|
||||
import { Event, parseReferences } from 'nostr-tools';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { Event } from 'nostr-tools';
|
||||
|
||||
const getURLs = new RegExp(
|
||||
'(^|[ \t\r\n])((ftp|http|https|gopher|mailto|news|nntp|telnet|wais|file|prospero|aim|webcal|wss|ws):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))',
|
||||
@@ -10,7 +6,6 @@ const getURLs = new RegExp(
|
||||
);
|
||||
|
||||
export const noteParser = (event: Event) => {
|
||||
const references = parseReferences(event);
|
||||
const content: { original: string; parsed: any; images: string[]; videos: string[] } = {
|
||||
original: event.content,
|
||||
parsed: event.content,
|
||||
@@ -33,43 +28,22 @@ export const noteParser = (event: Event) => {
|
||||
content.videos.push(url);
|
||||
// remove url from original content
|
||||
content.parsed = content.parsed.toString().replace(url, '');
|
||||
} else {
|
||||
content.parsed = reactStringReplace(content.parsed, url, () => {
|
||||
return (
|
||||
<a key={url} href={url} className="text-fuchsia-500 no-underline hover:text-fuchsia-600 hover:underline">
|
||||
{url}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// handle hashtag
|
||||
content.parsed = reactStringReplace(content.parsed, /#(\w+)/g, (match, i) => (
|
||||
<span key={match + i} className="cursor-pointer text-fuchsia-500 hover:text-fuchsia-600">
|
||||
#{match}
|
||||
</span>
|
||||
));
|
||||
|
||||
// handle note references
|
||||
references?.forEach((reference) => {
|
||||
if (reference?.profile) {
|
||||
content.parsed = reactStringReplace(content.parsed, reference.text, () => {
|
||||
return <NoteMentionUser key={reference.profile.pubkey} pubkey={reference.profile.pubkey} />;
|
||||
});
|
||||
}
|
||||
if (reference?.event) {
|
||||
content.parsed = reactStringReplace(content.parsed, reference.text, () => {
|
||||
return <NoteQuote key={reference.event.id} id={reference.event.id} />;
|
||||
});
|
||||
}
|
||||
// map hashtag to em
|
||||
content.original.match(/#(\w+)(?!:\/\/)/gi)?.forEach((item) => {
|
||||
content.parsed = content.parsed.replace(item, `*${item}*`);
|
||||
});
|
||||
|
||||
// remove extra spaces
|
||||
content.parsed.forEach((item, index) => {
|
||||
if (typeof item === 'string') {
|
||||
content.parsed[index] = item.replace(/\s{2,}/g, ' ');
|
||||
}
|
||||
// map note mention to h6
|
||||
content.original.match(/^(nostr:)?(note1|nevent1).*$/gm)?.forEach((item) => {
|
||||
content.parsed = content.parsed.replace(item, `###### ${item}`);
|
||||
});
|
||||
|
||||
// map profile mention to h5
|
||||
content.original.match(/^(nostr:)?(nprofile1|npub1).*$/gm)?.forEach((item) => {
|
||||
content.parsed = content.parsed.replace(item, `##### ${item}`);
|
||||
});
|
||||
|
||||
return content;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContentMarkdown } from '@lume/app/note/components/markdown';
|
||||
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';
|
||||
@@ -58,9 +59,7 @@ export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) {
|
||||
<div className="relative z-10 flex flex-col">
|
||||
<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">
|
||||
{content ? content.parsed : ''}
|
||||
</div>
|
||||
<ContentMarkdown content={content.parsed} />
|
||||
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
|
||||
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { noteParser } from '@lume/app/note/components//parser';
|
||||
import { ContentMarkdown } from '@lume/app/note/components/markdown';
|
||||
import ImagePreview from '@lume/app/note/components/preview/image';
|
||||
import VideoPreview from '@lume/app/note/components/preview/video';
|
||||
import NoteReplyUser from '@lume/app/note/components/user/reply';
|
||||
@@ -11,7 +12,7 @@ export default function NoteReply({ data }: { data: any }) {
|
||||
<div className="flex flex-col">
|
||||
<NoteReplyUser pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-[17px] pl-[48px]">
|
||||
<div className="whitespace-pre-line break-words text-sm leading-tight">{content.parsed}</div>
|
||||
<ContentMarkdown content={content.parsed} />
|
||||
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
|
||||
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContentMarkdown } from '@lume/app/note/components/markdown';
|
||||
import NoteMetadata from '@lume/app/note/components/metadata';
|
||||
import { noteParser } from '@lume/app/note/components/parser';
|
||||
import ImagePreview from '@lume/app/note/components/preview/image';
|
||||
@@ -64,9 +65,7 @@ export const RootNote = memo(function RootNote({ id, fallback }: { id: string; f
|
||||
<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">
|
||||
{contentFallback.parsed}
|
||||
</div>
|
||||
<ContentMarkdown content={contentFallback.parsed} />
|
||||
{Array.isArray(contentFallback.images) && contentFallback.images.length ? (
|
||||
<ImagePreview urls={contentFallback.images} />
|
||||
) : (
|
||||
@@ -114,9 +113,7 @@ export const RootNote = memo(function RootNote({ id, fallback }: { id: string; f
|
||||
<div onClick={(e) => openNote(e)} className="relative z-10 flex flex-col">
|
||||
<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">
|
||||
{content ? content.parsed : ''}
|
||||
</div>
|
||||
<ContentMarkdown content={content.parsed} />
|
||||
{Array.isArray(content.images) && content.images.length ? <ImagePreview urls={content.images} /> : <></>}
|
||||
{Array.isArray(content.videos) && content.videos.length ? <VideoPreview urls={content.videos} /> : <></>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user