@financial-times/cp-content-pipeline-schema 2.15.0 → 3.0.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 (161) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/lib/datasources/capi.d.ts +2 -2
  3. package/lib/datasources/capi.js +4 -2
  4. package/lib/datasources/capi.js.map +1 -1
  5. package/lib/datasources/capi.test.js +2 -2
  6. package/lib/fixtures/capiObject.d.ts +2 -2
  7. package/lib/fixtures/capiObject.js +2 -0
  8. package/lib/fixtures/capiObject.js.map +1 -1
  9. package/lib/fixtures/capiPerson.d.ts +1 -1
  10. package/lib/generated/index.d.ts +296 -131
  11. package/lib/helpers/decorateHeadshotUrl.d.ts +1 -2
  12. package/lib/helpers/decorateHeadshotUrl.js +2 -3
  13. package/lib/helpers/decorateHeadshotUrl.js.map +1 -1
  14. package/lib/model/Byline.d.ts +8 -10
  15. package/lib/model/Byline.js +34 -33
  16. package/lib/model/Byline.js.map +1 -1
  17. package/lib/model/Byline.test.js +105 -52
  18. package/lib/model/Byline.test.js.map +1 -1
  19. package/lib/model/CapiResponse.d.ts +12 -16
  20. package/lib/model/CapiResponse.js +38 -41
  21. package/lib/model/CapiResponse.js.map +1 -1
  22. package/lib/model/CapiResponse.test.js +7 -18
  23. package/lib/model/CapiResponse.test.js.map +1 -1
  24. package/lib/model/Clip.d.ts +1 -1
  25. package/lib/model/Concept.d.ts +1 -1
  26. package/lib/model/Concept.js +1 -2
  27. package/lib/model/Concept.js.map +1 -1
  28. package/lib/model/FlourishSource.d.ts +1 -1
  29. package/lib/model/FlourishSource.js.map +1 -1
  30. package/lib/model/Image.d.ts +1 -1
  31. package/lib/model/LeadFlourish.test.js +1 -0
  32. package/lib/model/LeadFlourish.test.js.map +1 -1
  33. package/lib/model/Person.d.ts +6 -12
  34. package/lib/model/Person.js +39 -66
  35. package/lib/model/Person.js.map +1 -1
  36. package/lib/model/Person.test.js +7 -60
  37. package/lib/model/Person.test.js.map +1 -1
  38. package/lib/model/Picture.d.ts +1 -1
  39. package/lib/model/RichText.d.ts +1 -2
  40. package/lib/model/Topper.d.ts +1 -1
  41. package/lib/model/Topper.js +10 -8
  42. package/lib/model/Topper.js.map +1 -1
  43. package/lib/model/Topper.test.js +9 -10
  44. package/lib/model/Topper.test.js.map +1 -1
  45. package/lib/model/schemas/capi/article.d.ts +7 -4
  46. package/lib/model/schemas/capi/article.js +1 -0
  47. package/lib/model/schemas/capi/article.js.map +1 -1
  48. package/lib/model/schemas/capi/audio.d.ts +7 -4
  49. package/lib/model/schemas/capi/audio.js +1 -0
  50. package/lib/model/schemas/capi/audio.js.map +1 -1
  51. package/lib/model/schemas/capi/base-schema.d.ts +14 -123
  52. package/lib/model/schemas/capi/base-schema.js +7 -6
  53. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  54. package/lib/model/schemas/capi/content-package.d.ts +10 -5
  55. package/lib/model/schemas/capi/content-package.js +2 -0
  56. package/lib/model/schemas/capi/content-package.js.map +1 -1
  57. package/lib/model/schemas/capi/index.d.ts +41 -22
  58. package/lib/model/schemas/capi/internal-content.d.ts +24 -0
  59. package/lib/model/schemas/capi/internal-content.js +3 -0
  60. package/lib/model/schemas/capi/internal-content.js.map +1 -0
  61. package/lib/model/schemas/capi/live-blog-package.d.ts +7 -4
  62. package/lib/model/schemas/capi/live-blog-package.js +1 -0
  63. package/lib/model/schemas/capi/live-blog-package.js.map +1 -1
  64. package/lib/model/schemas/capi/placeholder.d.ts +7 -4
  65. package/lib/model/schemas/capi/placeholder.js +1 -0
  66. package/lib/model/schemas/capi/placeholder.js.map +1 -1
  67. package/lib/model/schemas/capi/video.d.ts +10 -5
  68. package/lib/model/schemas/capi/video.js +2 -0
  69. package/lib/model/schemas/capi/video.js.map +1 -1
  70. package/lib/resolvers/concept.d.ts +37 -2
  71. package/lib/resolvers/concept.js +17 -10
  72. package/lib/resolvers/concept.js.map +1 -1
  73. package/lib/resolvers/content-tree/Workarounds.d.ts +19 -11
  74. package/lib/resolvers/content-tree/references/Author.d.ts +4 -0
  75. package/lib/resolvers/content-tree/references/Author.js +14 -0
  76. package/lib/resolvers/content-tree/references/Author.js.map +1 -0
  77. package/lib/resolvers/content-tree/references/ClipSet.d.ts +1 -1
  78. package/lib/resolvers/content-tree/references/ClipSet.js +1 -1
  79. package/lib/resolvers/content-tree/references/ClipSet.js.map +1 -1
  80. package/lib/resolvers/content-tree/references/Flourish.d.ts +1 -1
  81. package/lib/resolvers/content-tree/references/Reference.d.ts +1 -1
  82. package/lib/resolvers/content-tree/references/index.d.ts +4 -2
  83. package/lib/resolvers/content-tree/references/index.js +4 -1
  84. package/lib/resolvers/content-tree/references/index.js.map +1 -1
  85. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.d.ts +3 -3
  86. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js +2 -3
  87. package/lib/resolvers/content-tree/updateTreeWithReferenceIds.js.map +1 -1
  88. package/lib/resolvers/content.d.ts +27 -18
  89. package/lib/resolvers/content.js +4 -2
  90. package/lib/resolvers/content.js.map +1 -1
  91. package/lib/resolvers/image.d.ts +12 -12
  92. package/lib/resolvers/index.d.ts +89 -42
  93. package/lib/resolvers/leadFlourish.d.ts +2 -1
  94. package/lib/resolvers/leadFlourish.js +1 -0
  95. package/lib/resolvers/leadFlourish.js.map +1 -1
  96. package/lib/resolvers/person.d.ts +1 -1
  97. package/lib/resolvers/person.js +1 -1
  98. package/lib/resolvers/person.js.map +1 -1
  99. package/lib/resolvers/picture.d.ts +4 -4
  100. package/lib/resolvers/richText.d.ts +1 -1
  101. package/lib/resolvers/teaser.d.ts +1 -1
  102. package/lib/resolvers/topper.d.ts +3 -3
  103. package/package.json +1 -1
  104. package/queries/article.graphql +35 -13
  105. package/src/datasources/capi.test.ts +3 -3
  106. package/src/datasources/capi.ts +5 -3
  107. package/src/fixtures/capiObject.ts +4 -2
  108. package/src/fixtures/capiPerson.ts +1 -1
  109. package/src/generated/index.ts +321 -132
  110. package/src/helpers/decorateHeadshotUrl.ts +2 -2
  111. package/src/model/Byline.test.ts +136 -55
  112. package/src/model/Byline.ts +49 -39
  113. package/src/model/CapiResponse.test.ts +9 -25
  114. package/src/model/CapiResponse.ts +83 -56
  115. package/src/model/Clip.ts +1 -1
  116. package/src/model/Concept.ts +3 -3
  117. package/src/model/FlourishSource.ts +1 -1
  118. package/src/model/Image.test.ts +1 -1
  119. package/src/model/Image.ts +1 -1
  120. package/src/model/LeadFlourish.test.ts +1 -0
  121. package/src/model/Person.test.ts +11 -62
  122. package/src/model/Person.ts +47 -51
  123. package/src/model/Picture.test.ts +1 -1
  124. package/src/model/Picture.ts +1 -1
  125. package/src/model/Topper.test.ts +22 -18
  126. package/src/model/Topper.ts +10 -9
  127. package/src/model/__snapshots__/Byline.test.ts.snap +166 -27
  128. package/src/model/schemas/capi/article.ts +1 -0
  129. package/src/model/schemas/capi/audio.ts +1 -0
  130. package/src/model/schemas/capi/base-schema.ts +5 -4
  131. package/src/model/schemas/capi/content-package.ts +2 -0
  132. package/src/model/schemas/capi/internal-content.ts +45 -0
  133. package/src/model/schemas/capi/live-blog-package.ts +1 -0
  134. package/src/model/schemas/capi/placeholder.ts +1 -0
  135. package/src/model/schemas/capi/video.ts +2 -0
  136. package/src/resolvers/concept.ts +29 -12
  137. package/src/resolvers/content-tree/Workarounds.ts +39 -20
  138. package/src/resolvers/content-tree/references/Author.ts +18 -0
  139. package/src/resolvers/content-tree/references/ClipSet.ts +6 -8
  140. package/src/resolvers/content-tree/references/ImageSet.ts +1 -1
  141. package/src/resolvers/content-tree/references/ScrollyImage.ts +1 -1
  142. package/src/resolvers/content-tree/references/index.ts +6 -1
  143. package/src/resolvers/content-tree/updateTreeWithReferenceIds.ts +6 -8
  144. package/src/resolvers/content.ts +4 -2
  145. package/src/resolvers/leadFlourish.ts +1 -0
  146. package/src/resolvers/person.ts +1 -1
  147. package/src/types/n-display-metadata.d.ts +1 -1
  148. package/tsconfig.tsbuildinfo +1 -1
  149. package/typedefs/clip.graphql +2 -2
  150. package/typedefs/concept.graphql +64 -2
  151. package/typedefs/content.graphql +63 -39
  152. package/typedefs/image.graphql +12 -12
  153. package/typedefs/leadFlourish.graphql +32 -0
  154. package/typedefs/person.graphql +2 -2
  155. package/typedefs/picture.graphql +6 -6
  156. package/typedefs/references/author.graphql +7 -0
  157. package/typedefs/references/clipSet.graphql +14 -2
  158. package/typedefs/references/tweet.graphql +1 -1
  159. package/typedefs/teaser.graphql +10 -10
  160. package/src/types/internal-content.d.ts +0 -55
  161. package/typedefs/leadFlouish.graphql +0 -29
