diff --git a/cspell.json b/cspell.json index f8852270..e1f72cd9 100644 --- a/cspell.json +++ b/cspell.json @@ -32,6 +32,7 @@ "Clientside", "clsx", "cockroachdb", + "Collingwood", "cuid", "Daseiendes", "Dasein", @@ -47,6 +48,7 @@ "Geraets", "Guyer", "Hackett", + "Halkyon", "headeronly", "Houlgate", "Identität", @@ -98,6 +100,7 @@ "tollere", "Trisokkas", "Übergang", + "Unfreedom", "unmittelbare", "unseparated", "unseparatedness", diff --git a/features/courses/components/CourseCardDeleteButton.tsx b/features/courses/components/CourseCardDeleteButton.tsx index 14cfa3e8..becf1cbe 100644 --- a/features/courses/components/CourseCardDeleteButton.tsx +++ b/features/courses/components/CourseCardDeleteButton.tsx @@ -45,7 +45,7 @@ export function CourseCardDeleteButton({ > Delete Course - + Are you sure you want to delete this {modelName} item? This action cannot be undone. diff --git a/features/courses/components/CourseMaterialCard.tsx b/features/courses/components/CourseMaterialCard.tsx index 8d229e48..ef378d38 100644 --- a/features/courses/components/CourseMaterialCard.tsx +++ b/features/courses/components/CourseMaterialCard.tsx @@ -61,7 +61,7 @@ export function CourseMaterialCard({ > - + Are you sure you want to delete this {modelName} item? This action cannot be undone. diff --git a/features/courses/components/LessonFrontPage.tsx b/features/courses/components/LessonFrontPage.tsx index a4637b75..d06e0b0d 100644 --- a/features/courses/components/LessonFrontPage.tsx +++ b/features/courses/components/LessonFrontPage.tsx @@ -12,7 +12,6 @@ import { Heading } from "lib/components/ui/Heading"; import { VideoDataLoader } from "lib/components/VideoDataLoader"; import { Suspense } from "react"; import { Loading } from "lib/components/animations/Loading"; -import { FadeIn } from "lib/components/animations/FadeIn"; import { Paragraph } from "lib/components/ui/Paragraph"; export async function LessonFrontPage({ lessonSlug }: { lessonSlug: string }) { @@ -34,53 +33,51 @@ export async function LessonFrontPage({ lessonSlug }: { lessonSlug: string }) { const xxl = "2xl:gap-8 2xl:px-20"; return ( - -
-
- {lessonData.video ? ( - }> - - - ) : ( -
No video content
- )} +
+
+ {lessonData.video ? ( + }> + + + ) : ( +
No video content
+ )} +
+
+
+ + {`<- Back to ${lessonData.course.name}`} + +
-
-
- - {`<- Back to ${lessonData.course.name}`} - - -
+
+
+
+ {lessonData.name} + {lessonData.description}
-
-
- {lessonData.name} - {lessonData.description} -
- {lessonData?.content?.mdxCompiled ? ( - - ) : ( -
No lesson content
- )} -
-
- {lessonData?.transcript?.mdxCompiled ? ( - - ) : ( -
No transcript
- )} -
+ {lessonData?.content?.mdxCompiled ? ( + + ) : ( +
No lesson content
+ )} +
+
+ {lessonData?.transcript?.mdxCompiled ? ( + + ) : ( +
No transcript
+ )}
- +
); } diff --git a/features/editor/components/ButtonInsertTeacherProfile.tsx b/features/editor/components/ButtonInsertTeacherProfile.tsx new file mode 100644 index 00000000..7e716392 --- /dev/null +++ b/features/editor/components/ButtonInsertTeacherProfile.tsx @@ -0,0 +1,39 @@ +import { DialogButton, insertJsx$, usePublisher } from "@mdxeditor/editor"; + +export const ButtonInsertTeacherProfile = () => { + const insertJsx = usePublisher(insertJsx$); + // grab the insertDirective action (a.k.a. publisher) from the + // state management system of the directivesPlugin + + return ( + { + const inputLowercase = input.toLocaleLowerCase(); + if ( + inputLowercase && + (inputLowercase.includes("filip") || + inputLowercase.includes("ahilleas")) + ) { + insertJsx({ + name: "EmbedTeacherProfile", + kind: "text", + props: { teacherInput: inputLowercase }, + children: [ + { + type: "text", + value: `Teacher profile ${inputLowercase}`, + }, + ], + }); + } else { + alert("Unsupported teacher name"); + } + }} + /> + ); +}; diff --git a/features/editor/components/ButtonInsertYouTube.tsx b/features/editor/components/ButtonInsertYouTube.tsx new file mode 100644 index 00000000..4d9147c0 --- /dev/null +++ b/features/editor/components/ButtonInsertYouTube.tsx @@ -0,0 +1,35 @@ +import { DialogButton, insertJsx$, usePublisher } from "@mdxeditor/editor"; + +export const ButtonInsertYouTube = () => { + const insertJsx = usePublisher(insertJsx$); + // grab the insertDirective action (a.k.a. publisher) from the + // state management system of the directivesPlugin + + return ( + { + const videoId = new URL(url).searchParams.get("v"); + const videoUrl = `https://www.youtube.com/embed/${videoId}`; + if (videoId && videoUrl) { + insertJsx({ + name: "EmbedYT", + kind: "text", + props: { src: videoUrl }, + children: [ + { + type: "text", + value: `YouTube video ${videoId}`, + }, + ], + }); + } else { + alert("Invalid YouTube URL"); + } + }} + /> + ); +}; diff --git a/features/editor/components/EditorInternals.tsx b/features/editor/components/EditorInternals.tsx index 92bd75ce..69dcb012 100644 --- a/features/editor/components/EditorInternals.tsx +++ b/features/editor/components/EditorInternals.tsx @@ -16,12 +16,14 @@ import { CreateLink, DiffSourceToggleWrapper, EditorInFocus, + GenericJsxEditor, InsertAdmonition, InsertCodeBlock, InsertFrontmatter, InsertImage, InsertTable, InsertThematicBreak, + JsxComponentDescriptor, ListsToggle, MDXEditor, MDXEditorMethods, @@ -36,6 +38,7 @@ import { frontmatterPlugin, headingsPlugin, imagePlugin, + jsxPlugin, linkDialogPlugin, linkPlugin, listsPlugin, @@ -52,6 +55,8 @@ import { Loading } from "lib/components/animations/Loading"; import { actionUploadImage } from "lib/server/actions"; import { sleep } from "lib/utils"; import { actionUpdateMdxModelById } from "../server/actions"; +import { ButtonInsertYouTube } from "./ButtonInsertYouTube"; +import { ButtonInsertTeacherProfile } from "./ButtonInsertTeacherProfile"; /** * Context to hold the state of mutation loading as passing props did not work with the MDXEditor Toolbar. @@ -72,6 +77,26 @@ export default function EditorInternals({ material, title }: EditorProps) { const editorRef = React.useRef(null); const [isLoading, setIsLoading] = useState(false); + /** + * Custom JSX components used in Markdown must be registered here. + */ + const jsxComponentDescriptors: JsxComponentDescriptor[] = [ + { + name: "EmbedYT", + kind: "text", + props: [{ name: "src", type: "string" }], + hasChildren: true, + Editor: GenericJsxEditor, + }, + { + name: "EmbedTeacherProfile", + kind: "text", + props: [{ name: "teacher", type: "string" }], + hasChildren: true, + Editor: GenericJsxEditor, + }, + ]; + const handleSave = async () => { const markdownValue = editorRef.current?.getMarkdown(); if (!markdownValue) { @@ -121,8 +146,9 @@ export default function EditorInternals({ material, title }: EditorProps) { className="border-2 border-gray-200 rounded-lg full-demo-mdxeditor" ref={editorRef} markdown={material.mdx} - contentEditableClassName="prose dark:prose-invert max-w-none" + contentEditableClassName="!prose dark:!prose-invert max-w-none" plugins={[ + jsxPlugin({ jsxComponentDescriptors }), listsPlugin(), quotePlugin(), headingsPlugin(), @@ -271,6 +297,9 @@ const DefaultToolbar: React.FC = ({ ]} /> + + +
); }; - -type CardTeamMemberProps = { - name: string; - title: string; - image: string; - children?: React.ReactNode; -}; - -const CardTeamMember = ({ - name, - title, - image, - children, -}: CardTeamMemberProps) => { - return ( -
-
- {name} -
-

{name}

-

{title}

-
-
-
-

{children}

-
-
- ); -}; diff --git a/features/marketing/data/reviews.json b/features/marketing/data/reviews.json index 85e8415c..0b63ad0d 100644 --- a/features/marketing/data/reviews.json +++ b/features/marketing/data/reviews.json @@ -33,5 +33,12 @@ "text": "My philosophical studies received a jet fuel injection with Filip's teaching. The 'What is Philosophy?' course was beautifully engineered using Collingwood as an anchor text to visit upon several blockbuster philosophical works. Along with weekly videos and materials that brought the themes into sharp focus. Filip allowed us to unpack our thoughts in the seminars and learn at our own pace, guiding us wisely through the seminar discussions and nudging us on our blind spots. I thoroughly enjoyed it and I look forward to seeing my course-mates on another one soon.", "url": false, "img_url": "/static/images/people/stee.webp" + }, + { + "name": "Michael Patton", + "title": "Principal Architect", + "text": "Filip's teaching style embraces the rigor of a Hegelian scholarship with a patient approach that guides us through complex yet transformative insights. I've taken many of Filip's courses; each is immersive, allowing new perspectives to understand the text.", + "url": "https://www.linkedin.com/in/pattonmichael/", + "img_url": "/static/images/people/michael.webp" } ] diff --git a/lib/components/CardTeamProfile.tsx b/lib/components/CardTeamProfile.tsx new file mode 100644 index 00000000..2231217e --- /dev/null +++ b/lib/components/CardTeamProfile.tsx @@ -0,0 +1,42 @@ +import Image from "next/image"; + +type CardTeamMemberProps = { + name: string; + title: string; + image: string; + children?: React.ReactNode; +}; + +export const CardTeamMember = ({ + name, + title, + image, + children, +}: CardTeamMemberProps) => { + return ( +
+
+ {name} +
+

{name}

+

{title}

+
+
+
+

{children}

+
+
+ ); +}; diff --git a/lib/components/MDXRenderer.tsx b/lib/components/MDXRenderer.tsx index a1446e1b..d2dbd7df 100644 --- a/lib/components/MDXRenderer.tsx +++ b/lib/components/MDXRenderer.tsx @@ -6,6 +6,8 @@ import * as runtime from "react/jsx-runtime"; import { type MDXModule } from "mdx/types"; import { MDXCompilerReturnType } from "lib/server/mdxCompiler"; import { Loading } from "./animations/Loading"; +import { EmbedTeacherProfile } from "features/editor/components/EmbedTeacherProfile"; +import { EmbedYT } from "./ui/EmbedYT"; /* cSpell:disable */ @@ -38,8 +40,21 @@ export const MDXRenderer = ({ data }: { data: MDXCompilerReturnType }) => {
-
- +
+ + ); + }, + EmbedYT(props) { + return ; + }, + }} + />
diff --git a/package-lock.json b/package-lock.json index c231b858..86d00e0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@google-cloud/storage": "^7.13.0", - "@mdxeditor/editor": "3.11.5", + "@mdxeditor/editor": "3.14.0", "@mui/icons-material": "^6.4.0", "@mui/material": "^6.4.0", "@prisma/client": "^6.3.0", @@ -3951,9 +3951,9 @@ } }, "node_modules/@mdxeditor/editor": { - "version": "3.11.5", - "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.11.5.tgz", - "integrity": "sha512-OEWe8duzYqSG9BhWsKWOoX+Xqi/B+90E2xtFHsXFXJXciyRUEMnsdAWEx0D/qK4y1Aa7BUnreqcmZ+D5KJn/Bg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.14.0.tgz", + "integrity": "sha512-5upWjI89i+UWhIMf6zu4jiiw1KvjAINVsXQcE4RbJ/zYDj/F2bamAAt35wW5Xrd9nMM8ogrwUS9OKnpFR2AeOA==", "license": "MIT", "dependencies": { "@codemirror/lang-markdown": "^6.2.3", diff --git a/package.json b/package.json index c2f58a50..92627d77 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@google-cloud/storage": "^7.13.0", - "@mdxeditor/editor": "3.11.5", + "@mdxeditor/editor": "3.14.0", "@mui/icons-material": "^6.4.0", "@mui/material": "^6.4.0", "@prisma/client": "^6.3.0", diff --git a/public/static/images/people/michael.webp b/public/static/images/people/michael.webp new file mode 100644 index 00000000..9fb3d0ad Binary files /dev/null and b/public/static/images/people/michael.webp differ