@financial-times/cp-content-pipeline-schema 2.5.1 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/generated/index.d.ts +668 -907
- package/lib/helpers/imageService.d.ts +2 -1
- package/lib/model/Byline.js +1 -1
- package/lib/model/Byline.js.map +1 -1
- package/lib/model/CapiResponse.d.ts +14 -4
- package/lib/model/CapiResponse.js +19 -4
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Clip.d.ts +3 -2
- package/lib/model/Clip.js +7 -5
- package/lib/model/Clip.js.map +1 -1
- package/lib/model/Concept.d.ts +3 -13
- package/lib/model/Concept.js +10 -10
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Image.d.ts +1 -5
- package/lib/model/Image.js.map +1 -1
- package/lib/model/Image.test.js.map +1 -1
- package/lib/model/RichText.d.ts +1 -1
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/Topper.d.ts +4 -7
- package/lib/model/Topper.js +3 -3
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/schemas/capi/base-schema.d.ts +0 -119
- package/lib/model/schemas/capi/base-schema.js +7 -7
- package/lib/model/schemas/capi/base-schema.js.map +1 -1
- package/lib/model/schemas/capi/content-package.d.ts +18 -0
- package/lib/model/schemas/capi/content-package.js +1 -0
- package/lib/model/schemas/capi/content-package.js.map +1 -1
- package/lib/resolvers/clip.d.ts +19 -1
- package/lib/resolvers/clip.js +10 -0
- package/lib/resolvers/clip.js.map +1 -1
- package/lib/resolvers/content-tree/nodePredicates.d.ts +0 -5
- package/lib/resolvers/content-tree/nodePredicates.js +1 -26
- package/lib/resolvers/content-tree/nodePredicates.js.map +1 -1
- package/lib/resolvers/content-tree/references/ClipSet.d.ts +13 -0
- package/lib/resolvers/content-tree/references/ClipSet.js +12 -1
- package/lib/resolvers/content-tree/references/ClipSet.js.map +1 -1
- package/lib/resolvers/content-tree/references/Flourish.d.ts +6 -0
- package/lib/resolvers/content-tree/references/Flourish.js +15 -1
- package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
- package/lib/resolvers/content-tree/references/ImageSet.d.ts +1 -0
- package/lib/resolvers/content-tree/references/ImageSet.js +3 -0
- package/lib/resolvers/content-tree/references/ImageSet.js.map +1 -1
- package/lib/resolvers/content-tree/references/LayoutImage.d.ts +1 -0
- package/lib/resolvers/content-tree/references/LayoutImage.js +3 -0
- package/lib/resolvers/content-tree/references/LayoutImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/RawImage.d.ts +1 -0
- package/lib/resolvers/content-tree/references/RawImage.js +3 -0
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/Recommended.d.ts +1 -0
- package/lib/resolvers/content-tree/references/Recommended.js +3 -0
- package/lib/resolvers/content-tree/references/Recommended.js.map +1 -1
- package/lib/resolvers/content-tree/references/Reference.d.ts +1 -1
- package/lib/resolvers/content-tree/references/Reference.js +1 -3
- package/lib/resolvers/content-tree/references/Reference.js.map +1 -1
- package/lib/resolvers/content-tree/references/ScrollyImage.d.ts +1 -0
- package/lib/resolvers/content-tree/references/ScrollyImage.js +3 -0
- package/lib/resolvers/content-tree/references/ScrollyImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/Tweet.d.ts +1 -0
- package/lib/resolvers/content-tree/references/Tweet.js +3 -0
- package/lib/resolvers/content-tree/references/Tweet.js.map +1 -1
- package/lib/resolvers/content-tree/references/Video.d.ts +2 -1
- package/lib/resolvers/content-tree/references/Video.js +4 -3
- package/lib/resolvers/content-tree/references/Video.js.map +1 -1
- package/lib/resolvers/content-tree/references/index.d.ts +5 -1
- package/lib/resolvers/content-tree/references/index.js +4 -0
- package/lib/resolvers/content-tree/references/index.js.map +1 -1
- package/lib/resolvers/content-tree/tagMappings.test.js +6 -9
- package/lib/resolvers/content-tree/tagMappings.test.js.map +1 -1
- package/lib/resolvers/content.d.ts +242 -33
- package/lib/resolvers/content.js +66 -31
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/image.d.ts +142 -12
- package/lib/resolvers/image.js +45 -10
- package/lib/resolvers/image.js.map +1 -1
- package/lib/resolvers/index.d.ts +597 -61
- package/lib/resolvers/picture.d.ts +41 -8
- package/lib/resolvers/picture.js +19 -21
- package/lib/resolvers/picture.js.map +1 -1
- package/lib/resolvers/richText.d.ts +14 -2
- package/lib/resolvers/richText.js +8 -6
- package/lib/resolvers/richText.js.map +1 -1
- package/lib/resolvers/scalars.d.ts +2 -1
- package/lib/resolvers/scalars.js +5 -3
- package/lib/resolvers/scalars.js.map +1 -1
- package/lib/resolvers/teaser.d.ts +5 -0
- package/lib/resolvers/teaser.js +6 -1
- package/lib/resolvers/teaser.js.map +1 -1
- package/lib/resolvers/topper.d.ts +127 -7
- package/lib/resolvers/topper.js +64 -6
- package/lib/resolvers/topper.js.map +1 -1
- package/package.json +1 -1
- package/src/datasources/instrumented.ts +0 -1
- package/src/generated/index.ts +668 -668
- package/src/helpers/imageService.ts +1 -1
- package/src/model/Byline.ts +1 -1
- package/src/model/CapiResponse.ts +32 -7
- package/src/model/Clip.ts +11 -8
- package/src/model/Concept.ts +5 -9
- package/src/model/Image.test.ts +1 -6
- package/src/model/Image.ts +1 -6
- package/src/model/RichText.ts +1 -1
- package/src/model/Topper.ts +5 -9
- package/src/model/schemas/capi/base-schema.ts +3 -3
- package/src/model/schemas/capi/content-package.ts +1 -0
- package/src/resolvers/clip.ts +13 -2
- package/src/resolvers/content-tree/nodePredicates.ts +0 -42
- package/src/resolvers/content-tree/references/ClipSet.ts +22 -2
- package/src/resolvers/content-tree/references/Flourish.ts +20 -1
- package/src/resolvers/content-tree/references/ImageSet.ts +4 -0
- package/src/resolvers/content-tree/references/LayoutImage.ts +4 -0
- package/src/resolvers/content-tree/references/RawImage.ts +4 -0
- package/src/resolvers/content-tree/references/Recommended.ts +4 -0
- package/src/resolvers/content-tree/references/Reference.ts +1 -3
- package/src/resolvers/content-tree/references/ScrollyImage.ts +4 -0
- package/src/resolvers/content-tree/references/Tweet.ts +4 -0
- package/src/resolvers/content-tree/references/Video.ts +5 -3
- package/src/resolvers/content-tree/references/index.ts +13 -4
- package/src/resolvers/content-tree/tagMappings.test.ts +6 -10
- package/src/resolvers/content.ts +88 -38
- package/src/resolvers/image.ts +75 -5
- package/src/resolvers/picture.ts +35 -23
- package/src/resolvers/richText.ts +13 -8
- package/src/resolvers/scalars.ts +3 -1
- package/src/resolvers/teaser.ts +6 -2
- package/src/resolvers/topper.ts +86 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/fixtures/clipSet.d.ts +0 -2
- package/lib/fixtures/clipSet.js +0 -70
- package/lib/fixtures/clipSet.js.map +0 -1
- package/lib/helpers/kebabCaseToPascalCase.d.ts +0 -1
- package/lib/helpers/kebabCaseToPascalCase.js +0 -7
- package/lib/helpers/kebabCaseToPascalCase.js.map +0 -1
- package/src/fixtures/clipSet.ts +0 -72
- package/src/helpers/kebabCaseToPascalCase.ts +0 -5
package/src/model/Byline.ts
CHANGED
|
@@ -17,7 +17,7 @@ export class Byline {
|
|
|
17
17
|
const split = this.#splitBylineByName(bylineWithCorrectApostrophes, [
|
|
18
18
|
...this.authorUrlMapping.keys(),
|
|
19
19
|
])
|
|
20
|
-
return
|
|
20
|
+
return this.#buildUrlTree(this.authorUrlMapping, split)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
#splitBylineByName(byline: string, names: string[]) {
|
|
@@ -28,9 +28,16 @@ import {
|
|
|
28
28
|
validLiteralUnionValue,
|
|
29
29
|
} from '../resolvers/literal-union'
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
ContentPackageContainsArgs,
|
|
33
|
+
ContentUrlArgs,
|
|
34
|
+
Media,
|
|
35
|
+
TableOfContents,
|
|
36
|
+
} from '../generated'
|
|
32
37
|
import { Topper } from './Topper'
|
|
33
38
|
import { z } from 'zod'
|
|
39
|
+
import { Byline } from './Byline'
|
|
40
|
+
import { RichText } from './RichText'
|
|
34
41
|
|
|
35
42
|
function isPlainObject(object: unknown): object is Record<string, unknown> {
|
|
36
43
|
if (object && typeof object === 'object') {
|
|
@@ -214,7 +221,9 @@ export class CapiResponse {
|
|
|
214
221
|
packageContainer
|
|
215
222
|
)
|
|
216
223
|
}
|
|
217
|
-
|
|
224
|
+
body() {
|
|
225
|
+
return new RichText('bodyXML', this.bodyXML(), this)
|
|
226
|
+
}
|
|
218
227
|
bodyXML(): string | null {
|
|
219
228
|
if ('bodyXML' in this.capiData) {
|
|
220
229
|
return this.capiData.bodyXML
|
|
@@ -226,7 +235,20 @@ export class CapiResponse {
|
|
|
226
235
|
return this.capiData.embeds
|
|
227
236
|
return []
|
|
228
237
|
}
|
|
229
|
-
byline(
|
|
238
|
+
byline({ vanity }: Partial<ContentUrlArgs>) {
|
|
239
|
+
const bylineText = this.rawByline()
|
|
240
|
+
|
|
241
|
+
if (!bylineText) return null
|
|
242
|
+
const authorUrlMapping = this.getAuthorUrlMapping()
|
|
243
|
+
|
|
244
|
+
return new Byline(
|
|
245
|
+
bylineText,
|
|
246
|
+
Boolean(vanity),
|
|
247
|
+
authorUrlMapping,
|
|
248
|
+
this.context
|
|
249
|
+
).buildBylineTree()
|
|
250
|
+
}
|
|
251
|
+
rawByline(): string | null {
|
|
230
252
|
if ('byline' in this.capiData && this.capiData.byline) {
|
|
231
253
|
return this.capiData.byline
|
|
232
254
|
}
|
|
@@ -245,7 +267,10 @@ export class CapiResponse {
|
|
|
245
267
|
types() {
|
|
246
268
|
return this.capiData.types
|
|
247
269
|
}
|
|
248
|
-
|
|
270
|
+
url({ relative, vanity }: Partial<ContentUrlArgs>) {
|
|
271
|
+
return relative ? this.relativeUrl(vanity) : this.absoluteUrl(vanity)
|
|
272
|
+
}
|
|
273
|
+
async absoluteUrl(vanity?: boolean | null) {
|
|
249
274
|
const url =
|
|
250
275
|
this.capiData.webUrl ??
|
|
251
276
|
this.capiData.canonicalWebUrl ??
|
|
@@ -449,7 +474,7 @@ export class CapiResponse {
|
|
|
449
474
|
|
|
450
475
|
async relativeUrl(vanity?: boolean | null) {
|
|
451
476
|
const RELATIVE_URL_REGEX = /https?:\/\/www.ft.com/
|
|
452
|
-
const url = await this.
|
|
477
|
+
const url = await this.absoluteUrl(vanity)
|
|
453
478
|
return url.replace(RELATIVE_URL_REGEX, '')
|
|
454
479
|
}
|
|
455
480
|
|
|
@@ -635,9 +660,9 @@ export class CapiResponse {
|
|
|
635
660
|
return null
|
|
636
661
|
}
|
|
637
662
|
|
|
638
|
-
async tableOfContents() {
|
|
663
|
+
async tableOfContents(): Promise<TableOfContents | null> {
|
|
639
664
|
return 'tableOfContents' in this.capiData
|
|
640
|
-
? this.capiData.tableOfContents
|
|
665
|
+
? this.capiData.tableOfContents ?? null
|
|
641
666
|
: null
|
|
642
667
|
}
|
|
643
668
|
|
package/src/model/Clip.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { ClipFormat } from '../resolvers/scalars'
|
|
8
8
|
import imageServiceUrl from '../helpers/imageService'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
type ClipSource = {
|
|
11
11
|
binaryUrl: string
|
|
12
12
|
mediaType: string
|
|
13
13
|
audioCodec?: string
|
|
@@ -17,7 +17,7 @@ export type ClipSource = {
|
|
|
17
17
|
videoCodec?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
interface ClipVideo {
|
|
21
21
|
id(): string
|
|
22
22
|
type(): string
|
|
23
23
|
format(): LiteralUnionScalarValues<typeof ClipFormat>
|
|
@@ -79,11 +79,14 @@ export class Clip implements ClipVideo {
|
|
|
79
79
|
'full-grid': 1200,
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const url = this.clip.poster?.members[0].binaryUrl
|
|
83
|
-
return url
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const url = this.clip.poster?.members[0].binaryUrl
|
|
83
|
+
return url
|
|
84
|
+
? imageServiceUrl({
|
|
85
|
+
url,
|
|
86
|
+
systemCode: 'cp-content-pipeline',
|
|
87
|
+
width:
|
|
88
|
+
this.layout && mapSizes[this.layout] ? mapSizes[this.layout] : 700,
|
|
89
|
+
})
|
|
90
|
+
: ''
|
|
88
91
|
}
|
|
89
92
|
}
|
package/src/model/Concept.ts
CHANGED
|
@@ -5,21 +5,17 @@ import imageServiceUrl from '../helpers/imageService'
|
|
|
5
5
|
import isError from '../helpers/isError'
|
|
6
6
|
import conceptIds from '@financial-times/n-concept-ids'
|
|
7
7
|
import decorateHeadshotUrl, { UUID_REGEX } from '../helpers/decorateHeadshotUrl'
|
|
8
|
+
import type { TopperWithHeadshotHeadshotArgs } from '../generated'
|
|
8
9
|
|
|
9
10
|
const CAPI_ID_PREFIX = /^https?:\/\/(?:www|api)\.ft\.com\/things?\//
|
|
10
11
|
const BASE_URL = 'https://www.ft.com/stream/'
|
|
11
12
|
const FT_URL = 'https://www.ft.com'
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
type URLArguments = {
|
|
14
15
|
vanity?: boolean | null
|
|
15
16
|
relative?: boolean | null
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export type HeadshotArguments = {
|
|
19
|
-
width?: number | null
|
|
20
|
-
dpr?: number | null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
export const predicates = {
|
|
24
20
|
hasAuthor: 'http://www.ft.com/ontology/annotation/hasAuthor',
|
|
25
21
|
isClassifiedBy: 'http://www.ft.com/ontology/classification/isClassifiedBy',
|
|
@@ -30,14 +26,14 @@ export const predicates = {
|
|
|
30
26
|
about: 'http://www.ft.com/ontology/annotation/about',
|
|
31
27
|
} as const
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
const types = {
|
|
34
30
|
brand: 'http://www.ft.com/ontology/product/Brand',
|
|
35
31
|
genre: 'http://www.ft.com/ontology/Genre',
|
|
36
32
|
organisation: 'http://www.ft.com/ontology/organisation/Organisation',
|
|
37
33
|
publicCompany: 'http://www.ft.com/ontology/company/PublicCompany',
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
const packageBrands = [
|
|
41
37
|
'specialReport',
|
|
42
38
|
'ftSeries',
|
|
43
39
|
'ftGuides',
|
|
@@ -175,7 +171,7 @@ export class Concept {
|
|
|
175
171
|
return url
|
|
176
172
|
}
|
|
177
173
|
|
|
178
|
-
async headshot(args?:
|
|
174
|
+
async headshot(args?: TopperWithHeadshotHeadshotArgs) {
|
|
179
175
|
const uuid = this.apiUrl().match(UUID_REGEX)?.[0]
|
|
180
176
|
|
|
181
177
|
if (!this.isAuthor() || !uuid) return null
|
package/src/model/Image.test.ts
CHANGED
|
@@ -4,12 +4,7 @@ import type { QueryContext } from '..'
|
|
|
4
4
|
import { Image } from '../types/internal-content'
|
|
5
5
|
import { OrigamiImageDataSource } from '../datasources/origami-image'
|
|
6
6
|
import { MAX_IMAGE_WIDTH } from '../helpers/imageService'
|
|
7
|
-
|
|
8
|
-
type ImageSource = {
|
|
9
|
-
url: string
|
|
10
|
-
width?: number
|
|
11
|
-
dpr?: number
|
|
12
|
-
}
|
|
7
|
+
import type { ImageSource } from '../generated'
|
|
13
8
|
|
|
14
9
|
describe('Image', () => {
|
|
15
10
|
const mockImage = {
|
package/src/model/Image.ts
CHANGED
|
@@ -13,12 +13,7 @@ import {
|
|
|
13
13
|
import { ImageFormat, ImageType } from '../resolvers/scalars'
|
|
14
14
|
import isError from '../helpers/isError'
|
|
15
15
|
import { BaseError } from '@dotcom-reliability-kit/errors'
|
|
16
|
-
|
|
17
|
-
export type ImageSource = {
|
|
18
|
-
url: string
|
|
19
|
-
width?: number
|
|
20
|
-
dpr?: number
|
|
21
|
-
}
|
|
16
|
+
import type { ImageSource } from '../generated'
|
|
22
17
|
|
|
23
18
|
export type ImageSourceArgs = {
|
|
24
19
|
width?: number | null
|
package/src/model/RichText.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { QueryContext } from '..'
|
|
|
14
14
|
|
|
15
15
|
export class RichText {
|
|
16
16
|
constructor(
|
|
17
|
-
|
|
17
|
+
public source: LiteralUnionScalarValues<typeof RichTextSource>,
|
|
18
18
|
private value: string | null,
|
|
19
19
|
private contentApiData?: CapiResponse
|
|
20
20
|
) {}
|
package/src/model/Topper.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { TopperBackgroundColour } from '../resolvers/scalars'
|
|
9
9
|
import { RichText } from './RichText'
|
|
10
10
|
import imageServiceUrl from '../helpers/imageService'
|
|
11
|
+
import type { TopperWithHeadshotHeadshotArgs } from '../generated'
|
|
11
12
|
|
|
12
13
|
type TopperType =
|
|
13
14
|
| 'DeepPortraitTopper'
|
|
@@ -19,11 +20,6 @@ type TopperType =
|
|
|
19
20
|
| 'BrandedTopper'
|
|
20
21
|
| 'BasicTopper'
|
|
21
22
|
|
|
22
|
-
export type HeadshotArguments = {
|
|
23
|
-
width?: number | null
|
|
24
|
-
dpr?: number | null
|
|
25
|
-
}
|
|
26
|
-
|
|
27
23
|
type TopperBackgroundColourValues = LiteralUnionScalarValues<
|
|
28
24
|
typeof TopperBackgroundColour
|
|
29
25
|
>
|
|
@@ -207,11 +203,11 @@ export class Topper {
|
|
|
207
203
|
}
|
|
208
204
|
|
|
209
205
|
backgroundBox() {
|
|
210
|
-
return this.capiResponse.topper()?.backgroundBox
|
|
206
|
+
return this.capiResponse.topper()?.backgroundBox ?? null
|
|
211
207
|
}
|
|
212
208
|
|
|
213
209
|
textShadow() {
|
|
214
|
-
return this.capiResponse.topper()?.textShadow
|
|
210
|
+
return this.capiResponse.topper()?.textShadow ?? null
|
|
215
211
|
}
|
|
216
212
|
|
|
217
213
|
displayConcept() {
|
|
@@ -303,7 +299,7 @@ export class Topper {
|
|
|
303
299
|
return this.capiResponse.design()
|
|
304
300
|
}
|
|
305
301
|
|
|
306
|
-
async headshot(args:
|
|
302
|
+
async headshot(args: TopperWithHeadshotHeadshotArgs) {
|
|
307
303
|
let headshotUrl: string | undefined
|
|
308
304
|
|
|
309
305
|
if (this.capiResponse.isPodcast()) {
|
|
@@ -313,7 +309,7 @@ export class Topper {
|
|
|
313
309
|
if (this.capiResponse.isOpinion()) {
|
|
314
310
|
const authors = this.capiResponse.getAuthors()
|
|
315
311
|
const headshotUrls: (string | null)[] = await Promise.all(
|
|
316
|
-
authors.map(async (author) =>
|
|
312
|
+
authors.map(async (author) => author.headshot(args))
|
|
317
313
|
).then((res) => res.filter(Boolean))
|
|
318
314
|
|
|
319
315
|
headshotUrl = headshotUrls[0] ?? undefined
|
|
@@ -14,7 +14,7 @@ export const Annotation = Concept.extend({
|
|
|
14
14
|
predicate: z.string(),
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const Topper = z.object({
|
|
18
18
|
headline: z.string().optional(),
|
|
19
19
|
standfirst: z.string().optional(),
|
|
20
20
|
backgroundColour: z.string(),
|
|
@@ -84,7 +84,7 @@ export const LeadImage = z.object({
|
|
|
84
84
|
image: Image,
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
const AlternativeImage = z.object({
|
|
88
88
|
promotionalImage: BaseImage,
|
|
89
89
|
})
|
|
90
90
|
|
|
@@ -136,7 +136,7 @@ export const ClipSet = z.object({
|
|
|
136
136
|
.optional(),
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
const DataSource = z.object({
|
|
140
140
|
binaryUrl: z.string(),
|
|
141
141
|
duration: z.number(),
|
|
142
142
|
filesize: z.number(),
|
package/src/resolvers/clip.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClipResolvers } from '../generated'
|
|
1
|
+
import { ClipResolvers, ClipSourceResolvers } from '../generated'
|
|
2
2
|
|
|
3
3
|
const resolvers = {
|
|
4
4
|
Clip: {
|
|
@@ -7,7 +7,18 @@ const resolvers = {
|
|
|
7
7
|
type: (clip) => clip.type(),
|
|
8
8
|
format: (clip) => clip.format(),
|
|
9
9
|
poster: (clip) => clip.poster(),
|
|
10
|
+
id: (clip) => clip.id(),
|
|
10
11
|
},
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
ClipSource: {
|
|
14
|
+
audioCodec: (parent) => parent.audioCodec ?? null,
|
|
15
|
+
binaryUrl: (parent) => parent.binaryUrl,
|
|
16
|
+
duration: (parent) => parent.duration ?? null,
|
|
17
|
+
mediaType: (parent) => parent.mediaType,
|
|
18
|
+
pixelWidth: (parent) => parent.pixelWidth ?? null,
|
|
19
|
+
pixelHeight: (parent) => parent.pixelHeight ?? null,
|
|
20
|
+
videoCodec: (parent) => parent.videoCodec ?? null,
|
|
21
|
+
},
|
|
22
|
+
} satisfies { Clip: ClipResolvers; ClipSource: ClipSourceResolvers }
|
|
12
23
|
|
|
13
24
|
export default resolvers
|
|
@@ -6,10 +6,6 @@ type ValuesOfTuple<Tuple extends readonly string[]> = Tuple[number]
|
|
|
6
6
|
|
|
7
7
|
type NodeOfType<Type extends AnyNode['type']> = Extract<AnyNode, { type: Type }>
|
|
8
8
|
|
|
9
|
-
type NodesOfTypes<Types extends readonly AnyNode['type'][]> = {
|
|
10
|
-
-readonly [key in keyof Types]: NodeOfType<Types[key]>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
export const phrasingTypes = [
|
|
14
10
|
'text',
|
|
15
11
|
'break',
|
|
@@ -91,41 +87,3 @@ export const childrenOfTypes = <Types extends readonly AnyNode['type'][]>(
|
|
|
91
87
|
|
|
92
88
|
return nodes as NodeOfType<ValuesOfTuple<Types>>[]
|
|
93
89
|
}
|
|
94
|
-
|
|
95
|
-
export const nodesAreOrderedTypes = <Types extends readonly AnyNode['type'][]>(
|
|
96
|
-
types: Types,
|
|
97
|
-
nodes: readonly AnyNode[]
|
|
98
|
-
): nodes is NodesOfTypes<Types> => {
|
|
99
|
-
for (const [index, node] of nodes.entries()) {
|
|
100
|
-
if (node.type !== types[index]) {
|
|
101
|
-
return false
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return true
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const childrenOfOrderedTypes = <
|
|
109
|
-
Types extends readonly AnyNode['type'][]
|
|
110
|
-
>(
|
|
111
|
-
types: Types,
|
|
112
|
-
nodes: AnyNode[],
|
|
113
|
-
parentType: AnyNode['type'],
|
|
114
|
-
context?: QueryContext
|
|
115
|
-
): NodesOfTypes<Types> => {
|
|
116
|
-
if (nodesAreOrderedTypes(types, nodes)) {
|
|
117
|
-
return nodes
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
context?.logger.error({
|
|
121
|
-
event: 'RECOVERABLE_ERROR',
|
|
122
|
-
error: new OperationalError({
|
|
123
|
-
code: 'BODY_XML_UNEXPECTED_STRUCTURE',
|
|
124
|
-
message: `Unexpected children types for ${parentType}`,
|
|
125
|
-
expected: types,
|
|
126
|
-
actual: nodes.map((node) => node.type),
|
|
127
|
-
}),
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
return nodes as unknown as NodesOfTypes<Types>
|
|
131
|
-
}
|
|
@@ -6,7 +6,11 @@ import type {
|
|
|
6
6
|
import { uuidFromUrl } from '../../../helpers/metadata'
|
|
7
7
|
import { Clip } from '../../../model/Clip'
|
|
8
8
|
import { RichText } from '../../../model/RichText'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AccessibilityResolvers,
|
|
11
|
+
CaptionResolvers,
|
|
12
|
+
ClipSetResolvers,
|
|
13
|
+
} from '../../../generated'
|
|
10
14
|
import { ReferenceWithCAPIData } from '.'
|
|
11
15
|
|
|
12
16
|
function getClipSet(
|
|
@@ -73,7 +77,23 @@ export const ClipSet = {
|
|
|
73
77
|
const clipSet = getClipSet(parent)
|
|
74
78
|
|
|
75
79
|
return clipSet && clipSet.members && clipSet.members.length > 0
|
|
76
|
-
? clipSet.members.map(
|
|
80
|
+
? clipSet.members.map(
|
|
81
|
+
(clip) => new Clip(clip, parent.reference.dataLayout)
|
|
82
|
+
)
|
|
77
83
|
: null
|
|
78
84
|
},
|
|
85
|
+
|
|
86
|
+
type() {
|
|
87
|
+
return 'clip-set'
|
|
88
|
+
},
|
|
79
89
|
} satisfies ClipSetResolvers
|
|
90
|
+
|
|
91
|
+
export const Accessibility = {
|
|
92
|
+
captions: (parent) => parent.captions ?? null,
|
|
93
|
+
transcript: (parent) => parent.transcript,
|
|
94
|
+
} satisfies AccessibilityResolvers
|
|
95
|
+
|
|
96
|
+
export const Caption = {
|
|
97
|
+
mediaType: (parent) => parent.mediaType ?? null,
|
|
98
|
+
url: (parent) => parent.url ?? null,
|
|
99
|
+
} satisfies CaptionResolvers
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import imageServiceUrl from '../../../helpers/imageService'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
FlourishFallbackResolvers,
|
|
4
|
+
FlourishResolvers,
|
|
5
|
+
} from '../../../generated'
|
|
3
6
|
import { QueryContext } from '../../..'
|
|
4
7
|
import isError from '../../../helpers/isError'
|
|
5
8
|
|
|
@@ -35,8 +38,24 @@ export const Flourish = {
|
|
|
35
38
|
height,
|
|
36
39
|
}
|
|
37
40
|
},
|
|
41
|
+
|
|
42
|
+
type(parent) {
|
|
43
|
+
return parent.reference.type
|
|
44
|
+
},
|
|
38
45
|
} satisfies FlourishResolvers
|
|
39
46
|
|
|
47
|
+
export const FlourishFallback = {
|
|
48
|
+
async height(parent) {
|
|
49
|
+
return parent.height ?? null
|
|
50
|
+
},
|
|
51
|
+
async width(parent) {
|
|
52
|
+
return parent.width ?? null
|
|
53
|
+
},
|
|
54
|
+
async url(parent) {
|
|
55
|
+
return parent.url ?? null
|
|
56
|
+
},
|
|
57
|
+
} satisfies FlourishFallbackResolvers
|
|
58
|
+
|
|
40
59
|
type ImageMetadata = {
|
|
41
60
|
width: number
|
|
42
61
|
height: number
|
|
@@ -3,9 +3,7 @@ import { VideoReferenceResolvers } from '../../../generated'
|
|
|
3
3
|
|
|
4
4
|
export const Video = {
|
|
5
5
|
// This will override the original id from the parent that is a URL.
|
|
6
|
-
id(parent)
|
|
7
|
-
return uuidFromUrl(parent.reference.id)
|
|
8
|
-
},
|
|
6
|
+
id: (parent) => uuidFromUrl(parent.reference.id),
|
|
9
7
|
|
|
10
8
|
async title(parent, _args, context) {
|
|
11
9
|
const capiResponse = await context.dataSources.capi.getContent(
|
|
@@ -13,4 +11,8 @@ export const Video = {
|
|
|
13
11
|
)
|
|
14
12
|
return capiResponse.title()
|
|
15
13
|
},
|
|
14
|
+
|
|
15
|
+
type(parent) {
|
|
16
|
+
return parent.reference.type
|
|
17
|
+
},
|
|
16
18
|
} satisfies VideoReferenceResolvers
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { IResolvers } from '@graphql-tools/utils'
|
|
2
|
-
|
|
3
1
|
import type { ContentTree } from '@financial-times/content-tree'
|
|
4
2
|
import type { CapiResponse } from '../../../model/CapiResponse'
|
|
5
3
|
|
|
@@ -7,9 +5,9 @@ import { Reference } from './Reference'
|
|
|
7
5
|
|
|
8
6
|
import { Tweet } from './Tweet'
|
|
9
7
|
import { ImageSet } from './ImageSet'
|
|
10
|
-
import { ClipSet } from './ClipSet'
|
|
8
|
+
import { ClipSet, Accessibility, Caption } from './ClipSet'
|
|
11
9
|
import { Video } from './Video'
|
|
12
|
-
import { Flourish } from './Flourish'
|
|
10
|
+
import { Flourish, FlourishFallback } from './Flourish'
|
|
13
11
|
import { Recommended } from './Recommended'
|
|
14
12
|
import { LayoutImage } from './LayoutImage'
|
|
15
13
|
import { RawImage } from './RawImage'
|
|
@@ -25,6 +23,9 @@ import {
|
|
|
25
23
|
ScrollyImageResolvers,
|
|
26
24
|
TweetResolvers,
|
|
27
25
|
VideoReferenceResolvers,
|
|
26
|
+
CaptionResolvers,
|
|
27
|
+
AccessibilityResolvers,
|
|
28
|
+
FlourishFallbackResolvers,
|
|
28
29
|
} from '../../../generated'
|
|
29
30
|
import { AnyNode } from '../Workarounds'
|
|
30
31
|
|
|
@@ -44,6 +45,10 @@ export const resolvers: {
|
|
|
44
45
|
LayoutImage: LayoutImageResolvers
|
|
45
46
|
RawImage: RawImageResolvers
|
|
46
47
|
ScrollyImage: ScrollyImageResolvers
|
|
48
|
+
MainImage: ImageSetResolvers
|
|
49
|
+
Caption: CaptionResolvers
|
|
50
|
+
Accessibility: AccessibilityResolvers
|
|
51
|
+
FlourishFallback: FlourishFallbackResolvers
|
|
47
52
|
} = {
|
|
48
53
|
Reference,
|
|
49
54
|
Tweet,
|
|
@@ -55,6 +60,10 @@ export const resolvers: {
|
|
|
55
60
|
LayoutImage,
|
|
56
61
|
RawImage,
|
|
57
62
|
ScrollyImage,
|
|
63
|
+
MainImage: ImageSet,
|
|
64
|
+
Caption,
|
|
65
|
+
Accessibility,
|
|
66
|
+
FlourishFallback,
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export const mapNodeToReference = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyNode, OldClip } from './Workarounds'
|
|
2
2
|
import tagMappings, { getBooleanAttributeValue } from './tagMappings'
|
|
3
|
-
import
|
|
3
|
+
import { load } from 'cheerio'
|
|
4
4
|
|
|
5
5
|
function expectNotArray<T>(thing: T | T[]): asserts thing is T {
|
|
6
6
|
expect(thing).not.toBeInstanceOf(Array)
|
|
@@ -16,17 +16,13 @@ function expectNodeType<T extends AnyNode>(
|
|
|
16
16
|
|
|
17
17
|
describe('tagMappings test', () => {
|
|
18
18
|
it('getBooleanAttributeValue attrubute', () => {
|
|
19
|
-
let $el =
|
|
19
|
+
let $el = load('<ft-content autoplay></ft-content>')('ft-content')
|
|
20
20
|
expect(getBooleanAttributeValue($el, 'autoplay')).toBe(true)
|
|
21
|
-
$el =
|
|
22
|
-
'ft-content'
|
|
23
|
-
)
|
|
21
|
+
$el = load('<ft-content autoplay="true"></ft-content>')('ft-content')
|
|
24
22
|
expect(getBooleanAttributeValue($el, 'autoplay')).toBe(true)
|
|
25
|
-
$el =
|
|
26
|
-
'ft-content'
|
|
27
|
-
)
|
|
23
|
+
$el = load('<ft-content autoplay="false"></ft-content>')('ft-content')
|
|
28
24
|
expect(getBooleanAttributeValue($el, 'autoplay')).toBe(false)
|
|
29
|
-
$el =
|
|
25
|
+
$el = load('<ft-content></ft-content>')('ft-content')
|
|
30
26
|
expect(getBooleanAttributeValue($el, 'autoplay')).toBe(false)
|
|
31
27
|
})
|
|
32
28
|
|
|
@@ -43,7 +39,7 @@ describe('tagMappings test', () => {
|
|
|
43
39
|
<body>`
|
|
44
40
|
const selector =
|
|
45
41
|
'ft-content[type="http://www.ft.com/ontology/content/clip"]'
|
|
46
|
-
const $el =
|
|
42
|
+
const $el = load(bodyXML)(selector)
|
|
47
43
|
const mapping = tagMappings[selector]($el, () => [])
|
|
48
44
|
|
|
49
45
|
expectNodeType<OldClip>(mapping, 'clip')
|