@@ -1,6 +1,6 @@
1
- import { CapiPerson } from '../types/internal-content'
1
+ import { CapiPerson } from '../model/schemas/capi/internal-content'
2
2
 
3
- export const UUID_REGEX =
3
+ const UUID_REGEX =
4
4
  /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-9][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/i
5
5
 
6
6
  const IMAGESET_REGEX = /fthead(?:-v\d)?\:[^?]+/
@@ -1,107 +1,188 @@
1
1
  import { Byline } from './Byline'
2
2
  import context from '../fixtures/dummyContext'
3
+ import { Concept } from './Concept'
4
+
3
5
  const vanity = true
4
6
 
5
7
  describe('byline transformation', () => {
6
8
  test('adds a link around a single known author', async () => {
7
9
  const bylineText = 'Chris Giles in London'
8
- const authorUrlMapping = new Map([
9
- [
10
- 'Chris Giles',
11
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28',
12
- ],
13
- ])
10
+ const authors = [
11
+ new Concept(
12
+ {
13
+ id: 'https://api.ft.com/things/1d556016-ad16-4fe7-8724-42b3fb15ad28',
14
+ apiUrl:
15
+ 'https://api.ft.com/people/1d556016-ad16-4fe7-8724-42b3fb15ad28',
16
+ prefLabel: 'Chris Giles',
17
+ directType: 'http://www.ft.com/ontology/person/Person',
18
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
19
+ types: [
20
+ 'http://www.ft.com/ontology/core/Thing',
21
+ 'http://www.ft.com/ontology/concept/Concept',
22
+ 'http://www.ft.com/ontology/person/Person',
23
+ ],
24
+ },
25
+ context
26
+ ),
27
+ ]
14
28
 
15
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
29
+ const byline = new Byline(bylineText, vanity, authors)
16
30
  const result = await byline.buildBylineTree()
17
31
  expect(result).toMatchSnapshot()
18
32
  })
19
33
 
20
34
  test('adds links around multiple known authors', async () => {
21
35
  const bylineText = 'Chris Giles and Martin Wolf in London'
22
- const authorUrlMapping = new Map([
23
- [
24
- 'Chris Giles',
25
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28',
26
- ],
27
- [
28
- 'Martin Wolf',
29
- 'https://www.ft.com/stream/uuid/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
30
- ],
31
- ])
36
+ const authors = [
37
+ new Concept(
38
+ {
39
+ id: 'https://api.ft.com/things/1d556016-ad16-4fe7-8724-42b3fb15ad28',
40
+ apiUrl:
41
+ 'https://api.ft.com/people/1d556016-ad16-4fe7-8724-42b3fb15ad28',
42
+ prefLabel: 'Chris Giles',
43
+ directType: 'http://www.ft.com/ontology/person/Person',
44
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
45
+ types: [
46
+ 'http://www.ft.com/ontology/core/Thing',
47
+ 'http://www.ft.com/ontology/concept/Concept',
48
+ 'http://www.ft.com/ontology/person/Person',
49
+ ],
50
+ },
51
+ context
52
+ ),
53
+ new Concept(
54
+ {
55
+ id: 'https://api.ft.com/things/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
56
+ apiUrl:
57
+ 'https://api.ft.com/people/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
58
+ prefLabel: 'Martin Wolf',
59
+ directType: 'http://www.ft.com/ontology/person/Person',
60
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
61
+ types: [
62
+ 'http://www.ft.com/ontology/core/Thing',
63
+ 'http://www.ft.com/ontology/concept/Concept',
64
+ 'http://www.ft.com/ontology/person/Person',
65
+ ],
66
+ },
67
+ context
68
+ ),
69
+ ]
32
70
 
33
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
71
+ const byline = new Byline(bylineText, vanity, authors)
34
72
  const result = await byline.buildBylineTree()
35
73
  expect(result).toMatchSnapshot()
36
74
  })
37
75
 
38
76
  test('ignores unknown authors in byline text', async () => {
39
77
  const bylineText = 'Chris Giles and Nick Ramsbottom in London'
40
- const authorUrlMapping = new Map([
41
- [
42
- 'Chris Giles',
43
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28',
44
- ],
45
- ])
78
+ const authors = [
79
+ new Concept(
80
+ {
81
+ id: 'https://api.ft.com/things/1d556016-ad16-4fe7-8724-42b3fb15ad28',
82
+ apiUrl:
83
+ 'https://api.ft.com/people/1d556016-ad16-4fe7-8724-42b3fb15ad28',
84
+ prefLabel: 'Chris Giles',
85
+ directType: 'http://www.ft.com/ontology/person/Person',
86
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
87
+ types: [
88
+ 'http://www.ft.com/ontology/core/Thing',
89
+ 'http://www.ft.com/ontology/concept/Concept',
90
+ 'http://www.ft.com/ontology/person/Person',
91
+ ],
92
+ },
93
+ context
94
+ ),
95
+ ]
46
96
 
47
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
97
+ const byline = new Byline(bylineText, vanity, authors)
48
98
  const result = await byline.buildBylineTree()
49
99
  expect(result).toMatchSnapshot()
50
100
  })
51
101
 
52
102
  test('ignores extra authors in annotations array', async () => {
53
103
  const bylineText = 'Martin Wolf in London'
54
- const authorUrlMapping = new Map([
55
- [
56
- 'Chris Giles',
57
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-42b3fb15ad28',
58
- ],
59
- [
60
- 'Martin Wolf',
61
- 'https://www.ft.com/stream/uuid/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
62
- ],
63
- ])
104
+ const authors = [
105
+ new Concept(
106
+ {
107
+ id: 'https://api.ft.com/things/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
108
+ apiUrl:
109
+ 'https://api.ft.com/people/7c1e1e72-57ae-4461-862a-f8d24dd42e22',
110
+ prefLabel: 'Martin Wolf',
111
+ directType: 'http://www.ft.com/ontology/person/Person',
112
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
113
+ types: [
114
+ 'http://www.ft.com/ontology/core/Thing',
115
+ 'http://www.ft.com/ontology/concept/Concept',
116
+ 'http://www.ft.com/ontology/person/Person',
117
+ ],
118
+ },
119
+ context
120
+ ),
121
+ ]
64
122
 
65
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
123
+ const byline = new Byline(bylineText, vanity, authors)
66
124
  const result = await byline.buildBylineTree()
67
125
  expect(result).toMatchSnapshot()
68
126
  })
69
127
 
70
128
  test('handles bylines without any matching authors', async () => {
71
129
  const bylineText = 'Chris Giles and Nick Ramsbottom in London'
72
- const authorUrlMapping: Map<string, string> = new Map([])
130
+ const authors: Concept[] = []
73
131
 
74
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
132
+ const byline = new Byline(bylineText, vanity, authors)
75
133
  const result = await byline.buildBylineTree()
76
-
77
134
  expect(result).toMatchSnapshot()
78
135
  })
79
136
 
80
137
  test('converts straight apostrophes in byline', async () => {
81
138
  const bylineText = "Sarah O'Connor in Bali"
82
- const authorUrlMapping = new Map([
83
- [
84
- 'Sarah O’Connor',
85
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
86
- ],
87
- ])
88
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
89
- const result = await byline.buildBylineTree()
139
+ const authors = [
140
+ new Concept(
141
+ {
142
+ id: 'https://api.ft.com/things/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
143
+ apiUrl:
144
+ 'https://api.ft.com/people/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
145
+ prefLabel: 'Sarah O’Connor',
146
+ directType: 'http://www.ft.com/ontology/person/Person',
147
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
148
+ types: [
149
+ 'http://www.ft.com/ontology/core/Thing',
150
+ 'http://www.ft.com/ontology/concept/Concept',
151
+ 'http://www.ft.com/ontology/person/Person',
152
+ ],
153
+ },
154
+ context
155
+ ),
156
+ ]
90
157
 
158
+ const byline = new Byline(bylineText, vanity, authors)
159
+ const result = await byline.buildBylineTree()
91
160
  expect(result).toMatchSnapshot()
92
161
  })
93
162
 
94
163
  test('leaves curly apostrophes in byline', async () => {
95
164
  const bylineText = 'Sarah O’Connor in Bali'
96
- const authorUrlMapping = new Map([
97
- [
98
- 'Sarah O’Connor',
99
- 'https://www.ft.com/stream/uuid/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
100
- ],
101
- ])
102
- const byline = new Byline(bylineText, vanity, authorUrlMapping, context)
103
- const result = await byline.buildBylineTree()
165
+ const authors = [
166
+ new Concept(
167
+ {
168
+ id: 'https://api.ft.com/things/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
169
+ apiUrl:
170
+ 'https://api.ft.com/people/1d556016-ad16-4fe7-8724-eeeeeeeeeeee',
171
+ prefLabel: 'Sarah O’Connor',
172
+ directType: 'http://www.ft.com/ontology/person/Person',
173
+ predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
174
+ types: [
175
+ 'http://www.ft.com/ontology/core/Thing',
176
+ 'http://www.ft.com/ontology/concept/Concept',
177
+ 'http://www.ft.com/ontology/person/Person',
178
+ ],
179
+ },
180
+ context
181
+ ),
182
+ ]
104
183
 
184
+ const byline = new Byline(bylineText, vanity, authors)
185
+ const result = await byline.buildBylineTree()
105
186
  expect(result).toMatchSnapshot()
106
187
  })
107
188
  })
@@ -1,58 +1,68 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
- import { AuthorLink } from '../resolvers/content-tree/Workarounds'
3
- import { QueryContext } from '..'
2
+ import { Concept } from './Concept'
3
+ import {
4
+ Byline as BylineNode,
5
+ Author as AuthorNode,
6
+ } from '../resolvers/content-tree/Workarounds'
7
+ import updateTreeWithReferenceIds from '../resolvers/content-tree/updateTreeWithReferenceIds'
8
+ import { CapiResponse } from './CapiResponse'
4
9
 
5
10
  export class Byline {
11
+ static normaliseQuotes(str: string) {
12
+ return str.replaceAll("'", '’')
13
+ }
14
+
6
15
  constructor(
7
16
  private byline: string,
8
17
  private vanity: boolean,
9
- private authorUrlMapping: Map<string, string>,
10
- private context: QueryContext
18
+ private authors: Concept[],
19
+ private contentApiData?: CapiResponse
11
20
  ) {}
12
21
 
13
22
  async buildBylineTree() {
14
23
  // Normalise apostrophes in byline string
15
- const bylineWithCorrectApostrophes = this.byline.replace("'", '’')
24
+ const normalisedByline = Byline.normaliseQuotes(this.byline)
25
+
26
+ const bylineParts = this.#splitBylineByName(
27
+ normalisedByline,
28
+ this.authors.map((author) => Byline.normaliseQuotes(author.prefLabel()))
29
+ )
30
+
31
+ const authorNameMapping = Object.fromEntries(
32
+ this.authors.map((author) => {
33
+ return [Byline.normaliseQuotes(author.prefLabel()), author] as const
34
+ })
35
+ )
36
+
37
+ const children = bylineParts.map((part): AuthorNode | ContentTree.Text => {
38
+ const author = authorNameMapping[part]
39
+ const text: ContentTree.Text = {
40
+ type: 'text',
41
+ value: part,
42
+ }
43
+
44
+ return author
45
+ ? {
46
+ type: 'author',
47
+ id: author.uuid(),
48
+ children: [text],
49
+ }
50
+ : text
51
+ })
16
52
 
17
- const split = this.#splitBylineByName(bylineWithCorrectApostrophes, [
18
- ...this.authorUrlMapping.keys(),
19
- ])
20
- return this.#buildUrlTree(this.authorUrlMapping, split)
53
+ return updateTreeWithReferenceIds(
54
+ {
55
+ type: 'byline',
56
+ children,
57
+ } satisfies BylineNode,
58
+ this.contentApiData
59
+ )
21
60
  }
