Replaced all markdown displays with react-markdown and gituhb-markdown-css styles

This commit is contained in:
austinkelsay 2025-04-13 20:41:21 -05:00
parent ad6d0fd373
commit 75c2899244
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
14 changed files with 124 additions and 73 deletions

60
package-lock.json generated
View File

@ -35,6 +35,7 @@
"clsx": "^2.1.1",
"cors": "^2.8.5",
"discord.js": "^14.15.3",
"github-markdown-css": "^5.8.1",
"light-bolt11-decoder": "^3.1.1",
"lucide-react": "^0.441.0",
"next": "14.2.5",
@ -47,6 +48,7 @@
"react": "^18",
"react-dom": "^18",
"reactflow": "^11.11.4",
"remark-breaks": "^4.0.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
@ -57,6 +59,7 @@
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1"
}
},
@ -8247,6 +8250,18 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/github-markdown-css": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.8.1.tgz",
"integrity": "sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
@ -10416,6 +10431,20 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-newline-to-break": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
"integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-find-and-replace": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-phrasing": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
@ -12145,6 +12174,22 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
@ -12849,6 +12894,21 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-breaks": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
"integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-newline-to-break": "^2.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-gfm": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",

View File

@ -38,6 +38,7 @@
"clsx": "^2.1.1",
"cors": "^2.8.5",
"discord.js": "^14.15.3",
"github-markdown-css": "^5.8.1",
"light-bolt11-decoder": "^3.1.1",
"lucide-react": "^0.441.0",
"next": "14.2.5",
@ -50,6 +51,7 @@
"react": "^18",
"react-dom": "^18",
"reactflow": "^11.11.4",
"remark-breaks": "^4.0.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",

View File

