@financial-times/dotcom-ui-shell 7.2.7 → 7.3.1
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/node/components/Content.d.ts +6 -0
- package/dist/node/components/DocumentHead.d.ts +36 -0
- package/dist/node/components/GTMBody.d.ts +11 -0
- package/dist/node/components/GTMHead.d.ts +11 -0
- package/dist/node/components/LinkedData.d.ts +13 -0
- package/dist/node/components/OpenGraph.d.ts +12 -0
- package/dist/node/components/ResourceHints.d.ts +11 -0
- package/dist/node/components/Shell.d.ts +29 -0
- package/dist/node/components/StyleSheets.d.ts +14 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/lib/flattenOpenGraphData.d.ts +4 -0
- package/dist/node/lib/formatAttributeNames.d.ts +4 -0
- package/dist/node/lib/getResourceType.d.ts +2 -0
- package/dist/node/lib/imageServiceIconURL.d.ts +2 -0
- package/dist/node/lib/loadAsyncStylesheets.d.ts +2 -0
- package/dist/tsconfig.tsbuildinfo +2370 -0
- package/package.json +11 -9
- package/src/components/Content.tsx +0 -25
- package/src/components/DocumentHead.tsx +0 -103
- package/src/components/GTMBody.tsx +0 -29
- package/src/components/GTMHead.tsx +0 -23
- package/src/components/LinkedData.tsx +0 -38
- package/src/components/OpenGraph.tsx +0 -20
- package/src/components/ResourceHints.tsx +0 -63
- package/src/components/Shell.tsx +0 -97
- package/src/components/StyleSheets.tsx +0 -35
- package/src/index.ts +0 -1
- package/src/lib/flattenOpenGraphData.ts +0 -24
- package/src/lib/formatAttributeNames.ts +0 -36
- package/src/lib/getResourceType.ts +0 -34
- package/src/lib/imageServiceIconURL.ts +0 -24
- package/src/lib/loadAsyncStylesheets.ts +0 -27
package/package.json
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@financial-times/dotcom-ui-shell",
|
3
|
-
"version": "7.
|
3
|
+
"version": "7.3.1",
|
4
4
|
"description": "",
|
5
5
|
"main": "component.js",
|
6
6
|
"browser": "browser.js",
|
7
7
|
"types": "src/index.ts",
|
8
8
|
"scripts": {
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1",
|
10
|
-
"tsc": "../../node_modules/.bin/tsc --incremental",
|
11
10
|
"clean": "npm run clean:dist && npm run clean:node_modules",
|
12
11
|
"clean:dist": "rm -rf dist",
|
13
12
|
"clean:node_modules": "rm -rf node_modules",
|
14
13
|
"clean:install": "npm run clean && npm i",
|
15
|
-
"build:node": "
|
14
|
+
"build:node": "tsc",
|
16
15
|
"build": "npm run build:node",
|
17
16
|
"dev": "npm run build:node -- --watch",
|
18
17
|
"preinstall": "[ \"$INIT_CWD\" != \"$PWD\" ] || npm_config_yes=true npx check-engine"
|
@@ -21,11 +20,11 @@
|
|
21
20
|
"author": "",
|
22
21
|
"license": "MIT",
|
23
22
|
"dependencies": {
|
24
|
-
"@financial-times/dotcom-ui-app-context": "
|
25
|
-
"@financial-times/dotcom-ui-base-styles": "
|
26
|
-
"@financial-times/dotcom-ui-bootstrap": "
|
27
|
-
"@financial-times/dotcom-ui-flags": "
|
28
|
-
"@financial-times/dotcom-ui-polyfill-service": "
|
23
|
+
"@financial-times/dotcom-ui-app-context": "file:../dotcom-ui-app-context",
|
24
|
+
"@financial-times/dotcom-ui-base-styles": "file:../dotcom-ui-base-styles",
|
25
|
+
"@financial-times/dotcom-ui-bootstrap": "file:../dotcom-ui-bootstrap",
|
26
|
+
"@financial-times/dotcom-ui-flags": "file:../dotcom-ui-flags",
|
27
|
+
"@financial-times/dotcom-ui-polyfill-service": "file:../dotcom-ui-polyfill-service",
|
29
28
|
"mime-types": "^2.1.26"
|
30
29
|
},
|
31
30
|
"peerDependencies": {
|
@@ -35,6 +34,9 @@
|
|
35
34
|
"node": ">= 14.0.0",
|
36
35
|
"npm": "7.x || 8.x"
|
37
36
|
},
|
37
|
+
"files": [
|
38
|
+
"dist/"
|
39
|
+
],
|
38
40
|
"repository": {
|
39
41
|
"type": "git",
|
40
42
|
"repository": "https://github.com/Financial-Times/dotcom-page-kit.git",
|
@@ -48,4 +50,4 @@
|
|
48
50
|
"check-engine": "^1.10.1",
|
49
51
|
"react": "^16.8.6"
|
50
52
|
}
|
51
|
-
}
|
53
|
+
}
|
@@ -1,25 +0,0 @@
|
|
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
|
@@ -1,103 +0,0 @@
|
|
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
|
@@ -1,29 +0,0 @@
|
|
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
|
@@ -1,23 +0,0 @@
|
|
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
|
@@ -1,38 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
|
3
|
-
export type TLinkedDataProps = {
|
4
|
-
jsonLd?: { [key: string]: any }
|
5
|
-
}
|
6
|
-
|
7
|
-
const LinkedData = ({ jsonLd }: TLinkedDataProps) => (
|
8
|
-
<React.Fragment>
|
9
|
-
{Array.isArray(jsonLd) &&
|
10
|
-
jsonLd.map((data, i) => (
|
11
|
-
<script
|
12
|
-
key={`jsonld-${i}`}
|
13
|
-
type="application/ld+json"
|
14
|
-
dangerouslySetInnerHTML={{
|
15
|
-
__html: JSON.stringify(data)
|
16
|
-
}}
|
17
|
-
/>
|
18
|
-
))}
|
19
|
-
<script
|
20
|
-
type="application/ld+json"
|
21
|
-
dangerouslySetInnerHTML={{
|
22
|
-
__html: JSON.stringify({
|
23
|
-
'@context': 'http://schema.org',
|
24
|
-
'@type': 'WebSite',
|
25
|
-
name: 'Financial Times',
|
26
|
-
alternateName: 'FT.com',
|
27
|
-
url: 'http://www.ft.com'
|
28
|
-
})
|
29
|
-
}}
|
30
|
-
/>
|
31
|
-
</React.Fragment>
|
32
|
-
)
|
33
|
-
|
34
|
-
LinkedData.defaultProps = {
|
35
|
-
jsonLd: []
|
36
|
-
}
|
37
|
-
|
38
|
-
export default LinkedData
|
@@ -1,20 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import flattenOpenGraphData, { TOpenGraphData } from '../lib/flattenOpenGraphData'
|
3
|
-
|
4
|
-
export type TOpenGraphProps = {
|
5
|
-
openGraph?: TOpenGraphData
|
6
|
-
}
|
7
|
-
|
8
|
-
const OpenGraph = ({ openGraph }: TOpenGraphProps) => (
|
9
|
-
<React.Fragment>
|
10
|
-
{flattenOpenGraphData(openGraph).map(([property, content], i) => (
|
11
|
-
<meta key={`og-${i}`} property={property} content={content} />
|
12
|
-
))}
|
13
|
-
</React.Fragment>
|
14
|
-
)
|
15
|
-
|
16
|
-
OpenGraph.defaultProps = {
|
17
|
-
openGraph: {}
|
18
|
-
}
|
19
|
-
|
20
|
-
export default OpenGraph
|
@@ -1,63 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import mimeTypes from 'mime-types'
|
3
|
-
import getResourceType from '../lib/getResourceType'
|
4
|
-
|
5
|
-
export type TResourceHintsProps = {
|
6
|
-
resourceHints?: string[]
|
7
|
-
}
|
8
|
-
|
9
|
-
const ResourceHints = (props: TResourceHintsProps) => {
|
10
|
-
return (
|
11
|
-
<React.Fragment>
|
12
|
-
{/*
|
13
|
-
Spoor is the API which receives the tracking events sent by the o-tracking library
|
14
|
-
<https://github.com/Financial-Times/o-tracking>
|
15
|
-
*/}
|
16
|
-
<link rel="preconnect" href="https://spoor-api.ft.com" />
|
17
|
-
{/*
|
18
|
-
The session API is used to validate users and retrieve information about them
|
19
|
-
<https://github.com/Financial-Times/next-session>
|
20
|
-
*/}
|
21
|
-
<link rel="preconnect" href="https://session-next.ft.com" crossOrigin="use-credentials" />
|
22
|
-
{/*
|
23
|
-
The ads API is used to fetch ad targeting information for the current page
|
24
|
-
<https://github.com/Financial-Times/next-ads-api>
|
25
|
-
*/}
|
26
|
-
<link rel="preconnect" href="https://ads-api.ft.com" />
|
27
|
-
{/*
|
28
|
-
The Google Publisher Tag library (GPT) is hosted here which is used to deliver ads
|
29
|
-
<https://github.com/Financial-Times/o-ads/blob/HEAD/src/js/ad-servers/gpt.js>
|
30
|
-
*/}
|
31
|
-
<link rel="preconnect" href="https://securepubads.g.doubleclick.net" />
|
32
|
-
|
33
|
-
{props.resourceHints.map((resource, i) => {
|
34
|
-
const contentType = getResourceType(resource)
|
35
|
-
const mimeType =
|
36
|
-
mimeTypes.lookup(resource) ||
|
37
|
-
mimeTypes.lookup(resource.match(/(?<=font_format=)([a-z0-9]+)/)?.[0]) ||
|
38
|
-
null
|
39
|
-
|
40
|
-
const attributes: React.LinkHTMLAttributes<HTMLLinkElement> = {
|
41
|
-
as: contentType,
|
42
|
-
href: resource,
|
43
|
-
type: mimeType
|
44
|
-
}
|
45
|
-
|
46
|
-
// Fonts are expected to be fetched anonymously by the browser, and the preload request is
|
47
|
-
// only made anonymous by using the crossorigin attribute.
|
48
|
-
// <https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content>
|
49
|
-
if (contentType === 'font') {
|
50
|
-
attributes.crossOrigin = 'anonymous'
|
51
|
-
}
|
52
|
-
|
53
|
-
return <link key={`hint-${i}`} rel="preload" {...attributes} />
|
54
|
-
})}
|
55
|
-
</React.Fragment>
|
56
|
-
)
|
57
|
-
}
|
58
|
-
|
59
|
-
ResourceHints.defaultProps = {
|
60
|
-
resourceHints: []
|
61
|
-
}
|
62
|
-
|
63
|
-
export default ResourceHints
|
package/src/components/Shell.tsx
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import Content, { TContentProps } from './Content'
|
3
|
-
import DocumentHead, { TDocumentHeadProps } from './DocumentHead'
|
4
|
-
import StyleSheets, { TStylesheetProps } from './StyleSheets'
|
5
|
-
import ResourceHints, { TResourceHintsProps } from './ResourceHints'
|
6
|
-
import { AppContextEmbed, TAppContextProps } from '@financial-times/dotcom-ui-app-context'
|
7
|
-
import {
|
8
|
-
fontFaceURLs,
|
9
|
-
documentStyles,
|
10
|
-
LoadFontsEmbed,
|
11
|
-
loadCustomFontsClassNames
|
12
|
-
} from '@financial-times/dotcom-ui-base-styles'
|
13
|
-
import { FlagsEmbed, TFlagsEmbedProps } from '@financial-times/dotcom-ui-flags'
|
14
|
-
import { Bootstrap, TBootstrapProps } from '@financial-times/dotcom-ui-bootstrap'
|
15
|
-
import * as polyfillService from '@financial-times/dotcom-ui-polyfill-service'
|
16
|
-
import formatAttributeNames, { TAttributeData } from '../lib/formatAttributeNames'
|
17
|
-
import GTMHead from './GTMHead'
|
18
|
-
import GTMBody from './GTMBody'
|
19
|
-
|
20
|
-
type TShellProps = TDocumentHeadProps &
|
21
|
-
TAppContextProps &
|
22
|
-
TStylesheetProps &
|
23
|
-
TResourceHintsProps &
|
24
|
-
TContentProps &
|
25
|
-
TFlagsEmbedProps & {
|
26
|
-
scripts?: string[]
|
27
|
-
children?: any
|
28
|
-
initialProps?: any
|
29
|
-
bodyAttributes?: TAttributeData
|
30
|
-
htmlAttributes?: TAttributeData
|
31
|
-
}
|
32
|
-
|
33
|
-
function Shell(props: TShellProps) {
|
34
|
-
const bootstrapProps: TBootstrapProps = {
|
35
|
-
coreScripts: [polyfillService.core()],
|
36
|
-
enhancedScripts: [polyfillService.enhanced(), ...props.scripts]
|
37
|
-
}
|
38
|
-
|
39
|
-
const resourceHints = [
|
40
|
-
polyfillService.enhanced(),
|
41
|
-
// There is no need to include stylesheets here as any <link rel="stylesheet" /> tags
|
42
|
-
// should be found by the browser's speculative parser.
|
43
|
-
...props.scripts,
|
44
|
-
...props.resourceHints,
|
45
|
-
...fontFaceURLs
|
46
|
-
]
|
47
|
-
|
48
|
-
return (
|
49
|
-
<html
|
50
|
-
{...formatAttributeNames(props.htmlAttributes)}
|
51
|
-
lang="en-GB"
|
52
|
-
className={`no-js core ${loadCustomFontsClassNames}`}
|
53
|
-
data-o-component="o-typography"
|
54
|
-
style={documentStyles}
|
55
|
-
>
|
56
|
-
<head>
|
57
|
-
<DocumentHead {...props} />
|
58
|
-
<ResourceHints resourceHints={resourceHints} />
|
59
|
-
{/* TODO: refactor initial props, flags data, and context data to the bottom of body */}
|
60
|
-
<script
|
61
|
-
id="initial-props"
|
62
|
-
type="application/json"
|
63
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(props.initialProps) }}
|
64
|
-
/>
|
65
|
-
<StyleSheets
|
66
|
-
criticalStyles={props.criticalStyles}
|
67
|
-
stylesheets={props.stylesheets}
|
68
|
-
asyncStylesheets={props.asyncStylesheets}
|
69
|
-
/>
|
70
|
-
<Bootstrap {...bootstrapProps} />
|
71
|
-
<GTMHead flags={props.flags} />
|
72
|
-
<LoadFontsEmbed />
|
73
|
-
</head>
|
74
|
-
<body {...formatAttributeNames(props.bodyAttributes)}>
|
75
|
-
<GTMBody flags={props.flags} />
|
76
|
-
<Content contents={props.contents || props.children} />
|
77
|
-
<AppContextEmbed appContext={props.appContext} />
|
78
|
-
<FlagsEmbed flags={props.flags} />
|
79
|
-
</body>
|
80
|
-
</html>
|
81
|
-
)
|
82
|
-
}
|
83
|
-
|
84
|
-
Shell.defaultProps = {
|
85
|
-
scripts: [],
|
86
|
-
stylesheets: [],
|
87
|
-
asyncStylesheets: [],
|
88
|
-
resourceHints: [],
|
89
|
-
htmlAttributes: {},
|
90
|
-
bodyAttributes: {}
|
91
|
-
}
|
92
|
-
|
93
|
-
export { Shell }
|
94
|
-
export type { TShellProps }
|
95
|
-
|
96
|
-
// Export sub-components to more-easily enable custom integrations
|
97
|
-
export { DocumentHead, ResourceHints, Content }
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import loadAsyncStylesheetsString from '../lib/loadAsyncStylesheets'
|
3
|
-
|
4
|
-
export type TStylesheetProps = {
|
5
|
-
criticalStyles?: string
|
6
|
-
stylesheets?: string[]
|
7
|
-
asyncStylesheets?: string[]
|
8
|
-
}
|
9
|
-
|
10
|
-
const Stylesheets = ({ criticalStyles, stylesheets, asyncStylesheets }: TStylesheetProps) => (
|
11
|
-
<React.Fragment>
|
12
|
-
{criticalStyles && <style dangerouslySetInnerHTML={{ __html: criticalStyles }} />}
|
13
|
-
{Array.isArray(stylesheets) &&
|
14
|
-
stylesheets.map((stylesheet, i) => <link rel="stylesheet" key={`stylesheet-${i}`} href={stylesheet} />)}
|
15
|
-
{Array.isArray(asyncStylesheets) && !!asyncStylesheets.length && (
|
16
|
-
<React.Fragment>
|
17
|
-
<noscript>
|
18
|
-
{asyncStylesheets.map((stylesheet, i) => (
|
19
|
-
<link rel="stylesheet" href={stylesheet} key={`async-stylesheet-${i}`} />
|
20
|
-
))}
|
21
|
-
</noscript>
|
22
|
-
<script
|
23
|
-
data-stylesheets={asyncStylesheets.join()}
|
24
|
-
dangerouslySetInnerHTML={{ __html: loadAsyncStylesheetsString }}></script>
|
25
|
-
</React.Fragment>
|
26
|
-
)}
|
27
|
-
</React.Fragment>
|
28
|
-
)
|
29
|
-
|
30
|
-
Stylesheets.defaultProps = {
|
31
|
-
stylesheets: [],
|
32
|
-
asyncStylesheets: []
|
33
|
-
}
|
34
|
-
|
35
|
-
export default Stylesheets
|
package/src/index.ts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export * from './components/Shell'
|
@@ -1,24 +0,0 @@
|
|
1
|
-
// Flattens a nested object into an array of key/value pairs
|
2
|
-
// { foo: { bar: { baz: 123 } } } => [['foo:bar:baz', 123]]
|
3
|
-
|
4
|
-
export type TOpenGraphData = {
|
5
|
-
[key: string]: any | any[] | TOpenGraphData
|
6
|
-
}
|
7
|
-
|
8
|
-
export default function flattenData(data: TOpenGraphData, prefix?: string): Array<string[]> {
|
9
|
-
const output = []
|
10
|
-
|
11
|
-
for (const [key, value] of Object.entries(data)) {
|
12
|
-
const property = prefix ? `${prefix}:${key}` : key
|
13
|
-
|
14
|
-
if (value && value.constructor === Object) {
|
15
|
-
output.push(...flattenData(value, property))
|
16
|
-
} else if (Array.isArray(value)) {
|
17
|
-
output.push(...value.map((value) => [property, value]))
|
18
|
-
} else {
|
19
|
-
output.push([property, value])
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
return output
|
24
|
-
}
|
@@ -1,36 +0,0 @@
|
|
1
|
-
export type TAttributeData = {
|
2
|
-
[key: string]: string | number | boolean
|
3
|
-
}
|
4
|
-
|
5
|
-
export default function formatAttributeNames(data: TAttributeData = {}) {
|
6
|
-
const output = {}
|
7
|
-
|
8
|
-
for (const [key, value] of Object.entries(data)) {
|
9
|
-
const hyphenatedKey = hyphenateString(key)
|
10
|
-
|
11
|
-
// Let's render boolean data attributes properly
|
12
|
-
// as per https://github.com/Financial-Times/dotcom-page-kit/issues/370
|
13
|
-
if (hyphenatedKey.startsWith('data-') && typeof value === 'boolean') {
|
14
|
-
// Where react is concerned, a `true` boolean data attribute
|
15
|
-
// is one where the attribute value is an empty string (because
|
16
|
-
// it is not possible to render an attribute without a value),
|
17
|
-
// and a `false` boolean data attribute is one where the attribute
|
18
|
-
// has not been specified altogether
|
19
|
-
if (value) {
|
20
|
-
output[hyphenatedKey] = ''
|
21
|
-
}
|
22
|
-
} else {
|
23
|
-
output[hyphenatedKey] = value
|
24
|
-
}
|
25
|
-
}
|
26
|
-
|
27
|
-
return output
|
28
|
-
}
|
29
|
-
|
30
|
-
function hyphenateChar(char) {
|
31
|
-
return '-' + char.toLowerCase()
|
32
|
-
}
|
33
|
-
|
34
|
-
function hyphenateString(prop) {
|
35
|
-
return prop.replace(/([A-Z])/g, hyphenateChar)
|
36
|
-
}
|
@@ -1,34 +0,0 @@
|
|
1
|
-
import path from 'path'
|
2
|
-
import url from 'url'
|
3
|
-
|
4
|
-
const StyleFiles = new Set(['.css'])
|
5
|
-
|
6
|
-
const ScriptFiles = new Set(['.js', '.mjs'])
|
7
|
-
|
8
|
-
const ImageFiles = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'])
|
9
|
-
|
10
|
-
export default (file: string): string => {
|
11
|
-
// Always parse the file so that we can ignore any domain names, query strings etc.
|
12
|
-
// Node's old URL API is able to parse anything inc. filenames, paths, and URLs.
|
13
|
-
const { pathname } = url.parse(file)
|
14
|
-
|
15
|
-
const extension = path.extname(pathname)
|
16
|
-
|
17
|
-
if (StyleFiles.has(extension)) {
|
18
|
-
return 'style'
|
19
|
-
}
|
20
|
-
|
21
|
-
if (ScriptFiles.has(extension)) {
|
22
|
-
return 'script'
|
23
|
-
}
|
24
|
-
|
25
|
-
if (ImageFiles.has(extension)) {
|
26
|
-
return 'image'
|
27
|
-
}
|
28
|
-
|
29
|
-
if (file.includes('font_format=woff')) {
|
30
|
-
return 'font'
|
31
|
-
}
|
32
|
-
|
33
|
-
throw Error(`Unknown filename extension "${extension}`)
|
34
|
-
}
|
@@ -1,24 +0,0 @@
|
|
1
|
-
import querystring from 'querystring'
|
2
|
-
|
3
|
-
function imageServiceIconURL(image: string, size: number, format = 'png'): string {
|
4
|
-
const serviceURL = 'https://www.ft.com/__origami/service/image/v2/images/raw/'
|
5
|
-
|
6
|
-
const serviceParameters = {
|
7
|
-
source: 'update-logos',
|
8
|
-
format: format,
|
9
|
-
width: size,
|
10
|
-
height: size
|
11
|
-
}
|
12
|
-
|
13
|
-
// Do not add width and height if format is svg because svg files scale automatically
|
14
|
-
if (format === 'svg') {
|
15
|
-
delete serviceParameters.width
|
16
|
-
delete serviceParameters.height
|
17
|
-
}
|
18
|
-
|
19
|
-
const queryString = querystring.stringify(serviceParameters)
|
20
|
-
|
21
|
-
return `${serviceURL}${encodeURIComponent(image)}?${queryString}`
|
22
|
-
}
|
23
|
-
|
24
|
-
export default imageServiceIconURL
|
@@ -1,27 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
Load stylesheets asyncronously. See:
|
3
|
-
• https://www.filamentgroup.com/lab/load-css-simpler/
|
4
|
-
• https://w3c.github.io/preload/#example-5
|
5
|
-
|
6
|
-
@NOTE: This is in ES5 syntax, because it's not compiled, because it's server-side code.
|
7
|
-
(You don't need to compile server-side code because you get to set whichever version of node you want.)
|
8
|
-
Its stringified and given to the client via "dangerouslySetInnerHTML" in a <script> tag.
|
9
|
-
Because it runs in the client, it needs to be ES5 so it's compatible with older browsers.
|
10
|
-
*/
|
11
|
-
function loadAsyncStylesheets() {
|
12
|
-
var currentScript = document.scripts[document.scripts.length - 1]
|
13
|
-
var stylesheets = currentScript.getAttribute('data-stylesheets').split(',')
|
14
|
-
|
15
|
-
for (var i = 0, len = stylesheets.length; i < len; i++) {
|
16
|
-
var link = document.createElement('link')
|
17
|
-
link.href = stylesheets[i]
|
18
|
-
link.rel = 'stylesheet'
|
19
|
-
link.media = 'print' // <-- 'print' is intentional; on load, it changes to 'all'.
|
20
|
-
link.onload = function (event) {
|
21
|
-
var target = event.target as HTMLLinkElement
|
22
|
-
target.media = 'all'
|
23
|
-
}
|
24
|
-
currentScript.parentNode.insertBefore(link, currentScript)
|
25
|
-
}
|
26
|
-
}
|
27
|
-
export default '(' + loadAsyncStylesheets.toString() + ')()'
|