@financial-times/dotcom-ui-shell 7.2.7 → 7.3.1
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/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() + ')()'
|