Skip to content

Commit 8d7b087

Browse files
committed
feat: add ClipSet to schema
There are a few changes to the current ClipSet workaround in cp-content-pipeline: - dataLayout -> layoutWidth (for consistency with theother nodes) - changes to how the Body for transcripts is represented (pending further discussion with CP) This commit also updates the from-bodyxml transformerfor clipset
1 parent 1e271e9 commit 8d7b087

File tree

6 files changed

+528
-6
lines changed

6 files changed

+528
-6
lines changed

README.md

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type BodyBlock =
105105
| Tweet
106106
| Video
107107
| YoutubeVideo
108+
| ClipSet
108109
| Text
109110
```
110111
@@ -544,8 +545,6 @@ interface Video extends Node {
544545

545546
The `title` can be obtained by fetching the Video from the content API.
546547

547-
TODO: Figure out how Clips work, how they are different?
548-
549548
### `YoutubeVideo`
550549

551550
```ts
@@ -555,8 +554,72 @@ interface YoutubeVideo extends Node {
555554
}
556555
```
557556

557+
558558
**YoutubeVideo** represents a video referenced by a Youtube URL.
559559

560+
### `ClipSet`
561+
562+
```ts
563+
interface ClipSet extends Node {
564+
type: "clip-set"
565+
id: string
566+
autoplay: boolean
567+
loop: boolean
568+
muted: boolean
569+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid'
570+
external noAudio: boolean
571+
external caption: string
572+
external credits: string
573+
external description: string
574+
external displayTitle: string
575+
external systemTitle: string
576+
external source: string
577+
external contentWarning: string[]
578+
external publishedDate: string
579+
external subtitle: string
580+
external clips: Clip[]
581+
external accessibility: ClipAccessibility
582+
}
583+
```
584+
585+
```ts
586+
type Clip = {
587+
id: string
588+
format: 'standard-inline' | 'mobile'
589+
dataSource: ClipSource[]
590+
poster: string
591+
}
592+
```
593+
594+
```ts
595+
type ClipSource = {
596+
audioCodec: string
597+
binaryUrl: string
598+
duration: number
599+
mediaType: string
600+
pixelHeight: number
601+
pixelWidth: number
602+
videoCodec: string
603+
}
604+
```
605+
606+
```ts
607+
type ClipCaption = {
608+
mediaType?: string
609+
url?: string
610+
}
611+
```
612+
```ts
613+
type ClipAccessibility = {
614+
captions?: ClipCaption[]
615+
transcript?: Body
616+
}
617+
```
618+
619+
**ClipSet** represents a short piece of possibly-looping video content for an article.
620+
621+
The external fields are derived from the separately published [ClipSet](https://api.ft.com/schemas/clip-set.json) and [Clip](https://api.ft.com/schemas/clip.json) objects in the Content API.
622+
560623
### `ScrollyBlock`
561624
562625
```ts

