Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/docs/public/assets/lessons/embed/create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/assets/lessons/embed/save.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/assets/pages/embed-block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions apps/docs/src/pages/en/courses/add-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions apps/docs/src/pages/en/lessons/embed.md
Original file line number Diff line number Diff line change
@@ -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 <a href="https://discord.com/invite/GR4bQsN" target="_blank">Discord</a> channel or send a tweet at <a href="https://twitter.com/courselit" target="_blank">@CourseLit</a>.
56 changes: 51 additions & 5 deletions apps/docs/src/pages/en/website/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
</details>
</details>

### [Rich Text](#rich-text)

Expand Down Expand Up @@ -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 <kbd>Enter</kbd>.
![Create a hyperlink in rich text block](/assets/pages/courselit-text-editor-create-links.gif)
</details>
</details>

### [Hero](#hero)

Expand All @@ -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`.
</details>
</details>

### [Grid](#grid)

Expand Down Expand Up @@ -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`.
</details>
</details>

### [Featured](#featured)

Expand Down Expand Up @@ -250,6 +250,52 @@ Following is an animation that shows the entire flow.

</details>

### [Embed](#embed)

<details>
<summary>Expand to see Embed block details</summary>

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:
<br />
<br />

- YouTube
- Vimeo
- Google Docs
- Microsoft Word
- Google Forms
- Typeform
- Microsoft Forms
- Google Calendar
- GitHub Gist
- Apple Podcasts
- SoundCloud
- Spotify
- Senja
- Addcal
<br />
<br />

Here is [Cal.com](https://cal.com/)'s embed looks on a page.
<br />
<br />

![Embed block](/assets/pages/embed-block.png)

> Embed code not working? Please reach out to us on <a href="https://discord.com/invite/GR4bQsN" target="_blank">Discord</a>.

</details>

### [Footer](#footer)

<details>
Expand All @@ -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)
</details>
</details>

## [Shared blocks](#shared-blocks)

Expand Down
10 changes: 8 additions & 2 deletions apps/web/components/public/lesson-viewer/embed-viewer.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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("<script");