@ -12,11 +12,9 @@ import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscripti
import { getTotalFromZaps } from '@/utils/lightning';
import { useSession } from 'next-auth/react';
import useWindowWidth from '@/hooks/useWindowWidth';
import dynamic from 'next/dynamic';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), { ssr: false });
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const CombinedDetails = ({
processedEvent,
@ -203,7 +201,7 @@ const CombinedDetails = ({
const renderContent = () => {
if (decryptedContent) {
return <MDDisplay className="p-2 rounded-lg w-full" source={decryptedContent} />;
return <MarkdownDisplay content={decryptedContent} className="p-2 rounded-lg w-full" />;
}
if (paidResource && !decryptedContent) {
@ -231,7 +229,7 @@ const CombinedDetails = ({
}
if (processedEvent?.content) {
return <MDDisplay className="p-4 rounded-lg w-full" source={processedEvent.content} />;
return <MarkdownDisplay content={processedEvent.content} className="p-4 rounded-lg w-full" />;
}
return null;

View File

@ -7,7 +7,6 @@ import { useZapsQuery } from '@/hooks/nostrQueries/zaps/useZapsQuery';
import { nip19 } from 'nostr-tools';
import { Divider } from 'primereact/divider';
import { getTotalFromZaps } from '@/utils/lightning';
import dynamic from 'next/dynamic';
import useWindowWidth from '@/hooks/useWindowWidth';
import appConfig from '@/config/appConfig';
import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson';
@ -15,10 +14,7 @@ import { Menu } from 'primereact/menu';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
import { useSession } from 'next-auth/react';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => {
const [zapAmount, setZapAmount] = useState(0);
@ -175,7 +171,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
if (isPaid && decryptionPerformed) {
return (
<div ref={mdDisplayRef}>
<MDDisplay className={'p-4 rounded-lg w-full'} source={lesson.content} />
<MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />
</div>
);
}
@ -217,7 +213,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
if (lesson?.content) {
return (
<div ref={mdDisplayRef}>
<MDDisplay className={'p-4 rounded-lg w-full'} source={lesson.content} />
<MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />
</div>
);
}

View File

@ -4,7 +4,6 @@ import Image from 'next/image';
import { useImageProxy } from '@/hooks/useImageProxy';
import { getTotalFromZaps } from '@/utils/lightning';
import ZapDisplay from '@/components/zaps/ZapDisplay';
import dynamic from 'next/dynamic';
import { useZapsQuery } from '@/hooks/nostrQueries/zaps/useZapsQuery';
import { Toast } from 'primereact/toast';
import useTrackDocumentLesson from '@/hooks/tracking/useTrackDocumentLesson';
@ -13,10 +12,7 @@ import { nip19 } from 'nostr-tools';
import appConfig from '@/config/appConfig';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
import { useSession } from 'next-auth/react';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => {
const [zapAmount, setZapAmount] = useState(0);
@ -114,7 +110,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
const renderContent = () => {
if (isPaid && decryptionPerformed) {
return <MDDisplay className="p-4 rounded-lg w-full" source={lesson.content} />;
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
}
if (isPaid && !decryptionPerformed) {
return (
@ -124,7 +120,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete
);
}
if (lesson?.content) {
return <MDDisplay className="p-4 rounded-lg w-full" source={lesson.content} />;
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
}
return null;
};

View File

@ -7,17 +7,13 @@ import { useZapsQuery } from '@/hooks/nostrQueries/zaps/useZapsQuery';
import { nip19 } from 'nostr-tools';
import { Divider } from 'primereact/divider';
import { getTotalFromZaps } from '@/utils/lightning';
import dynamic from 'next/dynamic';
import useWindowWidth from '@/hooks/useWindowWidth';
import appConfig from '@/config/appConfig';
import useTrackDocumentLesson from '@/hooks/tracking/useTrackDocumentLesson';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
import { useSession } from 'next-auth/react';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => {
const [zapAmount, setZapAmount] = useState(0);
@ -118,7 +114,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
const renderContent = () => {
if (isPaid && decryptionPerformed) {
return <MDDisplay className="p-4 rounded-lg w-full" source={lesson.content} />;
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
}
if (isPaid && !decryptionPerformed) {
return (
@ -133,7 +129,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
);
}
if (lesson?.content) {
return <MDDisplay className="p-4 rounded-lg w-full" source={lesson.content} />;
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
}
return null;
};

View File

@ -4,7 +4,6 @@ import { useImageProxy } from '@/hooks/useImageProxy';
import { Tag } from 'primereact/tag';
import GenericButton from '@/components/buttons/GenericButton';
import Image from 'next/image';
import dynamic from 'next/dynamic';
import axios from 'axios';
import { nip04, nip19 } from 'nostr-tools';
import { v4 as uuidv4 } from 'uuid';
@ -18,10 +17,7 @@ import { validateEvent } from '@/utils/nostr';
import appConfig from '@/config/appConfig';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import 'primeicons/primeicons.css';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
export default function DraftCourseDetails({ processedEvent, draftId, lessons }) {
const [author, setAuthor] = useState(null);
@ -467,7 +463,7 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
</div>
<div className="w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]">
{processedEvent?.content && (
<MDDisplay className="p-4 rounded-lg" source={processedEvent.content} />
<MarkdownDisplay content={processedEvent.content} className="p-4 rounded-lg" />
)}
</div>
</div>

View File

@ -6,11 +6,7 @@ import Image from 'next/image';
import { useImageProxy } from '@/hooks/useImageProxy';
import { formatDateTime, formatUnixTimestamp } from '@/utils/time';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const DraftCourseLesson = ({ lesson, course }) => {
const [isPublished, setIsPublished] = useState(false);
@ -149,7 +145,7 @@ const DraftCourseLesson = ({ lesson, course }) => {
</div>
</div>
<div className="w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]">
{lesson?.content && <MDDisplay className="p-4 rounded-lg" source={lesson.content} />}
{lesson?.content && <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg" />}
</div>
</div>
);

View File

@ -6,7 +6,6 @@ import { useImageProxy } from '@/hooks/useImageProxy';
import { useZapsQuery } from '@/hooks/nostrQueries/zaps/useZapsQuery';
import { nip19 } from 'nostr-tools';
import { getTotalFromZaps } from '@/utils/lightning';
import dynamic from 'next/dynamic';
import { Divider } from 'primereact/divider';
import appConfig from '@/config/appConfig';
import useWindowWidth from '@/hooks/useWindowWidth';
@ -14,10 +13,7 @@ import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
import { useSession } from 'next-auth/react';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => {
const [zapAmount, setZapAmount] = useState(0);
@ -170,7 +166,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
if (isPaid && decryptionPerformed) {
return (
<div ref={mdDisplayRef}>
<MDDisplay className="p-0 rounded-lg w-full" source={lesson.content} />
<MarkdownDisplay content={lesson.content} className="p-0 rounded-lg w-full" />
</div>
);
} else if (isPaid && !decryptionPerformed) {
@ -196,7 +192,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
} else if (lesson?.content) {
return (
<div ref={mdDisplayRef}>
<MDDisplay className="p-0 rounded-lg w-full" source={lesson.content} />
<MarkdownDisplay content={lesson.content} className="p-0 rounded-lg w-full" />
</div>
);
}

View File

@ -12,13 +12,9 @@ import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscripti
import { getTotalFromZaps } from '@/utils/lightning';
import { useSession } from 'next-auth/react';
import useWindowWidth from '@/hooks/useWindowWidth';
import dynamic from 'next/dynamic';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const DocumentDetails = ({
processedEvent,
@ -211,7 +207,7 @@ const DocumentDetails = ({
const renderContent = () => {
if (decryptedContent) {
return <MDDisplay className="p-2 rounded-lg w-full" source={decryptedContent} />;
return <MarkdownDisplay content={decryptedContent} className="p-2 rounded-lg w-full" />;
}
if (paidResource && !decryptedContent) {
return (
@ -237,7 +233,7 @@ const DocumentDetails = ({
);
}
if (processedEvent?.content) {
return <MDDisplay className="p-4 rounded-lg w-full" source={processedEvent.content} />;
return <MarkdownDisplay content={processedEvent.content} className="p-4 rounded-lg w-full" />;
}
return null;
};

View File

@ -12,13 +12,9 @@ import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscripti
import { getTotalFromZaps } from '@/utils/lightning';
import { useSession } from 'next-auth/react';
import useWindowWidth from '@/hooks/useWindowWidth';
import dynamic from 'next/dynamic';
import { Toast } from 'primereact/toast';
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const VideoDetails = ({
processedEvent,
@ -208,7 +204,7 @@ const VideoDetails = ({
const renderContent = () => {
if (decryptedContent) {
return <MDDisplay className="p-0 rounded-lg w-full" source={decryptedContent} />;
return <MarkdownDisplay content={decryptedContent} className="p-0 rounded-lg w-full" />;
}
if (paidResource && !decryptedContent) {
return (
@ -241,7 +237,7 @@ const VideoDetails = ({
);
}
if (processedEvent?.content) {
return <MDDisplay className="p-0 rounded-lg w-full" source={processedEvent.content} />;
return <MarkdownDisplay content={processedEvent.content} className="p-0 rounded-lg w-full" />;
}
return null;
};

View File

@ -0,0 +1,27 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import remarkBreaks from 'remark-breaks';
import 'github-markdown-css/github-markdown-dark.css';
const MarkdownDisplay = ({ content, className = "" }) => {
if (!content) return null;
return (
<div className={`markdown-body bg-gray-900 ${className}`}>
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
rehypePlugins={[rehypeRaw]}
components={{
a: ({node, ...props}) => <a target="_blank" rel="noopener noreferrer" {...props} />,
img: ({node, ...props}) => <img {...props} className="max-w-full rounded my-2" />
}}
>
{content}
</ReactMarkdown>
</div>
);
};
export default MarkdownDisplay;

View File

@ -18,8 +18,7 @@ import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
import useWindowWidth from '@/hooks/useWindowWidth';
import MenuTab from '@/components/menutab/MenuTab';
import { Tag } from 'primereact/tag';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), { ssr: false });
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
const useCourseData = (ndk, fetchAuthor, router) => {
const [course, setCourse] = useState(null);
@ -430,6 +429,7 @@ const Course = () => {
isPaid={paidCourse}
setCompleted={setCompleted}
/>
);
} else if (lesson.type === 'video' && !lesson.topics?.includes('document')) {
return (
@ -456,7 +456,7 @@ const Course = () => {
return (
<>
<div className="mx-auto px-8 max-mob:px-1 mb-12 mt-4">
<div className="mx-auto px-8 max-mob:px-0 mb-12 mt-4">
{/* Tab navigation using MenuTab component */}
<div className="sticky z-10 bg-transparent border-b border-gray-700/30"
style={{
@ -486,25 +486,25 @@ const Course = () => {
{/* Content tab content */}
<div className={`${activeTab === 'content' ? 'block' : 'hidden'}`}>
{uniqueLessons.length > 0 && uniqueLessons[activeIndex] ? (
<div className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden">
<div className="bg-gray-900 rounded-lg shadow-sm overflow-hidden">
{renderLesson(uniqueLessons[activeIndex])}
</div>
) : (
<div className="text-center bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-8">
<div className="text-center bg-gray-900 rounded-lg p-8">
<p>Select a lesson from the sidebar to begin learning.</p>
</div>
)}
{course?.content && (
<div className="mt-8 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
<MDDisplay className="p-4 rounded-lg" source={course.content} />
<div className="mt-8 bg-gray-900 rounded-lg shadow-sm">
<MarkdownDisplay content={course.content} className="p-4 rounded-lg" />
</div>
)}
</div>
{/* Lessons tab - only visible on mobile */}
<div className={`${activeTab === 'lessons' && isMobileView ? 'block' : 'hidden'}`}>
<div className="text-center bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-8">
<div className="text-center bg-gray-900 rounded-lg p-8">
<p>Please use the sidebar to navigate lessons.</p>
</div>
</div>

View File

@ -15,15 +15,11 @@ import { formatDateTime } from '@/utils/time';
import Image from 'next/image';
import useResponsiveImageDimensions from '@/hooks/useResponsiveImageDimensions';
import 'primeicons/primeicons.css';
import dynamic from 'next/dynamic';
import { validateEvent } from '@/utils/nostr';
import appConfig from '@/config/appConfig';
import { useIsAdmin } from '@/hooks/useIsAdmin';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
ssr: false,
});
import MarkdownDisplay from '@/components/markdown/MarkdownDisplay';
export default function Draft() {
const [draft, setDraft] = useState(null);
@ -433,7 +429,7 @@ export default function Draft() {
</div>
</div>
<div className="w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]">
{draft?.content && <MDDisplay className="p-4 rounded-lg" source={draft.content} />}
{draft?.content && <MarkdownDisplay content={draft.content} className="p-4 rounded-lg" />}
</div>
</div>
);