Skip to content
Open
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
1,064 changes: 1,064 additions & 0 deletions data/mundo/articles/cle16n19nd9o.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions scripts/bundleSize/bundleSizeConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@

export const VARIANCE = 5;

export const MIN_SIZE = 913;
export const MAX_SIZE = 1282;
export const MIN_SIZE = 917;
export const MAX_SIZE = 1290;
8 changes: 8 additions & 0 deletions src/app/components/MediaLoader/fixture.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import TranscriptBlock from '../Transcript/fixture.json';

export const aresMediaCaptionBlock = {
id: '31318aec',
type: 'caption',
Expand Down Expand Up @@ -789,6 +791,12 @@ export const liveTvPageMediaBlock = {
},
};

export const aresMediaBlockWithTranscript = [
aresMediaBlock,
aresMediaCaptionBlock,
TranscriptBlock,
];

export const aresMediaBlocks = [aresMediaBlock, aresMediaCaptionBlock];
export const videoClipMediaBlocks = [
livePageVideoClipMediaBlock,
Expand Down
9 changes: 9 additions & 0 deletions src/app/components/MediaLoader/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
aresMediaPortraitBlocks,
videoClipMediaBlocks,
legacyMediaBlock,
aresMediaBlockWithTranscript,
} from './fixture';
import { MediaBlock } from './types';
import readme from './README.md';
Expand Down Expand Up @@ -68,3 +69,11 @@ export const LivePageMedia = () => (
blocks={videoClipMediaBlocks as MediaBlock[]}
/>
);

export const MediaLoaderWithTranscript = () => (
<Component
service="pidgin"
pageType="article"
blocks={aresMediaBlockWithTranscript as MediaBlock[]}
/>
);
22 changes: 22 additions & 0 deletions src/app/components/MediaLoader/index.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,26 @@ export default {
margin: `${spacings.TRIPLE}rem 0 0`,
},
}),
withTranscriptVideo: ({ palette, isDarkUi }: Theme) =>
css({
backgroundColor: isDarkUi ? palette.GREY_7 : palette.WHITE,
marginBottom: 0,
}),
withTranscriptCaption: ({ mq, spacings }: Theme) =>
css({
margin: `${spacings.FULL}rem`,
width: 'auto',
[mq.GROUP_2_ONLY]: {
width: 'auto',
margin: `${spacings.FULL}rem`,
},
[mq.GROUP_4_MIN_WIDTH]: {
width: 'auto',
margin: `${spacings.FULL}rem`,
},
}),
transcript: ({ spacings }: Theme) =>
css({
marginBottom: `${spacings.TRIPLE}rem`,
}),
};
40 changes: 40 additions & 0 deletions src/app/components/MediaLoader/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
aresMediaBlocks,
onDemandTvBlocks,
onDemandTvBlocksWithOverrides,
aresMediaBlockWithTranscript,
} from './fixture';
import { MediaBlock } from './types';
import * as buildConfig from './utils/buildSettings';
Expand Down Expand Up @@ -183,6 +184,45 @@ describe('MediaLoader', () => {
});
});

describe('Transcript', () => {
it('Displays a transcript when provided', async () => {
let container;

await act(async () => {
({ container } = render(
<MediaPlayer blocks={aresMediaBlockWithTranscript as MediaBlock[]} />,
{
id: 'testId',
},
));
});

const details = (container as unknown as HTMLElement).querySelector(
'summary',
);
expect(details?.textContent).toContain('Read transcript');
});

it('Displays a transcript when provided on lite', async () => {
let container;

await act(async () => {
({ container } = render(
<MediaPlayer blocks={aresMediaBlockWithTranscript as MediaBlock[]} />,
{
id: 'testId',
isLite: true,
},
));
});

const details = (container as unknown as HTMLElement).querySelector(
'summary',
);
expect(details?.textContent).toContain('Read transcript');
});
});