return (
<>
{content.value.includes("youtube") ||
content.value.includes("youtu.be") ? (
{isYouTube ? (
<YouTubeEmbed content={content.value} />
) : hasScript ? (
<SandboxedEmbed content={content.value} />
) : (
<div
dangerouslySetInnerHTML={{
Expand Down
2 changes: 1 addition & 1 deletion apps/web/ui-config/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,5 +726,5 @@ export const PRODUCTS_LIST_EMPTY_DESCRIPTION_PRIVATE =
"You have not added any products yet.";
export const LOGIN_CODE_SENT_MESSAGE =
"We have emailed you a one time password.";
export const LESSON_EMBED_URL_LABEL = "URL or Iframe Code";
export const LESSON_EMBED_URL_LABEL = "Embed code";
export const LESSON_CONTENT_LABEL = "Content";
1 change: 0 additions & 1 deletion packages/components-library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export * from "./paginated-table";
import getSymbolFromCurrency from "currency-symbol-map";
export * from "./content-card";
export * from "./skeleton-card";
export * from "./video-with-preview";
export * from "./image";
export * from "./vertical-padding-selector";
export * from "./max-width-selector";
Expand Down
76 changes: 37 additions & 39 deletions packages/page-blocks/src/blocks/embed/widget.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useRef, useEffect } from "react";
import React, { useRef } from "react";
import { WidgetProps } from "@courselit/common-models";
import { height as defaultHeight } from "./defaults";
import Settings from "./settings";
import { ThemeStyle } from "@courselit/page-models";
import { Section } from "@courselit/page-primitives";
import { SandboxedEmbed } from "../../components";

export default function Widget({
id,
settings: {
contentType,
content,
Expand All @@ -25,18 +27,22 @@ export default function Widget({
verticalPadding || theme.theme.structure.section.padding.y;

const iframeRef = useRef<HTMLIFrameElement>(null);
const containerRef = useRef<HTMLDivElement>(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"
? ({
position: "relative",
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"
Expand All @@ -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 (
<SandboxedEmbed
id={id}
content={content}
className="w-full"
style={iframeStyle}
/>
);
} else {
// URL-based content
return (
<iframe
ref={iframeRef}
src={content}
className="w-full border-0"
style={iframeStyle}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
);
}
}, [contentType, content]);
};

return (
<Section theme={overiddenTheme} id={cssId}>
Expand All @@ -78,22 +91,7 @@ export default function Widget({
className={`w-full overflow-hidden relative`}
style={containerStyle}
>
{contentType === "script" ? (
<div
ref={containerRef}
className="w-full h-full"
style={iframeStyle}
/>
) : (
<iframe
ref={iframeRef}
src={content}
className="w-full border-0"
style={iframeStyle}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
)}
{renderContent()}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/page-blocks/src/blocks/hero/admin-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import {
PageBuilderSlider,
PageBuilderPropertyHeader,
CssIdField,
AspectRatio,
ImageObjectFit,
Checkbox,
VerticalPaddingSelector,
MaxWidthSelector,
SectionBackgroundPanel,
} from "@courselit/components-library";
import { AspectRatio } from "../../components";
import { isVideo } from "@courselit/utils";
import { Editor } from "@courselit/text-editor";

Expand Down
2 changes: 1 addition & 1 deletion packages/page-blocks/src/blocks/hero/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
WidgetDefaultSettings,
} from "@courselit/common-models";
import { ImageObjectFit } from "@courselit/components-library";
import { AspectRatio } from "@courselit/components-library";
import { AspectRatio } from "../../components";

export default interface Settings extends WidgetDefaultSettings {
title?: string;
Expand Down
4 changes: 2 additions & 2 deletions packages/page-blocks/src/blocks/hero/widget.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { WidgetProps } from "@courselit/common-models";
import Settings from "./settings";
import { Image, VideoWithPreview, Link } from "@courselit/components-library";
import { TextRenderer } from "../../components";
import { Image, Link } from "@courselit/components-library";
import { TextRenderer, VideoWithPreview } from "../../components";
import { isVideo } from "@courselit/utils";
import clsx from "clsx";
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/page-blocks/src/blocks/media/admin-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import {
PageBuilderSlider,
CssIdField,
Checkbox,
AspectRatio,
ImageObjectFit,
Select,
MaxWidthSelector,
VerticalPaddingSelector,
Tooltip,
} from "@courselit/components-library";
import { AspectRatio } from "../../components";
import { isVideo } from "@courselit/utils";
import type { Theme, ThemeStyle } from "@courselit/page-models";
import { Help } from "@courselit/icons";
Expand Down
3 changes: 2 additions & 1 deletion packages/page-blocks/src/blocks/media/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Media, WidgetDefaultSettings } from "@courselit/common-models";
import { AspectRatio, ImageObjectFit } from "@courselit/components-library";
import { ImageObjectFit } from "@courselit/components-library";
import { AspectRatio } from "../../components";
export default interface Settings extends WidgetDefaultSettings {
media?: Media;
youtubeLink?: string;
Expand Down
3 changes: 2 additions & 1 deletion packages/page-blocks/src/blocks/media/widget.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import { WidgetProps } from "@courselit/common-models";
import Settings from "./settings";
import { Image, VideoWithPreview } from "@courselit/components-library";
import { Image } from "@courselit/components-library";
import { VideoWithPreview } from "../../components";
import { isVideo } from "@courselit/utils";
import { Section } from "@courselit/page-primitives";
import { ThemeStyle } from "@courselit/page-models";
Expand Down
2 changes: 2 additions & 0 deletions packages/page-blocks/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from "./product-card";
export * from "./text-renderer";
export * from "./sandboxed-embed";
export * from "./video-with-preview";
Loading
Loading