gatsby-theme-q3 4.5.16 → 4.5.19
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/.eslintrc.js +12 -12
- package/CHANGELOG.md +1181 -1164
- package/LICENSE +21 -21
- package/__fixtures__/en/titles.json +2 -2
- package/__fixtures__/fr/titles.json +2 -2
- package/gatsby-browser.js +38 -38
- package/gatsby-config.js +62 -62
- package/gatsby-node.js +77 -77
- package/gatsby-ssr.js +11 -20
- package/index.js +1 -1
- package/lib/components/AccountPublicGateway.js +2 -3
- package/lib/components/AdminLoader.js +2 -3
- package/lib/components/AdminPrivateGateway.js +3 -4
- package/lib/components/AdminPublicGateway.js +3 -4
- package/lib/components/AdminRouter.js +3 -4
- package/lib/components/BlogArchiveTemplate.js +2 -3
- package/lib/components/BlogTemplate.js +2 -3
- package/lib/components/FormBox.js +2 -3
- package/lib/components/FormBoxContent.js +2 -3
- package/lib/components/FormBoxNotice.js +2 -3
- package/lib/components/IsBrowserReady.js +2 -3
- package/lib/components/PageWrapper.js +2 -3
- package/lib/components/PublicTemplate.js +2 -3
- package/lib/components/Redirect.js +2 -3
- package/lib/components/RedirectToIndex.js +2 -3
- package/lib/components/RichText.js +2 -3
- package/lib/components/SearchEngine.js +4 -7
- package/lib/components/ShareButton.js +2 -3
- package/lib/components/Wrapper.js +2 -3
- package/lib/components/__tests__/useSiteMetaData.test.js +44 -44
- package/lib/components/__tests__/withAuthenticate.test.js +2 -2
- package/lib/components/__tests__/withSuccessOp.test.js +4 -4
- package/lib/components/index.js +2 -3
- package/lib/components/useSiteMetaData.js +1 -1
- package/lib/components/utils.js +2 -3
- package/lib/components/withAuthenticate.js +2 -2
- package/lib/components/withPublicTemplate.js +2 -3
- package/lib/components/withSuccessOp.js +3 -4
- package/lib/pages/404.js +2 -3
- package/lib/pages/login.js +2 -3
- package/lib/pages/password-change.js +2 -3
- package/lib/pages/password-reset.js +2 -3
- package/lib/pages/reverify.js +2 -3
- package/lib/pages/verify.js +2 -3
- package/package.json +5 -5
- package/src/components/AccountPublicGateway.jsx +18 -18
- package/src/components/AdminLoader.jsx +16 -16
- package/src/components/AdminPrivateGateway.jsx +37 -37
- package/src/components/AdminPublicGateway.jsx +34 -34
- package/src/components/AdminRouter.jsx +44 -44
- package/src/components/BlogArchiveTemplate.jsx +55 -55
- package/src/components/BlogTemplate.jsx +104 -104
- package/src/components/FormBox.jsx +22 -22
- package/src/components/FormBoxContent.jsx +26 -26
- package/src/components/FormBoxNotice.jsx +21 -21
- package/src/components/IsBrowserReady.jsx +13 -13
- package/src/components/PageWrapper.jsx +20 -20
- package/src/components/PublicTemplate.jsx +198 -198
- package/src/components/Redirect.jsx +13 -13
- package/src/components/RedirectToIndex.jsx +9 -9
- package/src/components/RichText.jsx +196 -196
- package/src/components/SearchEngine.jsx +124 -124
- package/src/components/ShareButton.jsx +80 -80
- package/src/components/Wrapper.jsx +14 -14
- package/src/components/__tests__/SearchEngine.test.jsx +58 -58
- package/src/components/__tests__/useSiteMetaData.test.js +44 -44
- package/src/components/__tests__/withAuthenticate.test.jsx +52 -52
- package/src/components/__tests__/withSuccessOp.test.jsx +57 -57
- package/src/components/index.js +16 -16
- package/src/components/useSiteMetaData.js +35 -35
- package/src/components/utils.js +23 -23
- package/src/components/withAuthenticate.jsx +20 -20
- package/src/components/withPublicTemplate.jsx +11 -11
- package/src/components/withSuccessOp.jsx +43 -43
- package/src/pages/404.jsx +31 -31
- package/src/pages/login.jsx +71 -71
- package/src/pages/password-change.jsx +72 -72
- package/src/pages/password-reset.jsx +47 -47
- package/src/pages/reverify.jsx +72 -72
- package/src/pages/verify.jsx +70 -70
@@ -1,196 +1,196 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { get } from 'lodash';
|
3
|
-
import PropTypes from 'prop-types';
|
4
|
-
import { useStaticQuery, graphql, Link } from 'gatsby';
|
5
|
-
import Card from '@material-ui/core/Card';
|
6
|
-
import CardContent from '@material-ui/core/CardContent';
|
7
|
-
import Box from '@material-ui/core/Box';
|
8
|
-
import Typography from '@material-ui/core/Typography';
|
9
|
-
import List from '@material-ui/core/List';
|
10
|
-
import ListItem from '@material-ui/core/ListItem';
|
11
|
-
import CardActionArea from '@material-ui/core/CardActionArea';
|
12
|
-
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
13
|
-
import Divider from '@material-ui/core/Divider';
|
14
|
-
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
15
|
-
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
|
16
|
-
import {
|
17
|
-
MARKS,
|
18
|
-
BLOCKS,
|
19
|
-
INLINES,
|
20
|
-
} from '@contentful/rich-text-types';
|
21
|
-
|
22
|
-
const isImage = (v) => v === 'image';
|
23
|
-
|
24
|
-
const getGroup = (v) =>
|
25
|
-
typeof v === 'string' ? v.split('/')[0] : '';
|
26
|
-
|
27
|
-
const imageRender = (node, locale) => {
|
28
|
-
const { title, description, file } = get(
|
29
|
-
node,
|
30
|
-
'data.target.fields',
|
31
|
-
{},
|
32
|
-
);
|
33
|
-
|
34
|
-
if (!file || !(locale in file)) return null;
|
35
|
-
|
36
|
-
const mimeType = file[locale].contentType;
|
37
|
-
const mimeGroup = getGroup(mimeType);
|
38
|
-
|
39
|
-
return isImage(mimeGroup) ? (
|
40
|
-
<img
|
41
|
-
title={title ? title[locale] : null}
|
42
|
-
alt={description ? description[locale] : null}
|
43
|
-
src={file[locale].url}
|
44
|
-
/>
|
45
|
-
) : null;
|
46
|
-
};
|
47
|
-
|
48
|
-
export const renderRichText = (
|
49
|
-
json,
|
50
|
-
locale = 'en-CA',
|
51
|
-
sitemap = {},
|
52
|
-
) => {
|
53
|
-
if (!json || !Object.keys(json).length) return null;
|
54
|
-
|
55
|
-
const getFromSitemap = (node) =>
|
56
|
-
get(
|
57
|
-
sitemap,
|
58
|
-
get(node, 'data.target.sys.contentful_id'),
|
59
|
-
);
|
60
|
-
|
61
|
-
return documentToReactComponents(json, {
|
62
|
-
renderMark: {
|
63
|
-
[MARKS.BOLD]: (text) => <strong>{text}</strong>,
|
64
|
-
[MARKS.ITALIC]: (text) => <i>{text}</i>,
|
65
|
-
[MARKS.UNDERLINE]: (text) => <u>{text}</u>,
|
66
|
-
[MARKS.CODE]: (text) => <code>{text}</code>,
|
67
|
-
},
|
68
|
-
renderNode: {
|
69
|
-
[BLOCKS.PARAGRAPH]: (node, children) => (
|
70
|
-
<Typography component="p">{children}</Typography>
|
71
|
-
),
|
72
|
-
[BLOCKS.HEADING_1]: (node, children) => (
|
73
|
-
<Typography variant="h1" gutterBottom>
|
74
|
-
{children}
|
75
|
-
</Typography>
|
76
|
-
),
|
77
|
-
[BLOCKS.HEADING_2]: (node, children) => (
|
78
|
-
<Typography variant="h2" gutterBottom>
|
79
|
-
{children}
|
80
|
-
</Typography>
|
81
|
-
),
|
82
|
-
[BLOCKS.HEADING_3]: (node, children) => (
|
83
|
-
<Typography variant="h3" gutterBottom>
|
84
|
-
{children}
|
85
|
-
</Typography>
|
86
|
-
),
|
87
|
-
[BLOCKS.HEADING_4]: (node, children) => (
|
88
|
-
<Typography variant="h4" gutterBottom>
|
89
|
-
{children}
|
90
|
-
</Typography>
|
91
|
-
),
|
92
|
-
[BLOCKS.HEADING_5]: (node, children) => (
|
93
|
-
<Typography variant="h5" gutterBottom>
|
94
|
-
{children}
|
95
|
-
</Typography>
|
96
|
-
),
|
97
|
-
[BLOCKS.HEADING_6]: (node, children) => (
|
98
|
-
<Typography variant="h6" gutterBottom>
|
99
|
-
{children}
|
100
|
-
</Typography>
|
101
|
-
),
|
102
|
-
[BLOCKS.OL_LIST]: (node, children) => (
|
103
|
-
<List component="ol">{children}</List>
|
104
|
-
),
|
105
|
-
[BLOCKS.UL_LIST]: (node, children) => (
|
106
|
-
<List component="ul">{children}</List>
|
107
|
-
),
|
108
|
-
[BLOCKS.LIST_ITEM]: (node, children) => (
|
109
|
-
<ListItem>
|
110
|
-
<ListItemIcon>
|
111
|
-
<CheckCircleIcon />
|
112
|
-
</ListItemIcon>
|
113
|
-
{children}
|
114
|
-
</ListItem>
|
115
|
-
),
|
116
|
-
[BLOCKS.HR]: () => <Divider />,
|
117
|
-
[BLOCKS.QUOTE]: (node, children) => (
|
118
|
-
<blockquote>{children}</blockquote>
|
119
|
-
),
|
120
|
-
[BLOCKS.EMBEDDED_ASSET]: (node) =>
|
121
|
-
imageRender(node, locale),
|
122
|
-
[BLOCKS.EMBEDDED_ENTRY]: () => null,
|
123
|
-
[INLINES.EMBEDDED_ENTRY]: (node) => {
|
124
|
-
const path = getFromSitemap(node);
|
125
|
-
|
126
|
-
return path ? (
|
127
|
-
<Box my={2}>
|
128
|
-
<Card component="aside">
|
129
|
-
<CardActionArea component={Link} to={path}>
|
130
|
-
<CardContent>
|
131
|
-
<Box p={2}>
|
132
|
-
<Typography
|
133
|
-
variant="body2"
|
134
|
-
component="h3"
|
135
|
-
gutterBottom
|
136
|
-
>
|
137
|
-
{get(
|
138
|
-
node,
|
139
|
-
`data.target.fields.title.${locale}`,
|
140
|
-
)}
|
141
|
-
</Typography>
|
142
|
-
<Typography component="small">
|
143
|
-
{get(
|
144
|
-
node,
|
145
|
-
`data.target.fields.description.${locale}`,
|
146
|
-
)}
|
147
|
-
</Typography>
|
148
|
-
</Box>
|
149
|
-
</CardContent>
|
150
|
-
</CardActionArea>
|
151
|
-
</Card>
|
152
|
-
</Box>
|
153
|
-
) : null;
|
154
|
-
},
|
155
|
-
},
|
156
|
-
});
|
157
|
-
};
|
158
|
-
|
159
|
-
const RichText = ({ json, locale }) => {
|
160
|
-
const data = useStaticQuery(graphql`
|
161
|
-
{
|
162
|
-
allSitePage {
|
163
|
-
nodes {
|
164
|
-
path
|
165
|
-
pageContext
|
166
|
-
}
|
167
|
-
}
|
168
|
-
}
|
169
|
-
`);
|
170
|
-
|
171
|
-
return (
|
172
|
-
<div>
|
173
|
-
{renderRichText(
|
174
|
-
json,
|
175
|
-
locale,
|
176
|
-
data.allSitePage.nodes.reduce((acc, next, i) => {
|
177
|
-
acc[get(next, 'pageContext.contentful_id', i)] =
|
178
|
-
next.path;
|
179
|
-
return acc;
|
180
|
-
}, {}),
|
181
|
-
)}
|
182
|
-
</div>
|
183
|
-
);
|
184
|
-
};
|
185
|
-
|
186
|
-
RichText.propTypes = {
|
187
|
-
// eslint-disable-next-line
|
188
|
-
json: PropTypes.object,
|
189
|
-
locale: PropTypes.string,
|
190
|
-
};
|
191
|
-
|
192
|
-
RichText.defaultProps = {
|
193
|
-
locale: 'en-CA',
|
194
|
-
};
|
195
|
-
|
196
|
-
export default RichText;
|
1
|
+
import React from 'react';
|
2
|
+
import { get } from 'lodash';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { useStaticQuery, graphql, Link } from 'gatsby';
|
5
|
+
import Card from '@material-ui/core/Card';
|
6
|
+
import CardContent from '@material-ui/core/CardContent';
|
7
|
+
import Box from '@material-ui/core/Box';
|
8
|
+
import Typography from '@material-ui/core/Typography';
|
9
|
+
import List from '@material-ui/core/List';
|
10
|
+
import ListItem from '@material-ui/core/ListItem';
|
11
|
+
import CardActionArea from '@material-ui/core/CardActionArea';
|
12
|
+
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
13
|
+
import Divider from '@material-ui/core/Divider';
|
14
|
+
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
15
|
+
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
|
16
|
+
import {
|
17
|
+
MARKS,
|
18
|
+
BLOCKS,
|
19
|
+
INLINES,
|
20
|
+
} from '@contentful/rich-text-types';
|
21
|
+
|
22
|
+
const isImage = (v) => v === 'image';
|
23
|
+
|
24
|
+
const getGroup = (v) =>
|
25
|
+
typeof v === 'string' ? v.split('/')[0] : '';
|
26
|
+
|
27
|
+
const imageRender = (node, locale) => {
|
28
|
+
const { title, description, file } = get(
|
29
|
+
node,
|
30
|
+
'data.target.fields',
|
31
|
+
{},
|
32
|
+
);
|
33
|
+
|
34
|
+
if (!file || !(locale in file)) return null;
|
35
|
+
|
36
|
+
const mimeType = file[locale].contentType;
|
37
|
+
const mimeGroup = getGroup(mimeType);
|
38
|
+
|
39
|
+
return isImage(mimeGroup) ? (
|
40
|
+
<img
|
41
|
+
title={title ? title[locale] : null}
|
42
|
+
alt={description ? description[locale] : null}
|
43
|
+
src={file[locale].url}
|
44
|
+
/>
|
45
|
+
) : null;
|
46
|
+
};
|
47
|
+
|
48
|
+
export const renderRichText = (
|
49
|
+
json,
|
50
|
+
locale = 'en-CA',
|
51
|
+
sitemap = {},
|
52
|
+
) => {
|
53
|
+
if (!json || !Object.keys(json).length) return null;
|
54
|
+
|
55
|
+
const getFromSitemap = (node) =>
|
56
|
+
get(
|
57
|
+
sitemap,
|
58
|
+
get(node, 'data.target.sys.contentful_id'),
|
59
|
+
);
|
60
|
+
|
61
|
+
return documentToReactComponents(json, {
|
62
|
+
renderMark: {
|
63
|
+
[MARKS.BOLD]: (text) => <strong>{text}</strong>,
|
64
|
+
[MARKS.ITALIC]: (text) => <i>{text}</i>,
|
65
|
+
[MARKS.UNDERLINE]: (text) => <u>{text}</u>,
|
66
|
+
[MARKS.CODE]: (text) => <code>{text}</code>,
|
67
|
+
},
|
68
|
+
renderNode: {
|
69
|
+
[BLOCKS.PARAGRAPH]: (node, children) => (
|
70
|
+
<Typography component="p">{children}</Typography>
|
71
|
+
),
|
72
|
+
[BLOCKS.HEADING_1]: (node, children) => (
|
73
|
+
<Typography variant="h1" gutterBottom>
|
74
|
+
{children}
|
75
|
+
</Typography>
|
76
|
+
),
|
77
|
+
[BLOCKS.HEADING_2]: (node, children) => (
|
78
|
+
<Typography variant="h2" gutterBottom>
|
79
|
+
{children}
|
80
|
+
</Typography>
|
81
|
+
),
|
82
|
+
[BLOCKS.HEADING_3]: (node, children) => (
|
83
|
+
<Typography variant="h3" gutterBottom>
|
84
|
+
{children}
|
85
|
+
</Typography>
|
86
|
+
),
|
87
|
+
[BLOCKS.HEADING_4]: (node, children) => (
|
88
|
+
<Typography variant="h4" gutterBottom>
|
89
|
+
{children}
|
90
|
+
</Typography>
|
91
|
+
),
|
92
|
+
[BLOCKS.HEADING_5]: (node, children) => (
|
93
|
+
<Typography variant="h5" gutterBottom>
|
94
|
+
{children}
|
95
|
+
</Typography>
|
96
|
+
),
|
97
|
+
[BLOCKS.HEADING_6]: (node, children) => (
|
98
|
+
<Typography variant="h6" gutterBottom>
|
99
|
+
{children}
|
100
|
+
</Typography>
|
101
|
+
),
|
102
|
+
[BLOCKS.OL_LIST]: (node, children) => (
|
103
|
+
<List component="ol">{children}</List>
|
104
|
+
),
|
105
|
+
[BLOCKS.UL_LIST]: (node, children) => (
|
106
|
+
<List component="ul">{children}</List>
|
107
|
+
),
|
108
|
+
[BLOCKS.LIST_ITEM]: (node, children) => (
|
109
|
+
<ListItem>
|
110
|
+
<ListItemIcon>
|
111
|
+
<CheckCircleIcon />
|
112
|
+
</ListItemIcon>
|
113
|
+
{children}
|
114
|
+
</ListItem>
|
115
|
+
),
|
116
|
+
[BLOCKS.HR]: () => <Divider />,
|
117
|
+
[BLOCKS.QUOTE]: (node, children) => (
|
118
|
+
<blockquote>{children}</blockquote>
|
119
|
+
),
|
120
|
+
[BLOCKS.EMBEDDED_ASSET]: (node) =>
|
121
|
+
imageRender(node, locale),
|
122
|
+
[BLOCKS.EMBEDDED_ENTRY]: () => null,
|
123
|
+
[INLINES.EMBEDDED_ENTRY]: (node) => {
|
124
|
+
const path = getFromSitemap(node);
|
125
|
+
|
126
|
+
return path ? (
|
127
|
+
<Box my={2}>
|
128
|
+
<Card component="aside">
|
129
|
+
<CardActionArea component={Link} to={path}>
|
130
|
+
<CardContent>
|
131
|
+
<Box p={2}>
|
132
|
+
<Typography
|
133
|
+
variant="body2"
|
134
|
+
component="h3"
|
135
|
+
gutterBottom
|
136
|
+
>
|
137
|
+
{get(
|
138
|
+
node,
|
139
|
+
`data.target.fields.title.${locale}`,
|
140
|
+
)}
|
141
|
+
</Typography>
|
142
|
+
<Typography component="small">
|
143
|
+
{get(
|
144
|
+
node,
|
145
|
+
`data.target.fields.description.${locale}`,
|
146
|
+
)}
|
147
|
+
</Typography>
|
148
|
+
</Box>
|
149
|
+
</CardContent>
|
150
|
+
</CardActionArea>
|
151
|
+
</Card>
|
152
|
+
</Box>
|
153
|
+
) : null;
|
154
|
+
},
|
155
|
+
},
|
156
|
+
});
|
157
|
+
};
|
158
|
+
|
159
|
+
const RichText = ({ json, locale }) => {
|
160
|
+
const data = useStaticQuery(graphql`
|
161
|
+
{
|
162
|
+
allSitePage {
|
163
|
+
nodes {
|
164
|
+
path
|
165
|
+
pageContext
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
`);
|
170
|
+
|
171
|
+
return (
|
172
|
+
<div>
|
173
|
+
{renderRichText(
|
174
|
+
json,
|
175
|
+
locale,
|
176
|
+
data.allSitePage.nodes.reduce((acc, next, i) => {
|
177
|
+
acc[get(next, 'pageContext.contentful_id', i)] =
|
178
|
+
next.path;
|
179
|
+
return acc;
|
180
|
+
}, {}),
|
181
|
+
)}
|
182
|
+
</div>
|
183
|
+
);
|
184
|
+
};
|
185
|
+
|
186
|
+
RichText.propTypes = {
|
187
|
+
// eslint-disable-next-line
|
188
|
+
json: PropTypes.object,
|
189
|
+
locale: PropTypes.string,
|
190
|
+
};
|
191
|
+
|
192
|
+
RichText.defaultProps = {
|
193
|
+
locale: 'en-CA',
|
194
|
+
};
|
195
|
+
|
196
|
+
export default RichText;
|
@@ -1,124 +1,124 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { get, isFunction, isObject } from 'lodash';
|
3
|
-
import PropTypes from 'prop-types';
|
4
|
-
import { Helmet } from 'react-helmet';
|
5
|
-
import { browser } from 'q3-ui-helpers';
|
6
|
-
import useSiteMetaData from './useSiteMetaData';
|
7
|
-
|
8
|
-
const withContent = (output) => (content) =>
|
9
|
-
content && isFunction(output) ? output(content) : [];
|
10
|
-
|
11
|
-
export const getStartUrl = () =>
|
12
|
-
browser.isBrowserReady()
|
13
|
-
? get(window, 'location.host')
|
14
|
-
: '';
|
15
|
-
|
16
|
-
export const generateMetaDescriptionOptions = withContent(
|
17
|
-
(content) => [
|
18
|
-
{
|
19
|
-
name: 'description',
|
20
|
-
content,
|
21
|
-
},
|
22
|
-
{
|
23
|
-
property: 'og:description',
|
24
|
-
content,
|
25
|
-
},
|
26
|
-
{
|
27
|
-
name: 'twitter:description',
|
28
|
-
content,
|
29
|
-
},
|
30
|
-
],
|
31
|
-
);
|
32
|
-
|
33
|
-
export const generateMetaTitleOptions = withContent(
|
34
|
-
(content) => [
|
35
|
-
{
|
36
|
-
property: 'og:title',
|
37
|
-
content,
|
38
|
-
},
|
39
|
-
{
|
40
|
-
name: 'twitter:title',
|
41
|
-
content,
|
42
|
-
},
|
43
|
-
],
|
44
|
-
);
|
45
|
-
|
46
|
-
export const generateBrand = (xs) =>
|
47
|
-
xs ? `%s | ${xs}` : undefined;
|
48
|
-
|
49
|
-
export const generateIcons = (site = {}) =>
|
50
|
-
site?.favicon
|
51
|
-
? [
|
52
|
-
{
|
53
|
-
src: site.favicon,
|
54
|
-
sizes: '512x512',
|
55
|
-
type: 'image/png',
|
56
|
-
},
|
57
|
-
]
|
58
|
-
: [];
|
59
|
-
|
60
|
-
export const generateManifest = (site = {}) => ({
|
61
|
-
background_color: site.color,
|
62
|
-
description: site.description,
|
63
|
-
display: 'fullscreen',
|
64
|
-
icons: generateIcons(site),
|
65
|
-
name: site.title,
|
66
|
-
start_url: getStartUrl(),
|
67
|
-
short_name: site.brand,
|
68
|
-
theme_color: site.color,
|
69
|
-
});
|
70
|
-
|
71
|
-
const SEO = ({ description, lang, meta, title }) => {
|
72
|
-
const site = useSiteMetaData();
|
73
|
-
const metaDescription = description || site.description;
|
74
|
-
const metaTitle = title || site.title;
|
75
|
-
const manifestData = generateManifest(site);
|
76
|
-
|
77
|
-
return (
|
78
|
-
<Helmet
|
79
|
-
htmlAttributes={{
|
80
|
-
lang,
|
81
|
-
}}
|
82
|
-
title={metaTitle}
|
83
|
-
titleTemplate={generateBrand(site.brand)}
|
84
|
-
meta={[
|
85
|
-
...generateMetaTitleOptions(metaTitle),
|
86
|
-
...generateMetaDescriptionOptions(metaDescription),
|
87
|
-
{
|
88
|
-
property: 'og:type',
|
89
|
-
content: 'website',
|
90
|
-
},
|
91
|
-
{
|
92
|
-
name: 'twitter:card',
|
93
|
-
content: 'summary',
|
94
|
-
},
|
95
|
-
].concat(meta)}
|
96
|
-
>
|
97
|
-
{isObject(manifestData) ? (
|
98
|
-
<link
|
99
|
-
rel="manifest"
|
100
|
-
href={`data:application/manifest+json,${encodeURIComponent(
|
101
|
-
JSON.stringify(manifestData),
|
102
|
-
)}`}
|
103
|
-
/>
|
104
|
-
) : null}
|
105
|
-
<link rel="icon" href={site.favicon} />
|
106
|
-
</Helmet>
|
107
|
-
);
|
108
|
-
};
|
109
|
-
|
110
|
-
SEO.defaultProps = {
|
111
|
-
lang: 'en',
|
112
|
-
meta: [],
|
113
|
-
description: '',
|
114
|
-
title: '',
|
115
|
-
};
|
116
|
-
|
117
|
-
SEO.propTypes = {
|
118
|
-
description: PropTypes.string,
|
119
|
-
lang: PropTypes.string,
|
120
|
-
meta: PropTypes.arrayOf(PropTypes.object),
|
121
|
-
title: PropTypes.string,
|
122
|
-
};
|
123
|
-
|
124
|
-
export default SEO;
|
1
|
+
import React from 'react';
|
2
|
+
import { get, isFunction, isObject } from 'lodash';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { Helmet } from 'react-helmet';
|
5
|
+
import { browser } from 'q3-ui-helpers';
|
6
|
+
import useSiteMetaData from './useSiteMetaData';
|
7
|
+
|
8
|
+
const withContent = (output) => (content) =>
|
9
|
+
content && isFunction(output) ? output(content) : [];
|
10
|
+
|
11
|
+
export const getStartUrl = () =>
|
12
|
+
browser.isBrowserReady()
|
13
|
+
? get(window, 'location.host')
|
14
|
+
: '';
|
15
|
+
|
16
|
+
export const generateMetaDescriptionOptions = withContent(
|
17
|
+
(content) => [
|
18
|
+
{
|
19
|
+
name: 'description',
|
20
|
+
content,
|
21
|
+
},
|
22
|
+
{
|
23
|
+
property: 'og:description',
|
24
|
+
content,
|
25
|
+
},
|
26
|
+
{
|
27
|
+
name: 'twitter:description',
|
28
|
+
content,
|
29
|
+
},
|
30
|
+
],
|
31
|
+
);
|
32
|
+
|
33
|
+
export const generateMetaTitleOptions = withContent(
|
34
|
+
(content) => [
|
35
|
+
{
|
36
|
+
property: 'og:title',
|
37
|
+
content,
|
38
|
+
},
|
39
|
+
{
|
40
|
+
name: 'twitter:title',
|
41
|
+
content,
|
42
|
+
},
|
43
|
+
],
|
44
|
+
);
|
45
|
+
|
46
|
+
export const generateBrand = (xs) =>
|
47
|
+
xs ? `%s | ${xs}` : undefined;
|
48
|
+
|
49
|
+
export const generateIcons = (site = {}) =>
|
50
|
+
site?.favicon
|
51
|
+
? [
|
52
|
+
{
|
53
|
+
src: site.favicon,
|
54
|
+
sizes: '512x512',
|
55
|
+
type: 'image/png',
|
56
|
+
},
|
57
|
+
]
|
58
|
+
: [];
|
59
|
+
|
60
|
+
export const generateManifest = (site = {}) => ({
|
61
|
+
background_color: site.color,
|
62
|
+
description: site.description,
|
63
|
+
display: 'fullscreen',
|
64
|
+
icons: generateIcons(site),
|
65
|
+
name: site.title,
|
66
|
+
start_url: getStartUrl(),
|
67
|
+
short_name: site.brand,
|
68
|
+
theme_color: site.color,
|
69
|
+
});
|
70
|
+
|
71
|
+
const SEO = ({ description, lang, meta, title }) => {
|
72
|
+
const site = useSiteMetaData();
|
73
|
+
const metaDescription = description || site.description;
|
74
|
+
const metaTitle = title || site.title;
|
75
|
+
const manifestData = generateManifest(site);
|
76
|
+
|
77
|
+
return (
|
78
|
+
<Helmet
|
79
|
+
htmlAttributes={{
|
80
|
+
lang,
|
81
|
+
}}
|
82
|
+
title={metaTitle}
|
83
|
+
titleTemplate={generateBrand(site.brand)}
|
84
|
+
meta={[
|
85
|
+
...generateMetaTitleOptions(metaTitle),
|
86
|
+
...generateMetaDescriptionOptions(metaDescription),
|
87
|
+
{
|
88
|
+
property: 'og:type',
|
89
|
+
content: 'website',
|
90
|
+
},
|
91
|
+
{
|
92
|
+
name: 'twitter:card',
|
93
|
+
content: 'summary',
|
94
|
+
},
|
95
|
+
].concat(meta)}
|
96
|
+
>
|
97
|
+
{isObject(manifestData) ? (
|
98
|
+
<link
|
99
|
+
rel="manifest"
|
100
|
+
href={`data:application/manifest+json,${encodeURIComponent(
|
101
|
+
JSON.stringify(manifestData),
|
102
|
+
)}`}
|
103
|
+
/>
|
104
|
+
) : null}
|
105
|
+
<link rel="icon" href={site.favicon} />
|
106
|
+
</Helmet>
|
107
|
+
);
|
108
|
+
};
|
109
|
+
|
110
|
+
SEO.defaultProps = {
|
111
|
+
lang: 'en',
|
112
|
+
meta: [],
|
113
|
+
description: '',
|
114
|
+
title: '',
|
115
|
+
};
|
116
|
+
|
117
|
+
SEO.propTypes = {
|
118
|
+
description: PropTypes.string,
|
119
|
+
lang: PropTypes.string,
|
120
|
+
meta: PropTypes.arrayOf(PropTypes.object),
|
121
|
+
title: PropTypes.string,
|
122
|
+
};
|
123
|
+
|
124
|
+
export default SEO;
|