22
61
 
23
62
  #splitBylineByName(byline: string, names: string[]) {
24
63
  if (!names.length) return [byline]
64
+
25
65
  const regex = new RegExp(`(${names.join('|')})`, 'ig')
26
66
  return byline.split(regex).filter((string) => string !== '')
27
67
  }
28
-
29
- async #buildUrlTree(urlMapping: Map<string, string>, parts: string[]) {
30
- const children: (AuthorLink | ContentTree.Text)[] = await Promise.all(
31
- parts.map(async (part) => {
32
- const fullUrl = urlMapping.get(part)
33
- const vanityUrl =
34
- this.vanity && fullUrl
35
- ? await this.context.dataSources.vanityUrls.get(fullUrl)
36
- : null
37
-
38
- if (fullUrl) {
39
- return {
40
- type: 'author-link',
41
- href: vanityUrl || fullUrl,
42
- children: [{ type: 'text', value: part }],
43
- }
44
- } else {
45
- return {
46
- type: 'text',
47
- value: part,
48
- }
49
- }
50
- })
51
- )
52
-
53
- return {
54
- tree: { type: 'root', children },
55
- references: [],
56
- }
57
- }
58
68
  }
@@ -13,31 +13,6 @@ describe('CAPI response', () => {
13
13
  })