describe('Metadata', () => {
it('should render metadata tags when media player is not embedded', async () => {
await act(async () => {
Expand Down
20 changes: 20 additions & 0 deletions src/app/components/MediaLoader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { getBootstrapSrc } from '../Ad/Canonical';
import Metadata from './Metadata';
import AmpMediaLoader from './Amp';
import Message from './Message';
import getTranscriptBlock from './utils/getTranscriptBlock';
import Transcript from '../Transcript';
import getTitleForLiteSiteTranscriptBlock from './utils/getTitleForLiteSiteTranscriptBlock';

const PAGETYPES_IGNORE_PLACEHOLDER: PageTypes[] = [
LIVE_PAGE,
Expand Down Expand Up @@ -226,6 +229,8 @@ const MediaLoader = ({
uniqueId,
eventMapping,
}: Props) => {
const transcriptBlock = getTranscriptBlock(blocks);
const hasTranscript = !!transcriptBlock;
const { lang, service, translations } = use(ServiceContext);
const { pageIdentifier } = use(EventTrackingContext);
const { enabled: adsEnabled } = useToggle('preroll');
Expand All @@ -244,6 +249,12 @@ const MediaLoader = ({
!PAGETYPES_IGNORE_PLACEHOLDER.includes(pageType),
);

// returns transcript for lite site pages with transcript
if (isLite && hasTranscript) {
const title = getTitleForLiteSiteTranscriptBlock(blocks);
return <Transcript transcript={transcriptBlock} title={title} />;
}

if (isLite) return null;

const { model: mediaOverrides } =
Expand Down Expand Up @@ -310,6 +321,7 @@ const MediaLoader = ({
isPortrait && styles.portraitFigure(embedded),
isLandscape && styles.landscapeFigure,
],
hasTranscript && styles.withTranscriptVideo,
]}
>
{isAmp ? (
Expand Down Expand Up @@ -352,10 +364,18 @@ const MediaLoader = ({
css={[
isAudio && styles.captionAudio,
!isAudio && [isPortrait && styles.captionPortrait],
hasTranscript && styles.withTranscriptCaption,
]}
/>
)}
</figure>
{hasTranscript && (
<Transcript
transcript={transcriptBlock}
title={placeholderConfig?.mediaInfo?.title}
css={styles.transcript}
/>
)}
</>
);
};
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/MediaLoader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '#app/models/types/media';
import { OptimoImageBlock } from '#app/models/types/optimo';
import { Translations } from '#app/models/types/translations';
import { TranscriptBlock } from '../Transcript/types';

export type SMPEvent = {
playlist?: {
Expand Down Expand Up @@ -204,7 +205,7 @@ export type AresMediaBlock = {
id: string;
type: 'aresMedia';
model: {
blocks: [AresMediaMetadataBlock | OptimoImageBlock];
blocks: [AresMediaMetadataBlock | OptimoImageBlock | TranscriptBlock];
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TranscriptBlock is being added to AresMediaBlock['model']['blocks'], but in the Ares/Optimo data shape the transcript block is a sibling of the aresMedia block (not nested inside it). To keep types accurate, add TranscriptBlock to the MediaBlock union instead (and keep AresMediaBlock.model.blocks limited to its actual child block types).

Suggested change
blocks: [AresMediaMetadataBlock | OptimoImageBlock | TranscriptBlock];
blocks: [AresMediaMetadataBlock | OptimoImageBlock];

Copilot uses AI. Check for mistakes.
};
position: number[];
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { aresMediaBlockWithTranscript } from '../../fixture';
import getTitleForLiteSiteTranscriptBlock from '.';
import { MediaBlock } from '../../types';

describe('getTitleForLiteSiteTranscriptBlock', () => {
it('should return the title from aresMediaMetadata block', () => {
const result = getTitleForLiteSiteTranscriptBlock(
aresMediaBlockWithTranscript as MediaBlock[],
);
expect(result).toBe('Five things ants can teach us about management');
});

it('should return empty string if aresMedia block is missing', () => {
const result = getTitleForLiteSiteTranscriptBlock([]);
expect(result).toBe('');
});

it('should return empty string if aresMediaMetadata block is missing', () => {
const blockMissingAresMediaMetadata = [
{
...aresMediaBlockWithTranscript,
model: {
blocks: [
{
type: 'aresMedia',
model: { blocks: [] },
},
],
},
},
];

const result = getTitleForLiteSiteTranscriptBlock(
// @ts-expect-error - partial data
blockMissingAresMediaMetadata,
);
expect(result).toBe('');
});

it('should return empty string if title is missing', () => {
const blockMissingTitle = [
{
...aresMediaBlockWithTranscript,
model: {
blocks: [
{
type: 'aresMedia',
model: {
blocks: [
{
type: 'aresMediaMetadata',
model: {},
},
],
},
},
],
},
},
];

// @ts-expect-error - partial data
const result = getTitleForLiteSiteTranscriptBlock(blockMissingTitle);
expect(result).toBe('');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import filterForBlockType from '#app/lib/utilities/blockHandlers';
import {
AresMediaBlock,
AresMediaMetadataBlock,
MediaBlock,
} from '../../types';

export default (blocks: MediaBlock[]) => {
const { model: aresMedia }: AresMediaBlock =
filterForBlockType(blocks, 'aresMedia') ?? {};

const { model: aresMediaMetadata }: AresMediaMetadataBlock =
filterForBlockType(aresMedia?.blocks, 'aresMediaMetadata') ?? {};

const title = aresMediaMetadata?.title ?? '';

return title;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { aresMediaBlockWithTranscript, aresMediaBlocks } from '../../fixture';
import { MediaBlock } from '../../types';
import getTranscriptBlock from '.';
import TranscriptBlock from '../../../Transcript/fixture.json';

describe('getTranscriptBlock', () => {
it('Should return a valid transcript block for an AresMedia block for an article page.', () => {
const result = getTranscriptBlock(
aresMediaBlockWithTranscript as MediaBlock[],
);

expect(result).toStrictEqual(TranscriptBlock);
});

it('Should return null if no transcript block is present.', () => {
const result = getTranscriptBlock(aresMediaBlocks as MediaBlock[]);

expect(result).toStrictEqual(null);
});
});
16 changes: 16 additions & 0 deletions src/app/components/MediaLoader/utils/getTranscriptBlock/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import filterForBlockType from '#app/lib/utilities/blockHandlers';
import { MediaBlock } from '../../types';
import { TranscriptBlock } from '../../../Transcript/types';

export default function getTranscriptBlock(
blocks: MediaBlock[],
): TranscriptBlock | null {
const transcriptBlock: TranscriptBlock = filterForBlockType(
blocks,
'transcript',
);

if (!transcriptBlock) return null;

return transcriptBlock;
}
16 changes: 16 additions & 0 deletions src/app/components/Transcript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Description

The `Transcript` component renders video transcripts.

## Props

| Name | type | Description |
| ---------- | ------ | --------------------------- |
| transcript | object | contains transcript content |
| title | string | title of video |

## Example

```tsx
<Transcript transcript={transcriptBlock} title={mediaTitle} hideDisclaimer />
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README example uses a hideDisclaimer prop, but the Transcript component currently only accepts transcript and optional title. This makes the docs misleading; either remove hideDisclaimer from the example or implement/support that prop.

Suggested change
<Transcript transcript={transcriptBlock} title={mediaTitle} hideDisclaimer />
<Transcript transcript={transcriptBlock} title={mediaTitle} />

Copilot uses AI. Check for mistakes.
```
14 changes: 14 additions & 0 deletions src/app/components/Transcript/TranscriptTimestamp/index.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { css, Theme } from '@emotion/react';

const styles = {
time: ({ mq }: Theme) =>
css({
float: 'inline-start',
width: '100%',
[mq.GROUP_1_MIN_WIDTH]: {
width: 'auto',
},
}),
};

export default styles;
6 changes: 6 additions & 0 deletions src/app/components/Transcript/TranscriptTimestamp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import styles from './index.styles';

// using span not time element to prevent text splitting bug on Talkback
export default ({ timestamp }: { timestamp: string }) => {
return <span css={styles.time}>{timestamp}</span>;
};
Loading
Loading