diff --git a/apps/docs/public/assets/lessons/embed/create.png b/apps/docs/public/assets/lessons/embed/create.png new file mode 100644 index 000000000..a35c7c82e Binary files /dev/null and b/apps/docs/public/assets/lessons/embed/create.png differ diff --git a/apps/docs/public/assets/lessons/embed/details.png b/apps/docs/public/assets/lessons/embed/details.png new file mode 100644 index 000000000..5734ab008 Binary files /dev/null and b/apps/docs/public/assets/lessons/embed/details.png differ diff --git a/apps/docs/public/assets/lessons/embed/preview.png b/apps/docs/public/assets/lessons/embed/preview.png new file mode 100644 index 000000000..bdd43ccd6 Binary files /dev/null and b/apps/docs/public/assets/lessons/embed/preview.png differ diff --git a/apps/docs/public/assets/lessons/embed/save.png b/apps/docs/public/assets/lessons/embed/save.png new file mode 100644 index 000000000..131a66b90 Binary files /dev/null and b/apps/docs/public/assets/lessons/embed/save.png differ diff --git a/apps/docs/public/assets/pages/embed-block.png b/apps/docs/public/assets/pages/embed-block.png new file mode 100644 index 000000000..5642830ce Binary files /dev/null and b/apps/docs/public/assets/pages/embed-block.png differ diff --git a/apps/docs/src/pages/en/courses/add-content.md b/apps/docs/src/pages/en/courses/add-content.md index f1d2319ea..49f2a1aa9 100644 --- a/apps/docs/src/pages/en/courses/add-content.md +++ b/apps/docs/src/pages/en/courses/add-content.md @@ -42,6 +42,8 @@ A lesson is a container for the actual learning material. CourseLit supports mul For embedding HTML or iframe based content. + See the [guide to add an embed](/en/lessons/embed). + 7. Quiz For quizzing your students. You can create graded and non-graded quizzes. diff --git a/apps/docs/src/pages/en/lessons/embed.md b/apps/docs/src/pages/en/lessons/embed.md new file mode 100644 index 000000000..9d6a79cd4 --- /dev/null +++ b/apps/docs/src/pages/en/lessons/embed.md @@ -0,0 +1,37 @@ +--- +title: Add an embed to a course +description: Add an embed to a course +layout: ../../../layouts/MainLayout.astro +--- + +You can easily add embeds to your online courses in CourseLit. This article will show you how. + +## Add an embed to a course + +1. Go to the `Products` page and click on the course you want to add SCORM content to. Click on `Edit content`. + +2. Click on `Add lesson` in any section. + +3. On the New Lesson screen, you'll see a row of lesson type cards. Click on the `Embed` card to select it. + +![Embed card](/assets/lessons/embed/create.png) + +4. Enter the title and the embed code. + +![Embed details](/assets/lessons/embed/details.png) + +5. The preview of the embeddable content will show up below the embed code. + +![Embed preview](/assets/lessons/embed/preview.png) + +6. Hit `Save` to save changes to your lesson. + +![Embed Save](/assets/lessons/embed/save.png) + +## What embeds are supported + +You can see the list of platforms [here](/en/website/blocks#what-embeds-are-supported). + +## Stuck somewhere? + +We are always there for you. Come chat with us in our Discord channel or send a tweet at @CourseLit. diff --git a/apps/docs/src/pages/en/website/blocks.md b/apps/docs/src/pages/en/website/blocks.md index 072c716db..0185675fa 100644 --- a/apps/docs/src/pages/en/website/blocks.md +++ b/apps/docs/src/pages/en/website/blocks.md @@ -36,7 +36,7 @@ You will also see the newly added link on the header itself. 3. Click on the pencil icon against the newly added link to edit it as shown above. 4. Change the label (displayed as text on the header block) and the URL (where the user should be taken upon clicking the label on the header) and click `Done` to save. ![Header edit link](/assets/pages/header-edit-link.png) - + ### [Rich Text](#rich-text) @@ -69,7 +69,7 @@ The rich text block uses the same text editor available elsewhere on the platfor 2. Click on the floating `link` icon to reveal a text input. 3. In the popup text input, enter the URL as shown below and press Enter. ![Create a hyperlink in rich text block](/assets/pages/courselit-text-editor-create-links.gif) - + ### [Hero](#hero) @@ -95,7 +95,7 @@ Following is how it looks on a page. 4. In the button action, enter the URL the user should be taken to upon clicking. a. If the URL is from your own school, use its relative form, i.e., `/courses`. b. If the URL is from some external website, use the absolute (complete) URL, i.e., `https://website.com/courses`. - + ### [Grid](#grid) @@ -140,7 +140,7 @@ A grid block comes in handy when you want to show some sort of list, for example 4. In the button action, enter the URL the user should be taken to upon clicking. a. If the URL is from your own school, use its relative form, i.e., `/courses`. b. If the URL is from some external website, use the absolute (complete) URL, i.e., `https://website.com/courses`. - + ### [Featured](#featured) @@ -250,6 +250,52 @@ Following is an animation that shows the entire flow. +### [Embed](#embed) + +
+Expand to see Embed block details + +Embedding content from other websites is a common requirement. CourseLit offers a dedicated block that lets you embed content from other websites. + +You can embed websites (using `URL` type) or widgets (using `Script` type). + +The height of the content in case of `Script` type embed is automatically calculated. The `Height` property in the builder has no effect. + +Following is how it looks while displaying a Cal.com calendar. + +## What embeds are supported + +Although, CourseLit platform should support any embed code, we have tested with the following platforms: +
+
+ +- YouTube +- Vimeo +- Google Docs +- Microsoft Word +- Google Forms +- Typeform +- Microsoft Forms +- Google Calendar +- GitHub Gist +- Apple Podcasts +- SoundCloud +- Spotify +- Senja +- Addcal +
+
+ +Here is [Cal.com](https://cal.com/)'s embed looks on a page. +
+
+ +![Embed block](/assets/pages/embed-block.png) + +> Embed code not working? Please reach out to us on Discord. + +
+ ### [Footer](#footer)
@@ -276,7 +322,7 @@ In the `Design` panel, you can customize: - Maximum width - Vertical padding - Social media links (Facebook, Twitter, Instagram, LinkedIn, YouTube, Discord, GitHub) -
+ ## [Shared blocks](#shared-blocks) diff --git a/apps/web/components/public/lesson-viewer/embed-viewer.tsx b/apps/web/components/public/lesson-viewer/embed-viewer.tsx index 7b6ab3040..bcf0c631f 100644 --- a/apps/web/components/public/lesson-viewer/embed-viewer.tsx +++ b/apps/web/components/public/lesson-viewer/embed-viewer.tsx @@ -1,5 +1,6 @@ import React from "react"; import { UIConstants } from "@courselit/common-models"; +import { SandboxedEmbed } from "@courselit/page-blocks"; const YouTubeEmbed = ({ content }: { content: string }) => { const match = content.match(UIConstants.YOUTUBE_REGEX); @@ -22,11 +23,16 @@ interface LessonEmbedViewerProps { } const LessonEmbedViewer = ({ content }: LessonEmbedViewerProps) => { + const isYouTube = + content.value.includes("youtube") || content.value.includes("youtu.be"); + const hasScript = content.value.includes(" - {content.value.includes("youtube") || - content.value.includes("youtu.be") ? ( + {isYouTube ? ( + ) : hasScript ? ( + ) : (
(null); - const containerRef = useRef(null); const formattedHeight = `${height}px`; + // Check if content is "script" or "iframe" (not a direct URL) + const isEmbedCode = contentType === "script"; + const containerStyle = aspectRatio && aspectRatio !== "default" ? ({ @@ -36,7 +40,9 @@ export default function Widget({ width: "100%", paddingTop: `calc(100% / (${aspectRatio.split(":")[0]} / ${aspectRatio.split(":")[1]}))`, } as React.CSSProperties) - : ({ height: formattedHeight } as React.CSSProperties); + : isEmbedCode + ? ({} as React.CSSProperties) + : ({ height: formattedHeight } as React.CSSProperties); const iframeStyle = aspectRatio && aspectRatio !== "default" @@ -47,28 +53,35 @@ export default function Widget({ width: "100%", height: "100%", } as React.CSSProperties) - : ({ height: "100%" } as React.CSSProperties); - - useEffect(() => { - if (contentType === "script" && content && containerRef.current) { - const tempContainer = document.createElement("div"); - tempContainer.innerHTML = content; + : isEmbedCode + ? ({} as React.CSSProperties) + : ({ height: "100%" } as React.CSSProperties); - // Append all elements to the container - Array.from(tempContainer.children).forEach((elem) => { - if (elem.nodeName === "SCRIPT") { - const script = document.createElement("script"); - script.innerHTML = elem.innerHTML; - Array.from(elem.attributes).forEach((attr) => { - script.setAttribute(attr.name, attr.value); - }); - containerRef.current?.appendChild(script); - } else { - containerRef.current?.appendChild(elem.cloneNode(true)); - } - }); + const renderContent = () => { + if (isEmbedCode) { + // Content is a script or iframe code - use sandboxed embed for dynamic height + return ( + + ); + } else { + // URL-based content + return ( +