@financial-times/dotcom-ui-shell 7.3.1 → 7.3.2
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/package.json +6 -2
- package/src/__test__/components/Content.test.tsx +23 -0
- package/src/__test__/components/DocumentHead.test.tsx +38 -0
- package/src/__test__/components/GTMBody.test.tsx +36 -0
- package/src/__test__/components/GTMHead.test.tsx +27 -0
- package/src/__test__/components/LinkedData.test.tsx +31 -0
- package/src/__test__/components/OpenGraph.test.tsx +25 -0
- package/src/__test__/components/ResourceHints.test.tsx +18 -0
- package/src/__test__/components/Shell.test.tsx +29 -0
- package/src/__test__/components/Stylesheets.test.tsx +22 -0
- package/src/__test__/components/__snapshots__/Content.test.tsx.snap +22 -0
- package/src/__test__/components/__snapshots__/DocumentHead.test.tsx.snap +87 -0
- package/src/__test__/components/__snapshots__/GTMBody.test.tsx.snap +21 -0
- package/src/__test__/components/__snapshots__/GTMHead.test.tsx.snap +17 -0
- package/src/__test__/components/__snapshots__/LinkedData.test.tsx.snap +41 -0
- package/src/__test__/components/__snapshots__/OpenGraph.test.tsx.snap +42 -0
- package/src/__test__/components/__snapshots__/ResourceHints.test.tsx.snap +54 -0
- package/src/__test__/components/__snapshots__/Shell.test.tsx.snap +317 -0
- package/src/__test__/components/__snapshots__/Stylesheets.test.tsx.snap +50 -0
- package/src/__test__/lib/flattenOpenGraphData.spec.ts +37 -0
- package/src/__test__/lib/formatAttributeNames.spec.ts +38 -0
- package/src/__test__/lib/getResourceType.spec.ts +28 -0
- package/src/components/Content.tsx +25 -0
- package/src/components/DocumentHead.tsx +103 -0
- package/src/components/GTMBody.tsx +29 -0
- package/src/components/GTMHead.tsx +23 -0
- package/src/components/LinkedData.tsx +38 -0
- package/src/components/OpenGraph.tsx +20 -0
- package/src/components/ResourceHints.tsx +63 -0
- package/src/components/Shell.tsx +97 -0
- package/src/components/StyleSheets.tsx +35 -0
- package/src/index.ts +1 -0
- package/src/lib/flattenOpenGraphData.ts +24 -0
- package/src/lib/formatAttributeNames.ts +36 -0
- package/src/lib/getResourceType.ts +34 -0
- package/src/lib/imageServiceIconURL.ts +24 -0
- package/src/lib/loadAsyncStylesheets.ts +27 -0
@@ -0,0 +1,317 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`dotcom-ui-shell/src/components/Shell renders the GTM script when the enableGTM flag is on 1`] = `
|
4
|
+
<html
|
5
|
+
className="no-js core o-typography--loading-sans o-typography--loading-sans-bold o-typography--loading-display o-typography--loading-display-bold"
|
6
|
+
data-o-component="o-typography"
|
7
|
+
lang="en-GB"
|
8
|
+
style={
|
9
|
+
Object {
|
10
|
+
"backgroundColor": "#fff1e5",
|
11
|
+
"color": "#33302e",
|
12
|
+
"overflowX": "hidden",
|
13
|
+
}
|
14
|
+
}
|
15
|
+
>
|
16
|
+
<head>
|
17
|
+
<meta
|
18
|
+
charSet="utf-8"
|
19
|
+
/>
|
20
|
+
<meta
|
21
|
+
content="IE=edge"
|
22
|
+
httpEquiv="X-UA-Compatible"
|
23
|
+
/>
|
24
|
+
<meta
|
25
|
+
content="width=device-width, initial-scale=1"
|
26
|
+
name="viewport"
|
27
|
+
/>
|
28
|
+
<title>
|
29
|
+
Foo | Financial Times
|
30
|
+
</title>
|
31
|
+
<meta
|
32
|
+
content="News, analysis and comment from the Financial Times, the worldʼs leading global business publication"
|
33
|
+
name="description"
|
34
|
+
/>
|
35
|
+
<meta
|
36
|
+
content="index,follow,max-snippet:200,max-image-preview:large"
|
37
|
+
name="robots"
|
38
|
+
/>
|
39
|
+
<meta
|
40
|
+
content="4-t8sFaPvpO5FH_Gnw1dkM28CQepjzo8UjjAkdDflTw"
|
41
|
+
name="google-site-verification"
|
42
|
+
/>
|
43
|
+
<script
|
44
|
+
dangerouslySetInnerHTML={
|
45
|
+
Object {
|
46
|
+
"__html": "{\\"@context\\":\\"http://schema.org\\",\\"@type\\":\\"WebSite\\",\\"name\\":\\"Financial Times\\",\\"alternateName\\":\\"FT.com\\",\\"url\\":\\"http://www.ft.com\\"}",
|
47
|
+
}
|
48
|
+
}
|
49
|
+
type="application/ld+json"
|
50
|
+
/>
|
51
|
+
<meta
|
52
|
+
content="8860325749"
|
53
|
+
property="fb:pages"
|
54
|
+
/>
|
55
|
+
<meta
|
56
|
+
content="@FinancialTimes"
|
57
|
+
property="twitter:site"
|
58
|
+
/>
|
59
|
+
<meta
|
60
|
+
content="app-id=1200842933"
|
61
|
+
name="apple-itunes-app"
|
62
|
+
/>
|
63
|
+
<link
|
64
|
+
href="https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo-v1%3Abrand-ft-logo-square-coloured?source=update-logos&format=svg"
|
65
|
+
rel="icon"
|
66
|
+
type="image/svg+xml"
|
67
|
+
/>
|
68
|
+
<link
|
69
|
+
href="https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo-v1%3Abrand-ft-logo-square-coloured?source=update-logos&format=png&width=32&height=32"
|
70
|
+
rel="alternate icon"
|
71
|
+
sizes="32x32"
|
72
|
+
type="image/png"
|
73
|
+
/>
|
74
|
+
<link
|
75
|
+
href="https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo-v1%3Abrand-ft-logo-square-coloured?source=update-logos&format=png&width=194&height=194"
|
76
|
+
rel="alternate icon"
|
77
|
+
sizes="194x194"
|
78
|
+
type="image/png"
|
79
|
+
/>
|
80
|
+
<link
|
81
|
+
href="https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo-v1%3Abrand-ft-logo-square-coloured?source=update-logos&format=png&width=180&height=180"
|
82
|
+
rel="apple-touch-icon"
|
83
|
+
sizes="180x180"
|
84
|
+
/>
|
85
|
+
<link
|
86
|
+
href="https://www.ft.com/__assets/creatives/manifest/manifest-v6.json"
|
87
|
+
rel="manifest"
|
88
|
+
/>
|
89
|
+
<link
|
90
|
+
href="https://spoor-api.ft.com"
|
91
|
+
rel="preconnect"
|
92
|
+
/>
|
93
|
+
<link
|
94
|
+
crossOrigin="use-credentials"
|
95
|
+
href="https://session-next.ft.com"
|
96
|
+
rel="preconnect"
|
97
|
+
/>
|
98
|
+
<link
|
99
|
+
href="https://ads-api.ft.com"
|
100
|
+
rel="preconnect"
|
101
|
+
/>
|
102
|
+
<link
|
103
|
+
href="https://securepubads.g.doubleclick.net"
|
104
|
+
rel="preconnect"
|
105
|
+
/>
|
106
|
+
<link
|
107
|
+
as="script"
|
108
|
+
href="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces5%2Ces2015%2Ces2016%2Ces2017%2CEventSource%2Cfetch%2CHTMLPictureElement%2CIntersectionObserver%2CNodeList.prototype.forEach&source=next"
|
109
|
+
rel="preload"
|
110
|
+
type={null}
|
111
|
+
/>
|
112
|
+
<link
|
113
|
+
as="font"
|
114
|
+
crossOrigin="anonymous"
|
115
|
+
href="https://www.ft.com/__origami/service/build/v3/font?font_format=woff2&font_name=MetricWeb-Regular&system_code=origami&version=1.12"
|
116
|
+
rel="preload"
|
117
|
+
type="font/woff2"
|
118
|
+
/>
|
119
|
+
<link
|
120
|
+
as="font"
|
121
|
+
crossOrigin="anonymous"
|
122
|
+
href="https://www.ft.com/__origami/service/build/v3/font?font_format=woff2&font_name=MetricWeb-Semibold&system_code=origami&version=1.12"
|
123
|
+
rel="preload"
|
124
|
+
type="font/woff2"
|
125
|
+
/>
|
126
|
+
<link
|
127
|
+
as="font"
|
128
|
+
crossOrigin="anonymous"
|
129
|
+
href="https://www.ft.com/__origami/service/build/v3/font?font_format=woff2&font_name=FinancierDisplayWeb-Regular&system_code=origami&version=1.12"
|
130
|
+
rel="preload"
|
131
|
+
type="font/woff2"
|
132
|
+
/>
|
133
|
+
<link
|
134
|
+
as="font"
|
135
|
+
crossOrigin="anonymous"
|
136
|
+
href="https://www.ft.com/__origami/service/build/v3/font?font_format=woff2&font_name=FinancierDisplayWeb-Bold&system_code=origami&version=1.12"
|
137
|
+
rel="preload"
|
138
|
+
type="font/woff2"
|
139
|
+
/>
|
140
|
+
<script
|
141
|
+
dangerouslySetInnerHTML={
|
142
|
+
Object {
|
143
|
+
"__html": undefined,
|
144
|
+
}
|
145
|
+
}
|
146
|
+
id="initial-props"
|
147
|
+
type="application/json"
|
148
|
+
/>
|
149
|
+
<script
|
150
|
+
dangerouslySetInnerHTML={
|
151
|
+
Object {
|
152
|
+
"__html": "{\\"trackErrors\\":true,\\"core\\":[\\"https://polyfill.io/v3/polyfill.min.js?features=HTMLPictureElement&source=next\\"],\\"enhanced\\":[\\"https://polyfill.io/v3/polyfill.min.js?features=default%2Ces5%2Ces2015%2Ces2016%2Ces2017%2CEventSource%2Cfetch%2CHTMLPictureElement%2CIntersectionObserver%2CNodeList.prototype.forEach&source=next\\"]}",
|
153
|
+
}
|
154
|
+
}
|
155
|
+
id="page-kit-bootstrap-config"
|
156
|
+
type="application/json"
|
157
|
+
/>
|
158
|
+
<script
|
159
|
+
dangerouslySetInnerHTML={
|
160
|
+
Object {
|
161
|
+
"__html": ";(function () {
|
162
|
+
var doc = document.documentElement
|
163
|
+
var isEnhanced = isEnhancedBrowser()
|
164
|
+
var scriptsConfig = getScriptsConfig()
|
165
|
+
var scriptsToLoad = []
|
166
|
+
var currentScript = document.scripts[document.scripts.length - 1]
|
167
|
+
|
168
|
+
doc.className = doc.className.replace('no-js', 'js')
|
169
|
+
|
170
|
+
if (isEnhanced) {
|
171
|
+
doc.className = doc.className.replace('core', 'enhanced')
|
172
|
+
Array.prototype.push.apply(scriptsToLoad, scriptsConfig.enhanced)
|
173
|
+
} else {
|
174
|
+
Array.prototype.push.apply(scriptsToLoad, scriptsConfig.core)
|
175
|
+
}
|
176
|
+
|
177
|
+
for (var i = 0, len = scriptsToLoad.length; i < len; i++) {
|
178
|
+
loadScript(scriptsToLoad[i])
|
179
|
+
}
|
180
|
+
|
181
|
+
function scriptLoadError(error) {
|
182
|
+
var script = error.target ? error.target.src : null
|
183
|
+
|
184
|
+
if (script) {
|
185
|
+
console.error('The script ' + script + ' failed to load') // eslint-disable-line no-console
|
186
|
+
}
|
187
|
+
|
188
|
+
if (/enhanced/.test(doc.className)) {
|
189
|
+
console.warn('Script loading failed, reverting to core experience') // eslint-disable-line no-console
|
190
|
+
doc.className = doc.className.replace('enhanced', 'core')
|
191
|
+
}
|
192
|
+
|
193
|
+
if (scriptsConfig.trackErrors) {
|
194
|
+
addErrorTrackingPixel(script)
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
function loadScript(src) {
|
199
|
+
var script = document.createElement('script')
|
200
|
+
script.onerror = scriptLoadError
|
201
|
+
script.async = false
|
202
|
+
script.src = src
|
203
|
+
currentScript.parentNode.insertBefore(script, currentScript)
|
204
|
+
}
|
205
|
+
|
206
|
+
// \\"Cut the mustard\\" test
|
207
|
+
// by Maggie Allen and Matt Hinchliffe November 2018
|
208
|
+
function isEnhancedBrowser() {
|
209
|
+
var script = document.createElement('script')
|
210
|
+
var input = document.createElement('input')
|
211
|
+
|
212
|
+
return (
|
213
|
+
'visibilityState' in document && // not supported by old Android (4.0-4.4) without a prefix
|
214
|
+
'indeterminate' in input && // not supported by BB 10
|
215
|
+
'flex' in doc.style && // not supported by old Safari (< 9) or IE 6-10
|
216
|
+
'async' in script // not supported by old Opera (Presto engine < 15)
|
217
|
+
)
|
218
|
+
}
|
219
|
+
|
220
|
+
function getScriptsConfig() {
|
221
|
+
var scriptsConfigEl = document.getElementById('page-kit-bootstrap-config')
|
222
|
+
var scriptsConfig = { core: [], enhanced: [], trackErrors: false }
|
223
|
+
|
224
|
+
if (scriptsConfigEl) {
|
225
|
+
try {
|
226
|
+
scriptsConfig = JSON.parse(scriptsConfigEl.innerHTML)
|
227
|
+
} catch (error) {
|
228
|
+
console.error('Bootstrap configuration error', error) // eslint-disable-line no-console
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
return scriptsConfig
|
233
|
+
}
|
234
|
+
|
235
|
+
function addErrorTrackingPixel(script) {
|
236
|
+
var img = new Image()
|
237
|
+
|
238
|
+
var data = JSON.stringify({
|
239
|
+
category: 'javascript',
|
240
|
+
action: 'load-error',
|
241
|
+
system: {
|
242
|
+
source: 'page-kit'
|
243
|
+
},
|
244
|
+
context: {
|
245
|
+
script: script
|
246
|
+
}
|
247
|
+
})
|
248
|
+
|
249
|
+
img.src = 'https://spoor-api.ft.com/px.gif?data=' + encodeURIComponent(data)
|
250
|
+
}
|
251
|
+
})()
|
252
|
+
",
|
253
|
+
}
|
254
|
+
}
|
255
|
+
/>
|
256
|
+
<script
|
257
|
+
dangerouslySetInnerHTML={
|
258
|
+
Object {
|
259
|
+
"__html": "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
260
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
261
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
262
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
263
|
+
})(window,document,'script','dataLayer','GTM-NWQJW68');",
|
264
|
+
}
|
265
|
+
}
|
266
|
+
/>
|
267
|
+
<script
|
268
|
+
dangerouslySetInnerHTML={
|
269
|
+
Object {
|
270
|
+
"__html": "(function loadCustomFonts() {
|
271
|
+
var rootElement = document.documentElement;
|
272
|
+
if (/(^|\\\\s)o-typography-fonts-loaded=1(;|$)/.test(document.cookie)) {
|
273
|
+
var fontLabels = ['sans', 'sans-bold', 'display', 'display-bold'];
|
274
|
+
for (var i = 0; i < fontLabels.length; i++) {
|
275
|
+
rootElement.className = rootElement.className.replace('o-typography--loading-' + fontLabels[i], '');
|
276
|
+
}
|
277
|
+
}
|
278
|
+
}());",
|
279
|
+
}
|
280
|
+
}
|
281
|
+
/>
|
282
|
+
</head>
|
283
|
+
<body>
|
284
|
+
<noscript>
|
285
|
+
<iframe
|
286
|
+
height="0"
|
287
|
+
src="https://www.googletagmanager.com/ns.html?id=GTM-NWQJW68"
|
288
|
+
style={
|
289
|
+
Object {
|
290
|
+
"display": "none",
|
291
|
+
"visibility": "hidden",
|
292
|
+
}
|
293
|
+
}
|
294
|
+
width="0"
|
295
|
+
/>
|
296
|
+
</noscript>
|
297
|
+
<script
|
298
|
+
dangerouslySetInnerHTML={
|
299
|
+
Object {
|
300
|
+
"__html": undefined,
|
301
|
+
}
|
302
|
+
}
|
303
|
+
id="page-kit-app-context"
|
304
|
+
type="application/json"
|
305
|
+
/>
|
306
|
+
<script
|
307
|
+
dangerouslySetInnerHTML={
|
308
|
+
Object {
|
309
|
+
"__html": "{\\"enableGTM\\":true}",
|
310
|
+
}
|
311
|
+
}
|
312
|
+
id="page-kit-flags-embed"
|
313
|
+
type="application/json"
|
314
|
+
/>
|
315
|
+
</body>
|
316
|
+
</html>
|
317
|
+
`;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`dotcom-ui-shell/src/components/StyleSheets renders JavaScript for adding asyncronous stylesheets if appropriate 1`] = `
|
4
|
+
Array [
|
5
|
+
<noscript>
|
6
|
+
<link
|
7
|
+
href="path/to/async-styles.css"
|
8
|
+
rel="stylesheet"
|
9
|
+
/>
|
10
|
+
</noscript>,
|
11
|
+
<script
|
12
|
+
dangerouslySetInnerHTML={
|
13
|
+
Object {
|
14
|
+
"__html": "(function loadAsyncStylesheets() {
|
15
|
+
var currentScript = document.scripts[document.scripts.length - 1];
|
16
|
+
var stylesheets = currentScript.getAttribute('data-stylesheets').split(',');
|
17
|
+
for (var i = 0, len = stylesheets.length; i < len; i++) {
|
18
|
+
var link = document.createElement('link');
|
19
|
+
link.href = stylesheets[i];
|
20
|
+
link.rel = 'stylesheet';
|
21
|
+
link.media = 'print'; // <-- 'print' is intentional; on load, it changes to 'all'.
|
22
|
+
link.onload = function (event) {
|
23
|
+
var target = event.target;
|
24
|
+
target.media = 'all';
|
25
|
+
};
|
26
|
+
currentScript.parentNode.insertBefore(link, currentScript);
|
27
|
+
}
|
28
|
+
})()",
|
29
|
+
}
|
30
|
+
}
|
31
|
+
data-stylesheets="path/to/async-styles.css"
|
32
|
+
/>,
|
33
|
+
]
|
34
|
+
`;
|
35
|
+
|
36
|
+
exports[`dotcom-ui-shell/src/components/StyleSheets renders the given stylesheets and critical styles 1`] = `
|
37
|
+
Array [
|
38
|
+
<style
|
39
|
+
dangerouslySetInnerHTML={
|
40
|
+
Object {
|
41
|
+
"__html": "html { margin: 0 } body { font-family: \\"Comic Sans\\" }",
|
42
|
+
}
|
43
|
+
}
|
44
|
+
/>,
|
45
|
+
<link
|
46
|
+
href="path/to/styles.css"
|
47
|
+
rel="stylesheet"
|
48
|
+
/>,
|
49
|
+
]
|
50
|
+
`;
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import subject from '../../lib/flattenOpenGraphData'
|
2
|
+
|
3
|
+
const fixture = Object.freeze({
|
4
|
+
og: {
|
5
|
+
title: 'Open Graph title',
|
6
|
+
video: 'https://my.site/video.mp4',
|
7
|
+
'video:type': 'video/mp4',
|
8
|
+
'video:width': 640,
|
9
|
+
'video:height': 360
|
10
|
+
},
|
11
|
+
article: {
|
12
|
+
published_time: '2019-02-27T06:59:35.000Z',
|
13
|
+
modified_time: '2019-02-27T16:01:21.000Z',
|
14
|
+
author: ['Joe Bloggs', 'Jane Doe']
|
15
|
+
}
|
16
|
+
})
|
17
|
+
|
18
|
+
describe('dotcom-ui-shell/src/lib/flattenOpenGraphData', () => {
|
19
|
+
it('returns an array', () => {
|
20
|
+
const result = subject(fixture)
|
21
|
+
expect(result).toEqual(expect.any(Array))
|
22
|
+
})
|
23
|
+
|
24
|
+
it('flattens the given object into property/value pairs', () => {
|
25
|
+
const result = subject(fixture)
|
26
|
+
|
27
|
+
expect(result[1]).toEqual(['og:video', fixture.og.video])
|
28
|
+
expect(result[5]).toEqual(['article:published_time', fixture.article.published_time])
|
29
|
+
})
|
30
|
+
|
31
|
+
it('can handle nested data', () => {
|
32
|
+
const result = subject(fixture)
|
33
|
+
|
34
|
+
expect(result[7]).toEqual(['article:author', fixture.article.author[0]])
|
35
|
+
expect(result[8]).toEqual(['article:author', fixture.article.author[1]])
|
36
|
+
})
|
37
|
+
})
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import subject from '../../lib/formatAttributeNames'
|
2
|
+
|
3
|
+
const fixture = Object.freeze({
|
4
|
+
dataVersion: 123,
|
5
|
+
dataAppName: 'test',
|
6
|
+
userLoggedIn: true,
|
7
|
+
lang: 'en-GB'
|
8
|
+
})
|
9
|
+
|
10
|
+
describe('dotcom-ui-shell/src/lib/formatAttributeNames', () => {
|
11
|
+
it('returns a new object', () => {
|
12
|
+
const result = subject(fixture)
|
13
|
+
expect(result).not.toBe(fixture)
|
14
|
+
})
|
15
|
+
|
16
|
+
it('converts camelCase property names to kebab-case', () => {
|
17
|
+
const result = subject(fixture)
|
18
|
+
expect(result).toHaveProperty('data-version', 123)
|
19
|
+
expect(result).toHaveProperty('data-app-name', 'test')
|
20
|
+
expect(result).toHaveProperty('user-logged-in', true)
|
21
|
+
expect(result).toHaveProperty('lang', 'en-GB')
|
22
|
+
})
|
23
|
+
|
24
|
+
it('converts boolean data properties to boolean data attributes', () => {
|
25
|
+
const fixture = { dataIsProduction: true, dataIsDevelopment: false, userIsLoggedIn: true }
|
26
|
+
const result = subject(fixture)
|
27
|
+
|
28
|
+
// where react is concerned, a `true` boolean data attribute
|
29
|
+
// is one where the attribute value is an empty string (because
|
30
|
+
// it is not possible to render the attribute without a value),
|
31
|
+
// and a `false` boolean data attribute is one where the attribute
|
32
|
+
// has not been specified altogether
|
33
|
+
expect(result).toEqual({
|
34
|
+
'data-is-production': '',
|
35
|
+
'user-is-logged-in': true
|
36
|
+
})
|
37
|
+
})
|
38
|
+
})
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import subject from '../../lib/getResourceType'
|
2
|
+
|
3
|
+
describe('dotcom-ui-shell/src/lib/getResourceType', () => {
|
4
|
+
it('uses the file extension to match to a resource type', () => {
|
5
|
+
expect(subject('style.css')).toEqual('style')
|
6
|
+
expect(subject('script.js')).toEqual('script')
|
7
|
+
expect(subject('image.png')).toEqual('image')
|
8
|
+
expect(
|
9
|
+
subject(
|
10
|
+
'https://www.ft.com/__origami/service/build/v3/font?font_format=woff2&font_name=MetricWeb-Regular&system_code=origami&version=1.12'
|
11
|
+
)
|
12
|
+
).toEqual('font')
|
13
|
+
})
|
14
|
+
|
15
|
+
it('throws if the file extension cannot be matched', () => {
|
16
|
+
expect(() => subject('style.doc')).toThrow()
|
17
|
+
})
|
18
|
+
|
19
|
+
it('supports URLs', () => {
|
20
|
+
expect(subject('www.example.com/assets/style.css')).toEqual('style')
|
21
|
+
expect(subject('www.example.com/images/graphic.svg#icon')).toEqual('image')
|
22
|
+
expect(subject('http://polyfill.io/v3/bundle.min.js?features=es5,es6')).toEqual('script')
|
23
|
+
})
|
24
|
+
|
25
|
+
it('supports file paths', () => {
|
26
|
+
expect(subject('/assets/public/style.as83hd99.css')).toEqual('style')
|
27
|
+
})
|
28
|
+
})
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
|
3
|
+
export type TContentProps = {
|
4
|
+
contents?: string | React.ReactNode
|
5
|
+
}
|
6
|
+
|
7
|
+
const styles = {
|
8
|
+
display: 'contents'
|
9
|
+
}
|
10
|
+
|
11
|
+
function Content({ contents }: TContentProps) {
|
12
|
+
if (typeof contents === 'string') {
|
13
|
+
return <div style={styles} dangerouslySetInnerHTML={{ __html: contents }} />
|
14
|
+
}
|
15
|
+
|
16
|
+
// We could try and validate this but there are so many possibilities
|
17
|
+
// of node types and potentially nested arrays etc.
|
18
|
+
if (contents) {
|
19
|
+
return <React.Fragment>{contents}</React.Fragment>
|
20
|
+
}
|
21
|
+
|
22
|
+
return null
|
23
|
+
}
|
24
|
+
|
25
|
+
export default Content
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import imageServiceIconURL from '../lib/imageServiceIconURL'
|
3
|
+
import OpenGraph, { TOpenGraphProps } from './OpenGraph'
|
4
|
+
import LinkedData, { TLinkedDataProps } from './LinkedData'
|
5
|
+
|
6
|
+
export type TDocumentHeadProps = TOpenGraphProps &
|
7
|
+
TLinkedDataProps & {
|
8
|
+
description?: string
|
9
|
+
facebookPage?: string
|
10
|
+
googleSiteVerification?: string
|
11
|
+
metaTags?: Array<{ [key: string]: any }>
|
12
|
+
pageTitle: string
|
13
|
+
robots?: string
|
14
|
+
siteTitle?: string
|
15
|
+
twitterSite?: string
|
16
|
+
canonicalURL?: string
|
17
|
+
manifestFile?: string
|
18
|
+
additionalMetadata?: React.ReactNode,
|
19
|
+
showSmartBanner?: boolean
|
20
|
+
}
|
21
|
+
|
22
|
+
const DocumentHead = (props: TDocumentHeadProps) => (
|
23
|
+
<React.Fragment>
|
24
|
+
<meta charSet="utf-8" />
|
25
|
+
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
27
|
+
|
28
|
+
<title>{props.pageTitle ? `${props.pageTitle} | ${props.siteTitle}` : props.siteTitle}</title>
|
29
|
+
|
30
|
+
{props.description && <meta name="description" content={props.description} />}
|
31
|
+
|
32
|
+
{props.canonicalURL && <link rel="canonical" href={props.canonicalURL} />}
|
33
|
+
|
34
|
+
{/* SEO */}
|
35
|
+
<meta name="robots" content={props.robots} />
|
36
|
+
<meta name="google-site-verification" content={props.googleSiteVerification} />
|
37
|
+
{props.metaTags.map((attributes, i) => (
|
38
|
+
<meta key={`meta-${i}`} {...attributes} />
|
39
|
+
))}
|
40
|
+
<LinkedData jsonLd={props.jsonLd} />
|
41
|
+
|
42
|
+
{/* social media */}
|
43
|
+
<meta property="fb:pages" content={props.facebookPage} />
|
44
|
+
<meta property="twitter:site" content={props.twitterSite} />
|
45
|
+
<OpenGraph openGraph={props.openGraph} />
|
46
|
+
|
47
|
+
{/* native apps */}
|
48
|
+
{props.showSmartBanner &&
|
49
|
+
(
|
50
|
+
<meta
|
51
|
+
name="apple-itunes-app"
|
52
|
+
content={props.canonicalURL ? `app-id=1200842933, app-argument=${props.canonicalURL}` : 'app-id=1200842933'}
|
53
|
+
/>
|
54
|
+
)
|
55
|
+
}
|
56
|
+
|
57
|
+
{/* packaging */}
|
58
|
+
<link
|
59
|
+
rel="icon"
|
60
|
+
type="image/svg+xml"
|
61
|
+
href={imageServiceIconURL('ftlogo-v1:brand-ft-logo-square-coloured', 0, 'svg')}
|
62
|
+
/>
|
63
|
+
<link
|
64
|
+
rel="alternate icon"
|
65
|
+
type="image/png"
|
66
|
+
href={imageServiceIconURL('ftlogo-v1:brand-ft-logo-square-coloured', 32)}
|
67
|
+
sizes="32x32"
|
68
|
+
/>
|
69
|
+
<link
|
70
|
+
rel="alternate icon"
|
71
|
+
type="image/png"
|
72
|
+
href={imageServiceIconURL('ftlogo-v1:brand-ft-logo-square-coloured', 194)}
|
73
|
+
sizes="194x194"
|
74
|
+
/>
|
75
|
+
<link
|
76
|
+
rel="apple-touch-icon"
|
77
|
+
href={imageServiceIconURL('ftlogo-v1:brand-ft-logo-square-coloured', 180)}
|
78
|
+
sizes="180x180"
|
79
|
+
/>
|
80
|
+
|
81
|
+
{props.manifestFile ? <link rel="manifest" href={props.manifestFile} /> : null}
|
82
|
+
|
83
|
+
{/* We can't add an option for every single metadata option so allow custom elements to be inserted*/}
|
84
|
+
{props.additionalMetadata}
|
85
|
+
</React.Fragment>
|
86
|
+
)
|
87
|
+
|
88
|
+
DocumentHead.defaultProps = {
|
89
|
+
description:
|
90
|
+
'News, analysis and comment from the Financial Times, the worldʼs leading global business publication',
|
91
|
+
facebookPage: '8860325749',
|
92
|
+
googleSiteVerification: '4-t8sFaPvpO5FH_Gnw1dkM28CQepjzo8UjjAkdDflTw',
|
93
|
+
metaTags: [],
|
94
|
+
jsonLd: [],
|
95
|
+
robots: 'index,follow,max-snippet:200,max-image-preview:large',
|
96
|
+
siteTitle: 'Financial Times',
|
97
|
+
twitterSite: '@FinancialTimes',
|
98
|
+
manifestFile: 'https://www.ft.com/__assets/creatives/manifest/manifest-v6.json',
|
99
|
+
additionalMetadata: null,
|
100
|
+
showSmartBanner: true
|
101
|
+
}
|
102
|
+
|
103
|
+
export default DocumentHead
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { TFlagsData } from '@financial-times/dotcom-ui-flags/src/types'
|
3
|
+
|
4
|
+
// This component is maintained by the ads team
|
5
|
+
const GTMBody = ({ flags }: { flags: TFlagsData }) => {
|
6
|
+
if (!flags.enableGTM) {
|
7
|
+
return null
|
8
|
+
}
|
9
|
+
|
10
|
+
return (
|
11
|
+
<noscript>
|
12
|
+
<iframe
|
13
|
+
src="https://www.googletagmanager.com/ns.html?id=GTM-NWQJW68"
|
14
|
+
height="0"
|
15
|
+
width="0"
|
16
|
+
style={{
|
17
|
+
display: 'none',
|
18
|
+
visibility: 'hidden'
|
19
|
+
}}
|
20
|
+
/>
|
21
|
+
</noscript>
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
GTMBody.defaultProps = {
|
26
|
+
flags: {}
|
27
|
+
}
|
28
|
+
|
29
|
+
export default GTMBody
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { TFlagsData } from '@financial-times/dotcom-ui-flags/src/types'
|
3
|
+
|
4
|
+
// This component is maintained by the ads team
|
5
|
+
const GTMHead = ({ flags }: { flags: TFlagsData }) => {
|
6
|
+
if (!flags.enableGTM) {
|
7
|
+
return null
|
8
|
+
}
|
9
|
+
|
10
|
+
const tagManager = `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
11
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
12
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
13
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
14
|
+
})(window,document,'script','dataLayer','GTM-NWQJW68');`
|
15
|
+
|
16
|
+
return <script dangerouslySetInnerHTML={{ __html: tagManager }} />
|
17
|
+
}
|
18
|
+
|
19
|
+
GTMHead.defaultProps = {
|
20
|
+
flags: {}
|
21
|
+
}
|
22
|
+
|
23
|
+
export default GTMHead
|