mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 01:02:04 +00:00
course form mostly
This commit is contained in:
parent
b92a0ada02
commit
45bc85bba6
3328
package-lock.json
generated
3328
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,11 +6,13 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"postinstall": "npx prisma generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getalby/bitcoin-connect-react": "^3.5.3",
|
"@getalby/bitcoin-connect-react": "^3.5.3",
|
||||||
"@prisma/client": "^5.17.0",
|
"@prisma/client": "^5.17.0",
|
||||||
|
"@uiw/react-markdown-preview": "^5.1.2",
|
||||||
"@uiw/react-md-editor": "^3.11.0",
|
"@uiw/react-md-editor": "^3.11.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
@ -8,6 +8,8 @@ import { Dropdown } from "primereact/dropdown";
|
|||||||
import { v4 as uuidv4, v4 } from 'uuid';
|
import { v4 as uuidv4, v4 } from 'uuid';
|
||||||
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage";
|
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage";
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
import { useNostr } from "@/hooks/useNostr";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useToast } from "@/hooks/useToast";
|
||||||
import { nip19 } from "nostr-tools"
|
import { nip19 } from "nostr-tools"
|
||||||
import { parseEvent } from "@/utils/nostr";
|
import { parseEvent } from "@/utils/nostr";
|
||||||
import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem";
|
import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem";
|
||||||
@ -29,6 +31,9 @@ const CourseForm = () => {
|
|||||||
const { fetchResources, fetchWorkshops, publish, fetchSingleEvent } = useNostr();
|
const { fetchResources, fetchWorkshops, publish, fetchSingleEvent } = useNostr();
|
||||||
const [pubkey, setPubkey] = useState('');
|
const [pubkey, setPubkey] = useState('');
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { showToast } = useToast();
|
||||||
|
|
||||||
const fetchAllContent = async () => {
|
const fetchAllContent = async () => {
|
||||||
try {
|
try {
|
||||||
// Fetch drafts from the database
|
// Fetch drafts from the database
|
||||||
@ -60,8 +65,6 @@ const CourseForm = () => {
|
|||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
console.log('lessons', selectedLessons);
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -74,7 +77,7 @@ const CourseForm = () => {
|
|||||||
// If the lesson is already published, add its id to finalIds
|
// If the lesson is already published, add its id to finalIds
|
||||||
finalIds.push(lesson.id);
|
finalIds.push(lesson.id);
|
||||||
} else {
|
} else {
|
||||||
// If the lesson is unpublished, create an event and sign it
|
// If the lesson is unpublished, create an event and sign it, publish it, save to db, and add its id to finalIds
|
||||||
let event;
|
let event;
|
||||||
if (lesson.price) {
|
if (lesson.price) {
|
||||||
event = {
|
event = {
|
||||||
@ -117,6 +120,8 @@ const CourseForm = () => {
|
|||||||
const published = await publish(signedEvent);
|
const published = await publish(signedEvent);
|
||||||
|
|
||||||
if (published) {
|
if (published) {
|
||||||
|
// need to save resource to db
|
||||||
|
|
||||||
// delete the draft
|
// delete the draft
|
||||||
axios.delete(`/api/drafts/${lesson.id}`)
|
axios.delete(`/api/drafts/${lesson.id}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -129,8 +134,6 @@ const CourseForm = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('finalIds:', finalIds);
|
|
||||||
|
|
||||||
// Fetch all of the lessons from Nostr by their ids
|
// Fetch all of the lessons from Nostr by their ids
|
||||||
const fetchedLessons = await Promise.all(
|
const fetchedLessons = await Promise.all(
|
||||||
finalIds.map(async (id) => {
|
finalIds.map(async (id) => {
|
||||||
@ -140,8 +143,6 @@ const CourseForm = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('fetchedLessons:', fetchedLessons);
|
|
||||||
|
|
||||||
// // Parse the fields from the lessons to get all of the necessary information
|
// // Parse the fields from the lessons to get all of the necessary information
|
||||||
const parsedLessons = fetchedLessons.map((lesson) => {
|
const parsedLessons = fetchedLessons.map((lesson) => {
|
||||||
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(lesson);
|
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(lesson);
|
||||||
@ -161,15 +162,19 @@ const CourseForm = () => {
|
|||||||
|
|
||||||
if (parsedLessons.length === selectedLessons.length) {
|
if (parsedLessons.length === selectedLessons.length) {
|
||||||
// Create a new course event
|
// Create a new course event
|
||||||
|
const newCourseId = uuidv4();
|
||||||
const courseEvent = {
|
const courseEvent = {
|
||||||
kind: 30004,
|
kind: 30004,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
content: "",
|
content: "",
|
||||||
tags: [
|
tags: [
|
||||||
['d', uuidv4()],
|
['d', newCourseId],
|
||||||
|
// add a tag for plebdevs community at some point
|
||||||
['name', title],
|
['name', title],
|
||||||
['picture', coverImage],
|
['picture', coverImage],
|
||||||
['about', summary],
|
['image', coverImage],
|
||||||
|
['description', summary],
|
||||||
|
['l', "Education"],
|
||||||
...parsedLessons.map((lesson) => ['a', `${lesson.kind}:${lesson.pubkey}:${lesson.d}`]),
|
...parsedLessons.map((lesson) => ['a', `${lesson.kind}:${lesson.pubkey}:${lesson.d}`]),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -181,18 +186,27 @@ const CourseForm = () => {
|
|||||||
const published = await publish(signedCourseEvent);
|
const published = await publish(signedCourseEvent);
|
||||||
|
|
||||||
if (published) {
|
if (published) {
|
||||||
|
axios.post('/api/courses', {
|
||||||
// Reset the form fields after publishing the course
|
id: newCourseId,
|
||||||
setTitle('');
|
resources: {
|
||||||
setSummary('');
|
connect: parsedLessons.map(lesson => ({ id: lesson.id }))
|
||||||
setChecked(false);
|
},
|
||||||
setPrice(0);
|
noteId: signedCourseEvent.id,
|
||||||
setCoverImage('');
|
user: {
|
||||||
setLessons([{ id: uuidv4(), title: 'Select a lesson' }]);
|
connect: {
|
||||||
setSelectedLessons([]);
|
id: user.id
|
||||||
setTopics(['']);
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Course created:', response);
|
||||||
|
router.push(`/course/${signedCourseEvent.id}`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error creating course:', error);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Handle error
|
showToast('error', 'Error', 'Failed to publish course. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
111
src/components/forms/CourseFormNew.js
Normal file
111
src/components/forms/CourseFormNew.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Course Creation Flow:
|
||||||
|
* 1. Generate a new course ID
|
||||||
|
* 2. Process each lesson:
|
||||||
|
* - If unpublished: create event, publish to Nostr, save to DB, delete draft
|
||||||
|
* - If published: use existing data
|
||||||
|
* 3. Create and publish course event to Nostr
|
||||||
|
* 4. Save course to database
|
||||||
|
* 5. Show success message and redirect to course page
|
||||||
|
*/
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const newCourseId = uuidv4();
|
||||||
|
const processedLessons = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Process lessons
|
||||||
|
for (const lesson of selectedLessons) {
|
||||||
|
let noteId = lesson.noteId;
|
||||||
|
|
||||||
|
if (!lesson.published_at) {
|
||||||
|
// Publish unpublished lesson
|
||||||
|
const event = createLessonEvent(lesson);
|
||||||
|
const signedEvent = await window.nostr.signEvent(event);
|
||||||
|
const published = await publish(signedEvent);
|
||||||
|
|
||||||
|
if (!published) {
|
||||||
|
throw new Error(`Failed to publish lesson: ${lesson.title}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
noteId = signedEvent.id;
|
||||||
|
|
||||||
|
// Save to db and delete draft
|
||||||
|
await Promise.all([
|
||||||
|
axios.post('/api/resources', {
|
||||||
|
id: lesson.id,
|
||||||
|
noteId: noteId,
|
||||||
|
userId: user.id,
|
||||||
|
price: lesson.price || 0,
|
||||||
|
}),
|
||||||
|
axios.delete(`/api/drafts/${lesson.id}`)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
processedLessons.push({ id: lesson.id, noteId: noteId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Create and publish course
|
||||||
|
const courseEvent = createCourseEvent(newCourseId, title, summary, coverImage, processedLessons);
|
||||||
|
const signedCourseEvent = await window.nostr.signEvent(courseEvent);
|
||||||
|
const published = await publish(signedCourseEvent);
|
||||||
|
|
||||||
|
if (!published) {
|
||||||
|
throw new Error('Failed to publish course');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Save course to db
|
||||||
|
await axios.post('/api/courses', {
|
||||||
|
id: newCourseId,
|
||||||
|
resources: {
|
||||||
|
connect: processedLessons.map(lesson => ({ id: lesson.id }))
|
||||||
|
},
|
||||||
|
noteId: signedCourseEvent.id,
|
||||||
|
userId: user.id,
|
||||||
|
price: price || 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 4: Show success message and redirect
|
||||||
|
showToast('success', 'Course created successfully');
|
||||||
|
router.push(`/course/${newCourseId}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating course:', error);
|
||||||
|
showToast('error', error.message || 'Failed to create course. Please try again.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createLessonEvent = (lesson) => ({
|
||||||
|
kind: lesson.price ? 30402 : 30023,
|
||||||
|
content: lesson.content,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [
|
||||||
|
['d', lesson.id],
|
||||||
|
['title', lesson.title],
|
||||||
|
['summary', lesson.summary],
|
||||||
|
['image', lesson.image],
|
||||||
|
...lesson.topics.map(topic => ['t', topic]),
|
||||||
|
['published_at', Math.floor(Date.now() / 1000).toString()],
|
||||||
|
...(lesson.price ? [
|
||||||
|
['price', lesson.price],
|
||||||
|
['location', `https://plebdevs.com/${lesson.topics[1]}/${lesson.id}`]
|
||||||
|
] : [])
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const createCourseEvent = (courseId, title, summary, coverImage, lessons) => ({
|
||||||
|
kind: 30004,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
content: "",
|
||||||
|
tags: [
|
||||||
|
['d', courseId],
|
||||||
|
['name', title],
|
||||||
|
['picture', coverImage],
|
||||||
|
['image', coverImage],
|
||||||
|
['description', summary],
|
||||||
|
['l', "Education"],
|
||||||
|
...lessons.map((lesson) => ['a', `${lesson.kind}:${lesson.pubkey}:${lesson.id}`]),
|
||||||
|
],
|
||||||
|
});
|
@ -21,7 +21,16 @@ export const getCourseById = async (id) => {
|
|||||||
|
|
||||||
export const createCourse = async (data) => {
|
export const createCourse = async (data) => {
|
||||||
return await prisma.course.create({
|
return await prisma.course.create({
|
||||||
data,
|
data: {
|
||||||
|
id: data.id,
|
||||||
|
noteId: data.noteId,
|
||||||
|
user: data.user,
|
||||||
|
resources: data.resources
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
resources: true,
|
||||||
|
user: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,9 +78,11 @@ export function useNostr() {
|
|||||||
[{ ids: [id] }],
|
[{ ids: [id] }],
|
||||||
{
|
{
|
||||||
onevent: (event) => {
|
onevent: (event) => {
|
||||||
|
console.log('Fetched event:', event);
|
||||||
resolve(event);
|
resolve(event);
|
||||||
},
|
},
|
||||||
onerror: (error) => {
|
onerror: (error) => {
|
||||||
|
console.error('Failed to fetch event:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,13 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
import { useNostr } from "@/hooks/useNostr";
|
||||||
import { parseEvent } from "@/utils/nostr";
|
import { parseEvent } from "@/utils/nostr";
|
||||||
import ReactMarkdown from 'react-markdown';
|
import dynamic from 'next/dynamic';
|
||||||
import rehypeRaw from 'rehype-raw';
|
const MDDisplay = dynamic(
|
||||||
|
() => import("@uiw/react-markdown-preview"),
|
||||||
const MarkdownContent = ({ content }) => {
|
{
|
||||||
return (
|
ssr: false,
|
||||||
<div>
|
}
|
||||||
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
|
);
|
||||||
{content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Course = () => {
|
const Course = () => {
|
||||||
const [course, setCourse] = useState(null);
|
const [course, setCourse] = useState(null);
|
||||||
@ -45,7 +39,7 @@ const Course = () => {
|
|||||||
<h2 className="text-lg text-center whitespace-pre-line">{course?.summary}</h2>
|
<h2 className="text-lg text-center whitespace-pre-line">{course?.summary}</h2>
|
||||||
<div className="mx-auto my-6">
|
<div className="mx-auto my-6">
|
||||||
{
|
{
|
||||||
course?.content && <MarkdownContent content={course?.content} />
|
course?.content && <MDDisplay source={course.content} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,19 +12,13 @@ import Image from 'next/image';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import ReactMarkdown from 'react-markdown';
|
const MDDisplay = dynamic(
|
||||||
import rehypeRaw from 'rehype-raw';
|
() => import("@uiw/react-markdown-preview"),
|
||||||
|
{
|
||||||
const MarkdownContent = ({ content }) => {
|
ssr: false,
|
||||||
return (
|
}
|
||||||
<div>
|
);
|
||||||
<ReactMarkdown rehypePlugins={[rehypeRaw]} className='markdown-content'>
|
|
||||||
{content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BitcoinConnectPayButton = dynamic(
|
const BitcoinConnectPayButton = dynamic(
|
||||||
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
|
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton),
|
||||||
@ -193,7 +187,7 @@ export default function Details() {
|
|||||||
)}
|
)}
|
||||||
<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]'>
|
<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 && <MarkdownContent content={processedEvent.content} />
|
processedEvent?.content && <MDDisplay source={processedEvent.content} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,19 +13,13 @@ import { Tag } from 'primereact/tag';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import useResponsiveImageDimensions from '@/hooks/useResponsiveImageDimensions';
|
import useResponsiveImageDimensions from '@/hooks/useResponsiveImageDimensions';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import ReactMarkdown from 'react-markdown';
|
const MDDisplay = dynamic(
|
||||||
import rehypeRaw from 'rehype-raw';
|
() => import("@uiw/react-markdown-preview"),
|
||||||
|
{
|
||||||
const MarkdownContent = ({ content }) => {
|
ssr: false,
|
||||||
return (
|
}
|
||||||
<div>
|
);
|
||||||
<ReactMarkdown rehypePlugins={[rehypeRaw]} className='markdown-content'>
|
|
||||||
{content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Details() {
|
export default function Details() {
|
||||||
const [draft, setDraft] = useState(null);
|
const [draft, setDraft] = useState(null);
|
||||||
@ -314,7 +308,7 @@ export default function Details() {
|
|||||||
</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]'>
|
<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 && <MarkdownContent content={draft.content} />
|
draft?.content && <MDDisplay source={draft.content} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import CoursesCarousel from '@/components/content/carousels/CoursesCarousel';
|
import CoursesCarousel from '@/components/content/carousels/CoursesCarousel';
|
||||||
import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel';
|
import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel';
|
||||||
import HeroBanner from '@/components/banner/HeroBanner';
|
import HeroBanner from '@/components/banner/HeroBanner';
|
||||||
@ -10,20 +10,19 @@ import axios from 'axios';
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [contentIds, setContentIds] = useLocalStorageWithEffect('contentIds', []);
|
const [contentIds, setContentIds] = useLocalStorageWithEffect('contentIds', []);
|
||||||
|
|
||||||
// this is ready so now we can pass all ids into fetch hooks from loacl storage
|
const fetchContentIds = useCallback(async () => {
|
||||||
useEffect(() => {
|
try {
|
||||||
const fetchContentIds = async () => {
|
const response = await axios.get('/api/content/all');
|
||||||
try {
|
const ids = response.data;
|
||||||
const response = await axios.get('/api/content/all');
|
setContentIds(ids);
|
||||||
const ids = response.data;
|
} catch (error) {
|
||||||
setContentIds(ids);
|
console.error('Failed to fetch content IDs:', error);
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Failed to fetch content IDs:', error);
|
}, []);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchContentIds();
|
fetchContentIds();
|
||||||
}, [setContentIds]);
|
}, [fetchContentIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -2,18 +2,6 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
import { useNostr } from "@/hooks/useNostr";
|
||||||
import { parseEvent } from "@/utils/nostr";
|
import { parseEvent } from "@/utils/nostr";
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import rehypeRaw from 'rehype-raw';
|
|
||||||
|
|
||||||
const MarkdownContent = ({ content }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
|
|
||||||
{content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Resource = () => {
|
const Resource = () => {
|
||||||
@ -48,7 +36,7 @@ const Resource = () => {
|
|||||||
<h2 className="text-lg text-center whitespace-pre-line">{resource?.summary}</h2>
|
<h2 className="text-lg text-center whitespace-pre-line">{resource?.summary}</h2>
|
||||||
<div className="mx-auto my-6">
|
<div className="mx-auto my-6">
|
||||||
{
|
{
|
||||||
resource?.content && <MarkdownContent content={resource?.content} />
|
resource?.content && <MDDisplay source={resource.content} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,4 +95,37 @@ h3 {
|
|||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MarkdownDisplay styles */
|
||||||
|
pre {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-tag {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-template-tag,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #ce9178;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user