content-tree.d.ts

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export declare namespace ContentTree {
2-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text;
2+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet | Text;
33
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
44
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
55
interface Node {
@@ -171,6 +171,49 @@ export declare namespace ContentTree {
171171
type: "youtube-video";
172172
url: string;
173173
}
174+
interface ClipSet extends Node {
175+
type: "clip-set";
176+
id: string;
177+
autoplay: boolean;
178+
loop: boolean;
179+
muted: boolean;
180+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
181+
noAudio: boolean;
182+
caption: string;
183+
credits: string;
184+
description: string;
185+
displayTitle: string;
186+
systemTitle: string;
187+
source: string;
188+
contentWarning: string[];
189+
publishedDate: string;
190+
subtitle: string;
191+
clips: Clip[];
192+
accessibility: ClipAccessibility;
193+
}
194+
type Clip = {
195+
id: string;
196+
format: 'standard-inline' | 'mobile';
197+
dataSource: ClipSource[];
198+
poster: string;
199+
};
200+
type ClipSource = {
201+
audioCodec: string;
202+
binaryUrl: string;
203+
duration: number;
204+
mediaType: string;
205+
pixelHeight: number;
206+
pixelWidth: number;
207+
videoCodec: string;
208+
};
209+
type ClipCaption = {
210+
mediaType?: string;
211+
url?: string;
212+
};
213+
type ClipAccessibility = {
214+
captions?: ClipCaption[];
215+
transcript?: Body;
216+
};
174217
interface ScrollyBlock extends Parent {
175218
type: "scrolly-block";
176219
theme: "sans" | "serif";
@@ -274,7 +317,7 @@ export declare namespace ContentTree {
274317
attributes: CustomCodeComponentAttributes;
275318
}
276319
namespace full {
277-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text;
320+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet | Text;
278321
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
279322
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
280323
interface Node {
@@ -446,6 +489,49 @@ export declare namespace ContentTree {
446489
type: "youtube-video";
447490
url: string;
448491
}
492+
interface ClipSet extends Node {
493+
type: "clip-set";
494+
id: string;
495+
autoplay: boolean;
496+
loop: boolean;
497+
muted: boolean;
498+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
499+
noAudio: boolean;
500+
caption: string;
501+
credits: string;
502+
description: string;
503+
displayTitle: string;
504+
systemTitle: string;
505+
source: string;
506+
contentWarning: string[];
507+
publishedDate: string;
508+
subtitle: string;
509+
clips: Clip[];
510+
accessibility: ClipAccessibility;
511+
}
512+
type Clip = {
513+
id: string;
514+
format: 'standard-inline' | 'mobile';
515+
dataSource: ClipSource[];
516+
poster: string;
517+
};
518+
type ClipSource = {
519+
audioCodec: string;
520+
binaryUrl: string;
521+
duration: number;
522+
mediaType: string;
523+
pixelHeight: number;
524+
pixelWidth: number;
525+
videoCodec: string;
526+
};
527+
type ClipCaption = {
528+
mediaType?: string;
529+
url?: string;
530+
};
531+
type ClipAccessibility = {
532+
captions?: ClipCaption[];
533+
transcript?: Body;
534+
};
449535
interface ScrollyBlock extends Parent {
450536
type: "scrolly-block";
451537
theme: "sans" | "serif";
@@ -550,7 +636,7 @@ export declare namespace ContentTree {
550636
}
551637
}
552638
namespace transit {
553-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text;
639+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet | Text;
554640
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
555641
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
556642
interface Node {
@@ -717,6 +803,37 @@ export declare namespace ContentTree {
717803
type: "youtube-video";
718804
url: string;
719805
}
806+
interface ClipSet extends Node {
807+
type: "clip-set";
808+
id: string;
809+
autoplay: boolean;
810+
loop: boolean;
811+
muted: boolean;
812+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
813+
}
814+
type Clip = {
815+
id: string;
816+
format: 'standard-inline' | 'mobile';
817+
dataSource: ClipSource[];
818+
poster: string;
819+
};
820+
type ClipSource = {
821+
audioCodec: string;
822+
binaryUrl: string;
823+
duration: number;
824+
mediaType: string;
825+
pixelHeight: number;
826+
pixelWidth: number;
827+
videoCodec: string;
828+
};
829+
type ClipCaption = {
830+
mediaType?: string;
831+
url?: string;
832+
};
833+
type ClipAccessibility = {
834+
captions?: ClipCaption[];
835+
transcript?: Body;
836+
};
720837
interface ScrollyBlock extends Parent {
721838
type: "scrolly-block";
722839
theme: "sans" | "serif";
@@ -811,7 +928,7 @@ export declare namespace ContentTree {
811928
}
812929
}
813930
namespace loose {
814-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text;
931+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet | Text;
815932
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
816933
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
817934
interface Node {
@@ -983,6 +1100,49 @@ export declare namespace ContentTree {
9831100
type: "youtube-video";
9841101
url: string;
9851102
}
1103+
interface ClipSet extends Node {
1104+
type: "clip-set";
1105+
id: string;
1106+
autoplay: boolean;
1107+
loop: boolean;
1108+
muted: boolean;
1109+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
1110+
noAudio?: boolean;
1111+
caption?: string;
1112+
credits?: string;
1113+
description?: string;
1114+
displayTitle?: string;
1115+
systemTitle?: string;
1116+
source?: string;
1117+
contentWarning?: string[];
1118+
publishedDate?: string;
1119+
subtitle?: string;
1120+
clips?: Clip[];
1121+
accessibility?: ClipAccessibility;
1122+
}
1123+
type Clip = {
1124+
id: string;
1125+
format: 'standard-inline' | 'mobile';
1126+
dataSource: ClipSource[];
1127+
poster: string;
1128+
};
1129+
type ClipSource = {
1130+
audioCodec: string;
1131+
binaryUrl: string;
1132+
duration: number;
1133+
mediaType: string;
1134+
pixelHeight: number;
1135+
pixelWidth: number;
1136+
videoCodec: string;
1137+
};
1138+
type ClipCaption = {
1139+
mediaType?: string;
1140+
url?: string;
1141+
};
1142+
type ClipAccessibility = {
1143+
captions?: ClipCaption[];
1144+
transcript?: Body;
1145+
};
9861146
interface ScrollyBlock extends Parent {
9871147
type: "scrolly-block";
9881148
theme: "sans" | "serif";

libraries/from-bodyxml/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ let ContentType = {
88
content: "http://www.ft.com/ontology/content/Content",
99
article: "http://www.ft.com/ontology/content/Article",
1010
customCodeComponent: "http://www.ft.com/ontology/content/CustomCodeComponent",
11+
clipSet: "http://www.ft.com/ontology/content/ClipSet",
1112
};
1213

1314
/**
@@ -32,6 +33,24 @@ function toValidLayoutWidth(layoutWidth) {
3233
return "full-width";
3334
}
3435
}
36+
37+
/**
38+
* @param {string} layoutWidth
39+
* @returns {ContentTree.ClipSet["layoutWidth"]}
40+
*/
41+
function toValidClipLayoutWidth(layoutWidth) {
42+
if (
43+
[
44+
"in-line",
45+
"full-grid",
46+
"mid-grid",
47+
].includes(layoutWidth)
48+
) {
49+
return /** @type {ContentTree.ClipSet["layoutWidth"]} */ (layoutWidth);
50+
} else {
51+
return "in-line";
52+
}
53+
}
3554
/**
3655
* @typedef {import("unist").Parent} UParent
3756
* @typedef {import("unist").Node} UNode
@@ -308,6 +327,24 @@ export let defaultTransformers = {
308327
children: null,
309328
};
310329
},
330+
/**
331+
* @type {Transformer<ContentTree.transit.ClipSet>}
332+
*/
333+
[ContentType.clipSet](clip) {
334+
const id = clip.attributes.url ?? "";
335+
const uuid = id.split("/").pop();
336+
return {
337+
type: "clip-set",
338+
id: uuid ?? "",
339+
layoutWidth: toValidClipLayoutWidth(
340+
clip.attributes["data-layout-width"] || ""
341+
),
342+
autoplay: clip.attributes?.autoplay === "true",
343+
loop: clip.attributes?.loop === "true",
344+
muted: clip.attributes?.muted === "true",
345+
children: null,
346+
};
347+
},
311348
/**
312349
* @type {Transformer<ContentTree.transit.Recommended>}
313350
*/

0 commit comments

Comments
 (0)