diff --git a/src/generic/key-utils.test.ts b/src/generic/key-utils.test.ts index f9e12072a2..db61856458 100644 --- a/src/generic/key-utils.test.ts +++ b/src/generic/key-utils.test.ts @@ -3,6 +3,8 @@ import { ContainerType, getBlockType, getLibraryId, + isContainerType, + isContainerUsageKey, isLibraryKey, isLibraryV1Key, normalizeContainerType, @@ -165,4 +167,62 @@ describe('component utils', () => { }); } }); + + describe('isContainerType', () => { + for ( + const containerType of [ + ContainerType.Vertical, + ContainerType.Sequential, + ContainerType.Chapter, + ContainerType.Unit, + ContainerType.Subsection, + ContainerType.Section, + ] as const + ) { + it(`returns true for '${containerType}'`, () => { + expect(isContainerType(containerType)).toBe(true); + }); + } + + for (const containerType of ['html', 'problem', '', 'video']) { + it(`returns false for '${containerType}'`, () => { + expect(isContainerType(containerType)).toBe(false); + }); + } + }); + + describe('isContainerUsageKey', () => { + for ( + const usageKey of [ + 'lct:org:lib:section:my-section-9284e2', + 'lct:org:lib:subsection:my-subsection-9284e2', + 'lct:org:lib:unit:my-unit-9284e2', + 'block-v1:org+type@chapter+block@1', + 'block-v1:org+type@sequential+block@1', + 'block-v1:org+type@vertical+block@1', + 'block-v1:org+type@section+block@1', + 'block-v1:org+type@subsection+block@1', + 'block-v1:org+type@unit+block@1', + ] + ) { + it(`returns true for '${usageKey}'`, () => { + expect(isContainerUsageKey(usageKey)).toBe(true); + }); + } + + for ( + const usageKey of [ + 'lb:org:lib:html:id', + 'block-v1:org+type@problem+block@1', + 'not a key', + '', + undefined, + null, + ] + ) { + it(`returns false for '${usageKey}'`, () => { + expect(isContainerUsageKey(usageKey as any)).toBe(false); + }); + } + }); }); diff --git a/src/generic/key-utils.ts b/src/generic/key-utils.ts index beb8b283f7..b7a75c1028 100644 --- a/src/generic/key-utils.ts +++ b/src/generic/key-utils.ts @@ -141,3 +141,27 @@ export function normalizeContainerType(containerType: ContainerType | string) { return containerType; } } + +/** + * Check whether a block/container type points to a container (section/subsection/unit), + * including legacy names (chapter/sequential/vertical). + */ +export function isContainerType(containerType: ContainerType | string): boolean { + const normalizedType = normalizeContainerType(containerType); + + return [ContainerType.Section, ContainerType.Subsection, ContainerType.Unit].includes( + normalizedType as ContainerType, + ); +} + +/** + * Check whether a usage key belongs to a container. + */ +export function isContainerUsageKey(usageKey: string | undefined | null): boolean { + if (typeof usageKey !== 'string') { + return false; + } + + const blockType = getBlockType(usageKey, 'empty'); + return blockType ? isContainerType(blockType) : false; +} diff --git a/src/library-authoring/LibraryBlock/LibraryBlock.tsx b/src/library-authoring/LibraryBlock/LibraryBlock.tsx index 3bc6a85ec8..9dc971a272 100644 --- a/src/library-authoring/LibraryBlock/LibraryBlock.tsx +++ b/src/library-authoring/LibraryBlock/LibraryBlock.tsx @@ -21,6 +21,7 @@ interface LibraryBlockProps { minHeight?: string; scrollIntoView?: boolean; showTitle?: boolean; + addHeight?: number; } /** * React component that displays an XBlock in a sandboxed IFrame. @@ -40,6 +41,7 @@ export const LibraryBlock = ({ scrolling = 'no', scrollIntoView = false, showTitle = false, + addHeight = 0, }: LibraryBlockProps) => { const { iframeRef, setIframeRef } = useIframe(); const xblockView = view ?? 'student_view'; @@ -50,6 +52,7 @@ export const LibraryBlock = ({ const intl = useIntl(); const params = new URLSearchParams(); + if (version) { params.set('version', version.toString()); } @@ -79,6 +82,10 @@ export const LibraryBlock = ({ useIframeContent(iframeRef, setIframeRef); + if (version === 0) { + return null; + } + return (