@financial-times/cp-content-pipeline-schema 2.13.0 → 2.14.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/generated/index.d.ts +6 -3
  3. package/lib/model/RichText.d.ts +3 -3
  4. package/lib/model/RichText.js.map +1 -1
  5. package/lib/model/Topper.js +2 -1
  6. package/lib/model/Topper.js.map +1 -1
  7. package/lib/resolvers/content-tree/Workarounds.d.ts +10 -3
  8. package/lib/resolvers/content-tree/bodyXMLToTree.d.ts +2 -3
  9. package/lib/resolvers/content-tree/bodyXMLToTree.test.js +1 -1
  10. package/lib/resolvers/content-tree/bodyXMLToTree.test.js.map +1 -1
  11. package/lib/resolvers/content-tree/extractText.d.ts +2 -2
  12. package/lib/resolvers/content-tree/extractText.js.map +1 -1
  13. package/lib/resolvers/content-tree/nodePredicates.d.ts +2 -1
  14. package/lib/resolvers/content-tree/nodePredicates.js +19 -1
  15. package/lib/resolvers/content-tree/nodePredicates.js.map +1 -1
  16. package/lib/resolvers/content-tree/tagMappings.js +10 -6
  17. package/lib/resolvers/content-tree/tagMappings.js.map +1 -1
  18. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.d.ts +3 -3
  19. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js.map +1 -1
  20. package/lib/resolvers/index.d.ts +2 -1
  21. package/lib/resolvers/richText.d.ts +1 -1
  22. package/lib/resolvers/topper.d.ts +1 -0
  23. package/lib/resolvers/topper.js +1 -0
  24. package/lib/resolvers/topper.js.map +1 -1
  25. package/package.json +2 -2
  26. package/queries/article.graphql +1 -0
  27. package/src/generated/index.ts +6 -3
  28. package/src/model/RichText.ts +2 -2
  29. package/src/model/Topper.ts +2 -1
  30. package/src/resolvers/content-tree/Workarounds.ts +30 -4
  31. package/src/resolvers/content-tree/bodyXMLToTree.test.ts +3 -2
  32. package/src/resolvers/content-tree/bodyXMLToTree.ts +2 -2
  33. package/src/resolvers/content-tree/extractText.ts +2 -1
  34. package/src/resolvers/content-tree/nodePredicates.ts +19 -0
  35. package/src/resolvers/content-tree/tagMappings.ts +25 -7
  36. package/src/resolvers/content-tree/updateTreeWithReferenceIds.ts +3 -3
  37. package/src/resolvers/topper.ts +1 -0
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/typedefs/topper.graphql +4 -1
@@ -9,8 +9,8 @@ import {
9
9
  articleTagMappings,
10
10
  liveBlogPostTagMappings,
11
11
  } from '../resolvers/content-tree/tagMappings'
12
- import { ContentTree } from '@financial-times/content-tree'
13
12
  import { QueryContext } from '..'
13
+ import { Body } from '../resolvers/content-tree/Workarounds'
14
14
 
