@financial-times/cp-content-pipeline-schema 1.8.4 → 1.8.6

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.
@@ -0,0 +1,606 @@
1
+ fragment Concept on Concept {
2
+ id
3
+ prefLabel
4
+ types
5
+ type
6
+ directType
7
+ isPackageBrand
8
+ predicate
9
+ url(vanity: $useVanities)
10
+ relativeUrl: url(relative: true, vanity: $useVanities)
11
+ }
12
+
13
+ fragment StructuredTree on StructuredContent {
14
+ tree
15
+ }
16
+
17
+ fragment MetaLink on MetaLink {
18
+ ... on Concept {
19
+ id
20
+ url
21
+ relativeUrl: url(relative: true)
22
+ prefLabel
23
+ }
24
+ ... on ContentPackage {
25
+ id
26
+ url
27
+ relativeUrl: url(relative: true)
28
+ prefLabel: title
29
+ }
30
+ }
31
+
32
+ fragment Teaser on Teaser {
33
+ title
34
+ id
35
+ url
36
+ relativeUrl: url(relative: true)
37
+ type
38
+ publishedDate
39
+ firstPublishedDate
40
+ metaPrefixText
41
+ metaSuffixText
42
+ metaLink {
43
+ ...MetaLink
44
+ }
45
+ metaAltLink {
46
+ ...MetaLink
47
+ }
48
+ image {
49
+ url
50
+ width
51
+ height
52
+ altText
53
+ }
54
+ indicators {
55
+ accessLevel
56
+ isOpinion
57
+ isColumn
58
+ isPodcast
59
+ isEditorsChoice
60
+ isExclusive
61
+ isScoop
62
+ }
63
+ }
64
+
65
+ fragment ImageSource on ImageSource {
66
+ dpr
67
+ width
68
+ url
69
+ }
70
+
71
+ fragment Intro on RichText {
72
+ source
73
+ structured {
74
+ ...StructuredTree
75
+ }
76
+ }
77
+
78
+ fragment Topper on Topper {
79
+ __typename
80
+ headline
81
+ backgroundColour
82
+ backgroundBox
83
+ textShadow
84
+ followButtonVariant
85
+
86
+ intro {
87
+ ...Intro
88
+ }
89
+
90
+ displayConcept {
91
+ ...Concept
92
+ }
93
+
94
+ genreConcept {
95
+ ...Concept
96
+ }
97
+
98
+ ... on TopperWithBrand {
99
+ brandConcept {
100
+ ...Concept
101
+ }
102
+ }
103
+
104
+ ... on TopperWithImages {
105
+ fallbackImage {
106
+ sourceSet(width: 1440) {
107
+ ...ImageSource
108
+ }
109
+ ...Image
110
+ }
111
+
112
+ images {
113
+ ...Image
114
+ }
115
+ }
116
+
117
+ ... on SplitTextTopper {
118
+ images {
119
+ ... on ImageSquare {
120
+ sourceSet(width: 490) {
121
+ ...ImageSource
122
+ }
123
+ }
124
+ ... on ImageStandard {
125
+ sourceSet(width: 800) {
126
+ ...ImageSource
127
+ }
128
+ }
129
+ ... on ImageWide {
130
+ normal: sourceSet(width: 1220) {
131
+ ...ImageSource
132
+ }
133
+ widest: sourceSet(width: 1440) {
134
+ ...ImageSource
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ... on FullBleedTopper {
140
+ images {
141
+ ... on ImageSquare {
142
+ sourceSet(width: 490) {
143
+ ...ImageSource
144
+ }
145
+ }
146
+ ... on ImageStandard {
147
+ sourceSet(width: 800) {
148
+ ...ImageSource
149
+ }
150
+ }
151
+ ... on ImageWide {
152
+ normal: sourceSet(width: 1220) {
153
+ ...ImageSource
154
+ }
155
+ wide: sourceSet(width: 1440) {
156
+ ...ImageSource
157
+ }
158
+ wideL: sourceSet(width: 1920) {
159
+ ...ImageSource
160
+ }
161
+ wideXL: sourceSet(width: 3840) {
162
+ ...ImageSource
163
+ }
164
+ }
165
+ }
166
+ }
167
+ ... on OpinionTopper {
168
+ headshot(dpr: 2, width: 150)
169
+ columnist {
170
+ ...Concept
171
+ }
172
+ }
173
+ ... on PodcastTopper {
174
+ headshot(dpr: 2, width: 150)
175
+ }
176
+ ... on TopperWithTheme {
177
+ isLargeHeadline
178
+ layout
179
+ }
180
+ ... on TopperWithPackage {
181
+ design
182
+ }
183
+ ... on DeepPortraitTopper {
184
+ images {
185
+ ... on ImagePortrait {
186
+ small: sourceSet(width: 490) {
187
+ ...ImageSource
188
+ }
189
+ normal: sourceSet(width: 740) {
190
+ ...ImageSource
191
+ }
192
+ wide: sourceSet(width: 1067) {
193
+ ...ImageSource
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ... on DeepLandscapeTopper {
199
+ images {
200
+ ... on ImageLandscape {
201
+ normal: sourceSet(width: 1440) {
202
+ ...ImageSource
203
+ }
204
+ wide: sourceSet(width: 1920) {
205
+ ...ImageSource
206
+ }
207
+ }
208
+ ... on ImagePortrait {
209
+ small: sourceSet(width: 490) {
210
+ ...ImageSource
211
+ }
212
+ tablets: sourceSet(width: 980) {
213
+ ...ImageSource
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ fragment Image on Image {
221
+ __typename
222
+ height
223
+ width
224
+ caption
225
+ credit
226
+ format
227
+ url
228
+ id
229
+ }
230
+
231
+ fragment PictureFields on Picture {
232
+ __typename
233
+ alt
234
+ caption
235
+ credit
236
+ imageType
237
+ layoutWidth
238
+
239
+ fallbackImage {
240
+ ...Image
241
+ }
242
+ }
243
+
244
+ fragment Picture on Picture {
245
+ ...PictureFields
246
+
247
+ images {
248
+ ...Image
249
+ }
250
+
251
+ ... on PictureStandard {
252
+ images {
253
+ ... on ImageStandard {
254
+ sourceSet(width: 700) {
255
+ ...ImageSource
256
+ }
257
+ }
258
+ ... on ImageStandardInline {
259
+ sourceSet(width: 700) {
260
+ ...ImageSource
261
+ }
262
+ }
263
+ ... on ImageMobile {
264
+ sourceSet(width: 490) {
265
+ ...ImageSource
266
+ }
267
+ }
268
+ }
269
+ }
270
+ ... on PictureInline {
271
+ images {
272
+ ... on ImageStandardInline {
273
+ sourceSet(width: 350) {
274
+ ...ImageSource
275
+ }
276
+ }
277
+ }
278
+ }
279
+ ... on PictureFullBleed {
280
+ images {
281
+ ... on ImageMobile {
282
+ sourceSet(width: 490) {
283
+ ...ImageSource
284
+ }
285
+ }
286
+ ... on ImageDesktop {
287
+ sourceSet(width: 1200) {
288
+ ...ImageSource
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ fragment MainImage on MainImage {
296
+ picture {
297
+ ...Picture
298
+ }
299
+ }
300
+
301
+ fragment ImageSet on ImageSet {
302
+ picture {
303
+ ...Picture
304
+ }
305
+ }
306
+
307
+ fragment Clip on Clip {
308
+ __typename
309
+ format
310
+ dataSource {
311
+ audioCodec
312
+ binaryUrl
313
+ duration
314
+ mediaType
315
+ pixelHeight
316
+ pixelWidth
317
+ videoCodec
318
+ }
319
+ poster
320
+ }
321
+
322
+ fragment ClipSet on ClipSet {
323
+ type
324
+ autoplay
325
+ loop
326
+ muted
327
+ dataLayout
328
+ id
329
+ noAudio
330
+ caption
331
+ credits
332
+ description
333
+ displayTitle
334
+ contentWarning
335
+ source
336
+ subtitle
337
+ publishedDate
338
+ clips {
339
+ ...Clip
340
+ }
341
+ accessibility {
342
+ captions {
343
+ url
344
+ mediaType
345
+ }
346
+ transcript
347
+ }
348
+ }
349
+
350
+ fragment LayoutImage on LayoutImage {
351
+ picture {
352
+ ...Picture
353
+ }
354
+ }
355
+
356
+ fragment Recommended on Recommended {
357
+ teaser {
358
+ ...Teaser
359
+ }
360
+ }
361
+
362
+ fragment Tweet on Tweet {
363
+ html
364
+ }
365
+
366
+ fragment Video on VideoReference {
367
+ title
368
+ id
369
+ }
370
+
371
+ fragment Flourish on Flourish {
372
+ fallbackImage {
373
+ url
374
+ height
375
+ width
376
+ }
377
+ }
378
+
379
+ fragment RawImage on RawImage {
380
+ image {
381
+ ...Image
382
+ }
383
+ }
384
+
385
+ fragment ScrollyImage on ScrollyImage {
386
+ picture {
387
+ ...PictureFields
388
+
389
+ images {
390
+ ...Image
391
+
392
+ ... on ImageDesktop {
393
+ s: sourceSet(width: 740) {
394
+ ...ImageSource
395
+ }
396
+ m: sourceSet(width: 980) {
397
+ ...ImageSource
398
+ }
399
+ l: sourceSet(width: 1220) {
400
+ ...ImageSource
401
+ }
402
+ xl: sourceSet(width: 1920) {
403
+ ...ImageSource
404
+ }
405
+ xxl: sourceSet(width: 3840) {
406
+ ...ImageSource
407
+ }
408
+ }
409
+
410
+ ... on ImageMobile {
411
+ s: sourceSet(width: 490) {
412
+ ...ImageSource
413
+ }
414
+ m: sourceSet(width: 600) {
415
+ ...ImageSource
416
+ }
417
+ l: sourceSet(width: 800) {
418
+ ...ImageSource
419
+ }
420
+ xl: sourceSet(width: 1200) {
421
+ ...ImageSource
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ fragment Design on Design {
429
+ theme
430
+ layout
431
+ }
432
+
433
+ fragment Media on Media {
434
+ url
435
+ mediaType
436
+ }
437
+
438
+ fragment PinnedPost on Content {
439
+ __typename
440
+ id
441
+ title
442
+ publishedDate
443
+ body {
444
+ structured {
445
+ ...StructuredContent
446
+ }
447
+ }
448
+ publishedDate
449
+ mainImage {
450
+ ...Image
451
+ }
452
+ altTitle {
453
+ promotionalTitle
454
+ }
455
+ byline(vanity: $useVanities) {
456
+ tree
457
+ }
458
+ accessLevel
459
+ canBeSyndicated
460
+ commentsEnabled
461
+ }
462
+
463
+ fragment ArticleReferences on Reference {
464
+ type
465
+ ...Recommended
466
+ ...ImageSet
467
+ ...ClipSet
468
+ ...LayoutImage
469
+ ...Tweet
470
+ ...Video
471
+ ...Flourish
472
+ ...RawImage
473
+ ...MainImage
474
+ ...ScrollyImage
475
+ }
476
+
477
+ fragment StructuredContent on StructuredContent {
478
+ ...StructuredTree
479
+
480
+ references {
481
+ ...ArticleReferences
482
+ }
483
+ text
484
+ }
485
+
486
+ fragment Content on Content {
487
+ __typename
488
+ id
489
+ title
490
+ publishedDate
491
+ firstPublishedDate
492
+ standfirst
493
+ topper {
494
+ ...Topper
495
+ }
496
+ body {
497
+ structured {
498
+ ...StructuredContent
499
+ }
500
+ }
501
+ publishedDate
502
+ mainImage {
503
+ ...Image
504
+ }
505
+ altTitle {
506
+ promotionalTitle
507
+ }
508
+ byline(vanity: $useVanities) {
509
+ tree
510
+ }
511
+ annotations {
512
+ ...Concept
513
+ }
514
+ accessLevel
515
+ canBeSyndicated
516
+ instantAlertConcept {
517
+ id
518
+ prefLabel
519
+ }
520
+ originatingParty
521
+ commentsEnabled
522
+ editorialDesk
523
+ design {
524
+ ...Design
525
+ }
526
+ }
527
+
528
+ fragment PackageContainer on Content {
529
+ ...Content
530
+ url(vanity: $useVanities)
531
+ topper {
532
+ ... on TopperWithBrand {
533
+ genreConcept {
534
+ ...Concept
535
+ }
536
+ }
537
+ }
538
+ ... on ContentPackage {
539
+ containsLength
540
+ contains(fromId: $uuid, surroundingArticles: 7) {
541
+ ...Teaser
542
+ standfirst
543
+ }
544
+ tableOfContents {
545
+ labelType
546
+ sequence
547
+ }
548
+ }
549
+ }
550
+
551
+ fragment ArticleFields on Content {
552
+ ...Content
553
+ url
554
+ ... on Article {
555
+ containedIn {
556
+ ...PackageContainer
557
+ }
558
+ }
559
+ ... on Placeholder {
560
+ containedIn {
561
+ ...PackageContainer
562
+ }
563
+ }
564
+ ... on LiveBlogPost {
565
+ containedIn {
566
+ ...PackageContainer
567
+ }
568
+ }
569
+ ... on LiveBlogPackage {
570
+ liveBlogPosts(includePinned: false) {
571
+ ...Content
572
+ url
573
+ }
574
+ pinnedPost {
575
+ ...PinnedPost
576
+ }
577
+ realtime
578
+ }
579
+ ... on ContentPackage {
580
+ contains {
581
+ ...Teaser
582
+ standfirst
583
+ }
584
+ }
585
+ ... on Audio {
586
+ media {
587
+ ...Media
588
+ }
589
+ }
590
+ }
591
+
592
+ query Article($uuid: String!, $useVanities: Boolean!) {
593
+ content(uuid: $uuid) {
594
+ ...ArticleFields
595
+ }
596
+ }
597
+
598
+ query ArticleFromJSON(
599
+ $content: JSON!
600
+ $useVanities: Boolean!
601
+ $uuid: String = null
602
+ ) {
603
+ contentFromJSON(content: $content) {
604
+ ...ArticleFields
605
+ }
606
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import type { Logger } from '@dotcom-reliability-kit/logger'
4
4
  import { loadSchemaSync } from '@graphql-tools/load'
5
5
  import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'
6
6
  import * as path from 'path'
7
+ import { readFileSync } from 'fs'
7
8
 
8
9
  interface Metrics {
9
10
  count(metric: string | string[], count?: number): void
@@ -23,6 +24,11 @@ export interface QueryContext {
23
24
  }
24
25
  }
25
26
 
27
+ export const articleDocumentQuery = readFileSync(
28
+ require.resolve('../queries/article.graphql'),
29
+ 'utf-8'
30
+ )
31
+
26
32
  export const typeDefs = loadSchemaSync(
27
33
  path.join(__dirname, '../typedefs/**/*.graphql'),
28
34
  {
@@ -227,15 +227,6 @@ export class Topper {
227
227
 
228
228
  const brandConcept = this.brandConcept()
229
229
  const displayConcept = this.capiResponse.getDisplayConcept()
230
-
231
- if (this.capiResponse.isOpinion()) {
232
- return brandConcept ?? displayConcept ?? null
233
- }
234
-
235
- if (displayConcept) {
236
- return displayConcept
237
- }
238
-
239
230
  const aboutAnnotation = this.capiResponse
240
231
  .annotations()
241
232
  ?.find(
@@ -244,6 +235,14 @@ export class Topper {
244
235
  'http://www.ft.com/ontology/annotation/about'
245
236
  )
246
237
 
238
+ if (this.capiResponse.isOpinion() && brandConcept) {
239
+ return brandConcept
240
+ }
241
+
242
+ if (displayConcept) {
243
+ return displayConcept
244
+ }
245
+
247
246
  return aboutAnnotation ?? null
248
247
  }
249
248
 
@@ -83,12 +83,6 @@ export interface OldClip extends ContentTree.Node {
83
83
  credits: string
84
84
  }
85
85
 
86
- export interface CustomCodeComponent extends ContentTree.Node {
87
- type: 'custom-code-component'
88
- path: string
89
- version: string
90
- dataComponentProps: string
91
- }
92
86
  // this type is used for bodyXML transformation ONLY in tagMappings
93
87
  export interface ClipSet extends ContentTree.Node {
94
88
  type: 'clip-set'
@@ -191,7 +185,6 @@ export type AnyNode =
191
185
  | TableRow
192
186
  | TableCell
193
187
  | Video
194
- | CustomCodeComponent
195
188
  | YoutubeVideo
196
189
  | MainImage
197
190
  | MainImageRaw
@@ -153,13 +153,7 @@ const commonTagMappings: TagMappings = {
153
153
  type: 'tweet',
154
154
  id: $el.attr('href') || '',
155
155
  }),
156
- '[data-asset-type="custom-code-component"]': ($el, traverse) => ({
157
- type: 'custom-code-component',
158
- path: $el.attr('path') || '',
159
- version: $el.attr('version') || '',
160
- dataComponentProps: $el.attr('data-component-props') || '',
161
- children: traverse(),
162
- }),
156
+
163
157
  'h4,h5,h6': ($el, traverse, context) => ({
164
158
  type: 'heading',
165
159
  level: 'label',
@@ -387,7 +381,7 @@ const commonTagMappings: TagMappings = {
387
381
  tableChildren = [caption, ...tableChildren]
388
382
  }
389
383
 
390
- if (footer) {
384
+ if (footer?.children && footer.children.length > 0) {
391
385
  tableChildren = [...tableChildren, footer]
392
386
  }
393
387