@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.
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