15
15
  export class RichText {
16
16
  constructor(
@@ -24,7 +24,7 @@ export class RichText {
24
24
  }
25
25
 
26
26
  async structured(context?: QueryContext) {
27
- const tree = await new Promise<ContentTree.Body>((resolve, reject) => {
27
+ const tree = await new Promise<Body>((resolve, reject) => {
28
28
  // bodyXMLToTree is synchronous and slow. scheduling it in a setImmediate
29
29
  // prevents it from blocking the event loop, so the app can still handle
30
30
  // requests, and it won't skew resolver timing metrics by blocking the
@@ -138,7 +138,8 @@ export class Topper {
138
138
  type === 'SplitTextTopper' ||
139
139
  type === 'FullBleedTopper' ||
140
140
  type === 'DeepPortraitTopper' ||
141
- type === 'DeepLandscapeTopper'
141
+ type === 'DeepLandscapeTopper' ||
142
+ type === 'TopperWithFlourish'
142
143
  )
143
144
  }
144
145
 
@@ -1,4 +1,5 @@
1
- import { ContentTree } from '@financial-times/content-tree'
1
+ import { ContentTree as ContentTreeNamespace } from '@financial-times/content-tree'
2
+ import ContentTree = ContentTreeNamespace.transit
2
3
 
3
4
  /* ====CONTENT TREE WORKAROUNDS=====
4
5
  This file contains type definitions for any node that are either specific to Customer Products rendering,
@@ -118,7 +119,7 @@ export interface Table extends ContentTree.Parent {
118
119
  | 'inset-left'
119
120
  | 'inset-right'
120
121
  | 'full-bleed'
121
- collapseAfterHowManyRows: number | null
122
+ collapseAfterHowManyRows: number | undefined
122
123
  responsiveStyle: 'overflow' | 'flat' | 'scroll'
123
124
  children: TableChildren
124
125
  columnSettings: TableColumnSettings[]
@@ -160,9 +161,31 @@ export interface Recommended extends ContentTree.Recommended {
160
161
  isInLiveBlog?: boolean
161
162
  }
162
163
 
164
+ export interface Body extends ContentTree.Parent {
165
+ type: 'body'
166
+ version: number
167
+ children: BodyBlock[]
168
+ }
169
+
170
+ export type BodyBlock =
171
+ | ContentTree.Paragraph
172
+ | ContentTree.Heading
173
+ | ContentTree.ImageSet
174
+ | ContentTree.BigNumber
175
+ | ContentTree.CustomCodeComponent
176
+ | ContentTree.Layout
177
+ | ContentTree.List
178
+ | ContentTree.Blockquote
179
+ | ContentTree.Pullquote
180
+ | ContentTree.ScrollyBlock
181
+ | ContentTree.ThematicBreak
182
+ | Table
183
+ | Recommended
184
+ | ContentTree.Tweet
185
+ | Video
186
+ | YoutubeVideo
187
+
163
188
  export type AnyNode =
164
- | ContentTree.Root
165
- | ContentTree.Body
166
189
  | ContentTree.Text
167
190
  | ContentTree.Break
168
191
  | ContentTree.ThematicBreak
@@ -177,6 +200,9 @@ export type AnyNode =
177
200
  | ContentTree.Blockquote
178
201
  | ContentTree.Pullquote
179
202
  | ContentTree.ImageSet
203
+ | ContentTree.CustomCodeComponent
204
+ | ContentTree.Root
205
+ | Body
180
206
  | ClipSet
181
207
  | OldClip
182
208
  | Recommended
@@ -4,6 +4,7 @@ import tags from './tagMappings'
4
4
  import { Logger } from '@dotcom-reliability-kit/logger'
5
5
  import { QueryContext, BodyXMLToTreeError } from '../..'
6
6
  import { OperationalError } from '@dotcom-reliability-kit/errors'
7
+ import { BodyBlock } from './Workarounds'
7
8
 
8
9
  const mockLogger = new Logger()
9
10
  const mockLogWarn = jest.spyOn(mockLogger, 'warn')
@@ -52,7 +53,7 @@ describe('bodyXMLToTree', () => {
52
53
  const tags: TagMappings = {
53
54
  body: ($el, traverse) => ({
54
55
  type: 'body',
55
- children: traverse(),
56
+ children: traverse() as BodyBlock[],
56
57
  version: 1,
57
58
  }),
58
59
  'body > img': () => ({ type: 'image-set', id: 'id' }),
@@ -598,7 +599,7 @@ describe('bodyXMLToTree', () => {
598
599
  "type": "table-footer",
599
600
  },
600
601
  ],
601
- "collapseAfterHowManyRows": null,
602
+ "collapseAfterHowManyRows": undefined,
602
603
  "columnSettings": Array [
603
604
  Object {
604
605
  "hideOnMobile": false,
@@ -1,7 +1,7 @@
1
1
  import * as cheerio from 'cheerio'
2
2
 
3
3
  import type { ContentTree } from '@financial-times/content-tree'
4
- import { AnyNode } from './Workarounds'
4
+ import { AnyNode, Body } from './Workarounds'
5
5
  import { QueryContext } from '../..'
6
6
  import { isTag, isText } from 'domhandler'
7
7
  import { OperationalError } from '@dotcom-reliability-kit/errors'
@@ -26,7 +26,7 @@ export default function bodyXMLToTree(
26
26
  xml: string,
27
27
  tagMappings: TagMappings,
28
28
  context?: QueryContext
29
- ): ContentTree.Body {
29
+ ): Body {
30
30
  const $ = cheerio.load(xml)
31
31
 
32
32
  const flattenAndTraverseChildren = (children: cheerio.Node[]): AnyNode[] =>
@@ -1,4 +1,5 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
+ import { Body } from './Workarounds'
2
3
 
3
4
  const extractTextFromNode = (
4
5
  node: ContentTree.Paragraph | ContentTree.Phrasing
@@ -12,7 +13,7 @@ const extractTextFromNode = (
12
13
  }
13
14
  }
14
15
 
15
- export default function extractText(tree: ContentTree.Body): string {
16
+ export default function extractText(tree: Body): string {
16
17
  return tree.children
17
18
  .filter((node): node is ContentTree.Paragraph => node.type === 'paragraph')
18
19
  .map(extractTextFromNode)
@@ -14,6 +14,25 @@ export const phrasingTypes = [
14
14
  'link',
15
15
  ] as const
16
16
 
17
+ export const bodyBlockTypes = [
18
+ 'paragraph',
19
+ 'heading',
20
+ 'image-set',
21
+ 'big-number',
22
+ 'custom-code-component',
23
+ 'layout',
24
+ 'list',
25
+ 'blockquote',
26
+ 'pullquote',
27
+ 'scrolly-block',
28
+ 'thematic-break',
29
+ 'table',
30
+ 'recommended',
31
+ 'tweet',
32
+ 'video',
33
+ 'youtube-video',
34
+ ] as const
35
+
17
36
  export const findChildOftype = <NodeType extends AnyNode>(
18
37
  type: NodeType['type'],
19
38
  nodes: AnyNode[],
@@ -1,4 +1,5 @@
1
- import { ContentTree } from '@financial-times/content-tree'
1
+ import { ContentTree as ContentTreeNamespace } from '@financial-times/content-tree'
2
+ import ContentTree = ContentTreeNamespace.transit
2
3
  import type { TagMappings } from './bodyXMLToTree'
3
4
  import {
4
5
  AnyNode,
@@ -15,6 +16,7 @@ import {
15
16
  childrenOfTypes,
16
17
  findChildOftype,
17
18
  phrasingTypes,
19
+ bodyBlockTypes,
18
20
  } from './nodePredicates'
19
21
 
20
22
  import * as scrollytelling from '@financial-times/n-scrollytelling-image/server'
@@ -104,7 +106,11 @@ const liveBlogPostTagMappings: TagMappings = {
104
106
  }
105
107
 
106
108
  const commonTagMappings: TagMappings = {
107
- body: ($el, traverse) => ({ type: 'body', version: 1, children: traverse() }),
109
+ body: ($el, traverse) => ({
110
+ type: 'body',
111
+ version: 1,
112
+ children: childrenOfTypes(bodyBlockTypes, traverse(), 'body'),
113
+ }),
108
114
  'a:not([data-asset-type])': ($el, traverse, context) => ({
109
115
  type: 'link',
110
116
  url: $el.attr('href') || '',
@@ -125,7 +131,12 @@ const commonTagMappings: TagMappings = {
125
131
  }),
126
132
  blockquote: ($el, traverse, context) => ({
127
133
  type: 'blockquote',
128
- children: childrenOfTypes(phrasingTypes, traverse(), 'blockquote', context),
134
+ children: childrenOfTypes(
135
+ [...phrasingTypes, 'paragraph'],
136
+ traverse(),
137
+ 'blockquote',
138
+ context
139
+ ),
129
140
  }),
130
141
  // strip any (redundant) line breaks in between two paragraphs
131
142
  'p + br': ($el) => ($el.next()[0]?.tagName === 'p' ? [] : { type: 'break' }),
@@ -152,7 +163,12 @@ const commonTagMappings: TagMappings = {
152
163
  }),
153
164
  li: ($el, traverse, context) => ({
154
165
  type: 'list-item',
155
- children: childrenOfTypes(phrasingTypes, traverse(), 'list-item', context),
166
+ children: childrenOfTypes(
167
+ [...phrasingTypes, 'paragraph'],
168
+ traverse(),
169
+ 'list-item',
170
+ context
171
+ ),
156
172
  }),
157
173
  'a[data-asset-type="tweet"]': ($el) => ({
158
174
  type: 'tweet',
@@ -164,9 +180,11 @@ const commonTagMappings: TagMappings = {
164
180
  level: 'label',
165
181
  children: everyChildIsType('text', traverse(), 'heading', context),
166
182
  }),
167
- 'pull-quote': ($el, traverse, context) => {
183
+ 'pull-quote': ($el, traverse) => {
168
184
  const children = traverse()
169
- const image = findChildOftype('image-set', children, 'pullquote', context)
185
+ const image = children.find(
186
+ (child): child is ContentTree.ImageSet => child.type === 'image-set'
187
+ )
170
188
 
171
189
  return {
172
190
  type: 'pullquote',
@@ -422,7 +440,7 @@ const commonTagMappings: TagMappings = {
422
440
  layoutWidth: isValidLayoutWidth(layoutWidth) ? layoutWidth : 'auto',
423
441
  collapseAfterHowManyRows: $el.attr('data-table-collapse-rownum')
424
442
  ? parseInt($el.attr('data-table-collapse-rownum') || '0')
425
- : null,
443
+ : undefined,
426
444
  responsiveStyle,
427
445
  children: tableChildren,
428
446
  columnSettings: $el
@@ -1,7 +1,7 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
2
  import { CapiResponse } from '../../model/CapiResponse'
3
3
  import { ReferenceWithCAPIData, mapNodeToReference } from './references'
4
- import { AnyNode } from './Workarounds'
4
+ import { AnyNode, Body } from './Workarounds'
5
5
 
6
6
  function isParent(node: ContentTree.Node): node is ContentTree.Parent {
7
7
  return 'children' in node
@@ -12,10 +12,10 @@ const isValidReferenceType = (
12
12
  ): type is keyof typeof mapNodeToReference => type in mapNodeToReference
13
13
 
14
14
  export default function updateTreeWithReferenceIds(
15
- tree: ContentTree.Body,
15
+ tree: Body,
16
16
  contentApiData?: CapiResponse
17
17
  ): {
18
- tree: ContentTree.Body
18
+ tree: Body
19
19
  references: ReferenceWithCAPIData[]
20
20
  } {
21
21
  const references: ReferenceWithCAPIData[] = []
@@ -105,6 +105,7 @@ const resolvers = {
105
105
 
106
106
  TopperWithFlourish: {
107
107
  ...topperResolvers,
108
+ isLargeHeadline: (topper) => topper.isLargeHeadline(),
108
109
  layout: (topper) => topper.layout(),
109
110
  layoutWidth: (topper) => topper.layoutWidth(),
110
111
  leadFlourish: (topper) => topper.leadFlourish(),