diff --git a/package-lock.json b/package-lock.json index baa362a..97276e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index edaf6d3..55bb32a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/content/combined/CombinedDetails.js b/src/components/content/combined/CombinedDetails.js index e254b7e..f3fbae9 100644 --- a/src/components/content/combined/CombinedDetails.js +++ b/src/components/content/combined/CombinedDetails.js @@ -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 ; + return ; } if (paidResource && !decryptedContent) { @@ -231,7 +229,7 @@ const CombinedDetails = ({ } if (processedEvent?.content) { - return ; + return ; } return null; diff --git a/src/components/content/courses/CombinedLesson.js b/src/components/content/courses/CombinedLesson.js index af705ad..61f9854 100644 --- a/src/components/content/courses/CombinedLesson.js +++ b/src/components/content/courses/CombinedLesson.js @@ -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 (
- +
); } @@ -217,7 +213,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple if (lesson?.content) { return (
- +
); } diff --git a/src/components/content/courses/CourseLesson.js b/src/components/content/courses/CourseLesson.js index 3447cce..07230f3 100644 --- a/src/components/content/courses/CourseLesson.js +++ b/src/components/content/courses/CourseLesson.js @@ -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 ; + return ; } if (isPaid && !decryptionPerformed) { return ( @@ -124,7 +120,7 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid, setComplete ); } if (lesson?.content) { - return ; + return ; } return null; }; diff --git a/src/components/content/courses/DocumentLesson.js b/src/components/content/courses/DocumentLesson.js index 81e5664..017153a 100644 --- a/src/components/content/courses/DocumentLesson.js +++ b/src/components/content/courses/DocumentLesson.js @@ -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 ; + return ; } if (isPaid && !decryptionPerformed) { return ( @@ -133,7 +129,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple ); } if (lesson?.content) { - return ; + return ; } return null; }; diff --git a/src/components/content/courses/DraftCourseDetails.js b/src/components/content/courses/DraftCourseDetails.js index b228908..8649443 100644 --- a/src/components/content/courses/DraftCourseDetails.js +++ b/src/components/content/courses/DraftCourseDetails.js @@ -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 })
{processedEvent?.content && ( - + )}
diff --git a/src/components/content/courses/DraftCourseLesson.js b/src/components/content/courses/DraftCourseLesson.js index d2886c5..1c1db42 100644 --- a/src/components/content/courses/DraftCourseLesson.js +++ b/src/components/content/courses/DraftCourseLesson.js @@ -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 }) => {
- {lesson?.content && } + {lesson?.content && }
); diff --git a/src/components/content/courses/VideoLesson.js b/src/components/content/courses/VideoLesson.js index e6ba7ff..9f59e46 100644 --- a/src/components/content/courses/VideoLesson.js +++ b/src/components/content/courses/VideoLesson.js @@ -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 (
- +
); } else if (isPaid && !decryptionPerformed) { @@ -196,7 +192,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted } else if (lesson?.content) { return (
- +
); } diff --git a/src/components/content/documents/DocumentDetails.js b/src/components/content/documents/DocumentDetails.js index 6d91805..290ab24 100644 --- a/src/components/content/documents/DocumentDetails.js +++ b/src/components/content/documents/DocumentDetails.js @@ -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 ; + return ; } if (paidResource && !decryptedContent) { return ( @@ -237,7 +233,7 @@ const DocumentDetails = ({ ); } if (processedEvent?.content) { - return ; + return ; } return null; }; diff --git a/src/components/content/videos/VideoDetails.js b/src/components/content/videos/VideoDetails.js index f9219aa..a41dc4b 100644 --- a/src/components/content/videos/VideoDetails.js +++ b/src/components/content/videos/VideoDetails.js @@ -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 ; + return ; } if (paidResource && !decryptedContent) { return ( @@ -241,7 +237,7 @@ const VideoDetails = ({ ); } if (processedEvent?.content) { - return ; + return ; } return null; }; diff --git a/src/components/markdown/MarkdownDisplay.js b/src/components/markdown/MarkdownDisplay.js new file mode 100644 index 0000000..ce8b13a --- /dev/null +++ b/src/components/markdown/MarkdownDisplay.js @@ -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 ( + + ); +}; + +export default MarkdownDisplay; \ No newline at end of file diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js index 931e858..6d5c71b 100644 --- a/src/pages/course/[slug]/index.js +++ b/src/pages/course/[slug]/index.js @@ -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 ( <> -
+
{/* Tab navigation using MenuTab component */}
{ {/* Content tab content */}
{uniqueLessons.length > 0 && uniqueLessons[activeIndex] ? ( -
+
{renderLesson(uniqueLessons[activeIndex])}
) : ( -
+

Select a lesson from the sidebar to begin learning.

)} {course?.content && ( -
- +
+
)}
{/* Lessons tab - only visible on mobile */}
-
+

Please use the sidebar to navigate lessons.

diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js index 84e709a..6ccdb3f 100644 --- a/src/pages/draft/[slug]/index.js +++ b/src/pages/draft/[slug]/index.js @@ -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() {
- {draft?.content && } + {draft?.content && }
);