14
14
  })
15
15
 
16
- describe('Author URL mapping', () => {
17
- test('converts any straight quotes in the author name to curly quotes', () => {
18
- const straighQuoteAuthorName = `Sinéad O'Connor`
19
- const curlyQuoteAuthorName = `Sinéad O’Connor`
20
-
21
- const article = cloneDeep(baseCapiObject)
22
- delete article.annotations
23
- article.annotations = [
24
- {
25
- id: 'http://api.ft.com/things/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
26
- predicate: 'http://www.ft.com/ontology/annotation/hasAuthor',
27
- prefLabel: straighQuoteAuthorName,
28
- },
29
- ]
30
- const capiResponse = new CapiResponse(article, context)
31
-
32
- expect(
33
- capiResponse.getAuthorUrlMapping().get(curlyQuoteAuthorName)
34
- ).toBeDefined()
35
- expect(
36
- capiResponse.getAuthorUrlMapping().get(straighQuoteAuthorName)
37
- ).toBeUndefined()
38
- })
39
- })
40
-
41
16
  describe('Content Type', () => {
42
17
  test(`uses first entry in types if there's no type`, () => {
43
18
  const article = cloneDeep(baseCapiObject)
@@ -84,6 +59,15 @@ describe('CAPI response', () => {
84
59
  })
85
60
  })
86
61
 
62
+ describe('Modified timestamp', () => {
63
+ test('generates a timestamp from the last modified date', () => {
64
+ const article = cloneDeep(baseCapiObject)
65
+ const capiResponse = new CapiResponse(article, context)
66
+
67
+ expect(capiResponse.modifiedTimestamp()).toEqual(1712061296789)
68
+ })
69
+ })
70
+
87
71
  describe('liveBlogPosts', () => {
88
72
  test('returns a resolved array of the articles contained sorted by most recently published', async () => {
89
73
  const liveBlogPackage = cloneDeep({