@financial-times/dotcom-ui-shell 7.3.1 → 7.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/package.json +6 -2
  2. package/src/__test__/components/Content.test.tsx +23 -0
  3. package/src/__test__/components/DocumentHead.test.tsx +38 -0
  4. package/src/__test__/components/GTMBody.test.tsx +36 -0
  5. package/src/__test__/components/GTMHead.test.tsx +27 -0
  6. package/src/__test__/components/LinkedData.test.tsx +31 -0
  7. package/src/__test__/components/OpenGraph.test.tsx +25 -0
  8. package/src/__test__/components/ResourceHints.test.tsx +18 -0
  9. package/src/__test__/components/Shell.test.tsx +29 -0
  10. package/src/__test__/components/Stylesheets.test.tsx +22 -0
  11. package/src/__test__/components/__snapshots__/Content.test.tsx.snap +22 -0
  12. package/src/__test__/components/__snapshots__/DocumentHead.test.tsx.snap +87 -0
  13. package/src/__test__/components/__snapshots__/GTMBody.test.tsx.snap +21 -0
  14. package/src/__test__/components/__snapshots__/GTMHead.test.tsx.snap +17 -0
  15. package/src/__test__/components/__snapshots__/LinkedData.test.tsx.snap +41 -0
  16. package/src/__test__/components/__snapshots__/OpenGraph.test.tsx.snap +42 -0
  17. package/src/__test__/components/__snapshots__/ResourceHints.test.tsx.snap +54 -0
  18. package/src/__test__/components/__snapshots__/Shell.test.tsx.snap +317 -0
  19. package/src/__test__/components/__snapshots__/Stylesheets.test.tsx.snap +50 -0
  20. package/src/__test__/lib/flattenOpenGraphData.spec.ts +37 -0
  21. package/src/__test__/lib/formatAttributeNames.spec.ts +38 -0
  22. package/src/__test__/lib/getResourceType.spec.ts +28 -0
  23. package/src/components/Content.tsx +25 -0
  24. package/src/components/DocumentHead.tsx +103 -0
  25. package/src/components/GTMBody.tsx +29 -0
  26. package/src/components/GTMHead.tsx +23 -0
  27. package/src/components/LinkedData.tsx +38 -0
  28. package/src/components/OpenGraph.tsx +20 -0
  29. package/src/components/ResourceHints.tsx +63 -0
  30. package/src/components/Shell.tsx +97 -0
  31. package/src/components/StyleSheets.tsx +35 -0
  32. package/src/index.ts +1 -0
  33. package/src/lib/flattenOpenGraphData.ts +24 -0
  34. package/src/lib/formatAttributeNames.ts +36 -0
  35. package/src/lib/getResourceType.ts +34 -0
  36. package/src/lib/imageServiceIconURL.ts +24 -0
  37. 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