@quintype/seo 1.40.9 → 1.40.11
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/CHANGELOG.md +17 -0
- package/dist/index.cjs.js +454 -450
- package/index.js +14 -14
- package/package.json +1 -1
- package/src/structured-data/schema.js +3 -1
- package/src/structured-data/structured-data-tags.js +5 -1
- package/test/structured_data_tags_test.js +9 -7
package/dist/index.cjs.js
CHANGED
|
@@ -5,12 +5,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var lodash = require('lodash');
|
|
6
6
|
var React = require('react');
|
|
7
7
|
var ReactDomServer = require('react-dom/server');
|
|
8
|
+
var get = require('lodash/get');
|
|
8
9
|
var url = require('url');
|
|
9
10
|
var dateFnsTz = require('date-fns-tz');
|
|
10
|
-
var quintypeJs = require('quintype-js');
|
|
11
|
-
var get = require('lodash/get');
|
|
12
11
|
var isUndefined = require('lodash/isUndefined');
|
|
13
12
|
var omitBy = require('lodash/omitBy');
|
|
13
|
+
var quintypeJs = require('quintype-js');
|
|
14
14
|
|
|
15
15
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
16
16
|
|
|
@@ -49,375 +49,185 @@ function isStoryPublic(story) {
|
|
|
49
49
|
return story.access === undefined || story.access === null || story.access === 'public';
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function
|
|
53
|
-
if (!story)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const { metadata = {} } = story.cards.find(card => card.id === cardId) || {};
|
|
57
|
-
const urlWithCardId = `${config["sketches-host"]}/${story.slug}?cardId=${cardId}`;
|
|
52
|
+
function showAmpTag({ ampStoryPages = true }, pageType, story) {
|
|
53
|
+
if (!ampStoryPages || pageType !== 'story-page') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
title: metadata["social-share"].title || story.headline,
|
|
62
|
-
description: metadata["social-share"].message || story.summary,
|
|
63
|
-
ogUrl: urlWithCardId,
|
|
64
|
-
ogTitle: metadata["social-share"].title || story.headline,
|
|
65
|
-
ogDescription: metadata["social-share"].message || story.summary
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return metadata;
|
|
57
|
+
if (ampStoryPages === 'public' && !isStoryPublic(story)) {
|
|
58
|
+
return false;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
const getDomain = (url, domainSlug) => {
|
|
65
|
+
const domain = domainSlug ? new URL(url).origin : '';
|
|
66
|
+
try {
|
|
67
|
+
return domain;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"page-title": pageTitle,
|
|
87
|
-
description,
|
|
88
|
-
keywords,
|
|
89
|
-
canonicalUrl: story["canonical-url"] || storyUrl,
|
|
90
|
-
ogUrl,
|
|
91
|
-
ogTitle: getOgTitle,
|
|
92
|
-
ogDescription,
|
|
93
|
-
storyUrl,
|
|
94
|
-
author: authors
|
|
95
|
-
};
|
|
73
|
+
/**
|
|
74
|
+
* StoryAmpTags adds the amphref to stories which support amp.
|
|
75
|
+
*
|
|
76
|
+
* @extends Generator
|
|
77
|
+
* @param {*} seoConfig
|
|
78
|
+
* @param {(boolean|"public")} seoConfig.ampStoryPages Should amp story pages be shown for all stories (true), not shown (false), or only be shown for public stories ("public"). Default: true
|
|
79
|
+
* @param {(boolean)} seoConfig.appendHostToAmpUrl If set to true, the url to be appended to the slug is computed based on the currentHostUrl and the domain slug, else the url is taken as the sketches host. Default: false
|
|
80
|
+
* @param {(boolean)} seoConfig.decodeAmpUrl If set to true, the storySlug that goes as the amp href url is decoded, else the storyslug is encoded. Default: false
|
|
81
|
+
* @param {...*} params See {@link Generator} for other Parameters
|
|
82
|
+
*/
|
|
83
|
+
function StoryAmpTags(seoConfig, config, pageType, data = {}, opts) {
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
const story = get__default["default"](data, ["data", "story"], {});
|
|
86
|
+
const { currentHostUrl = '', domainSlug } = data;
|
|
87
|
+
// TODO: Remove this condition and always make absolute URL if that's better for AMP discoverability.
|
|
88
|
+
const ampUrlAppend = seoConfig.appendHostToAmpUrl ? getDomain(currentHostUrl, domainSlug) || config['sketches-host'] : '';
|
|
89
|
+
const storySlug = seoConfig.decodeAmpUrl ? decodeURIComponent(story.slug) : encodeURIComponent(story.slug);
|
|
90
|
+
const ampUrl = story["story-template"] === "visual-story" ? `${ampUrlAppend}/${storySlug}` : `${ampUrlAppend}/amp/story/${storySlug}`;
|
|
91
|
+
|
|
92
|
+
if (showAmpTag(seoConfig, pageType, story)) {
|
|
93
|
+
return [{
|
|
94
|
+
tag: 'link',
|
|
95
|
+
rel: 'amphtml',
|
|
96
|
+
href: ampUrl
|
|
97
|
+
}];
|
|
98
|
+
} else {
|
|
99
|
+
return [];
|
|
100
100
|
}
|
|
101
|
+
}
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
/**
|
|
104
|
+
* AuthorTags adds the twitter:creator tag for story pages
|
|
105
|
+
*
|
|
106
|
+
* @extends Generator
|
|
107
|
+
* @param {*} seoConfig
|
|
108
|
+
* @param {...*} params See {@link Generator} for other Parameters
|
|
109
|
+
*/
|
|
110
|
+
function AuthorTags(seoConfig, config, pageType, data, { url }) {
|
|
111
|
+
if (pageType != "story-page" || pageType != "story-page-amp") return [];
|
|
112
|
+
|
|
113
|
+
return [{
|
|
114
|
+
name: "twitter:creator",
|
|
115
|
+
content: lodash.get(data, ["data", "story", "author-name"])
|
|
116
|
+
}];
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
function getTitle$1(config) {
|
|
120
|
+
return config["publisher-settings"] ? config["publisher-settings"]["title"] : config["publisher-name"];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function generateStaticData(config) {
|
|
124
|
+
const title = getTitle$1(config);
|
|
125
|
+
const themeConfig = config["theme-attributes"] || {};
|
|
126
|
+
const publicIntegrations = get__default["default"](config, ['public-integrations'], {});
|
|
127
|
+
const staticData = {
|
|
128
|
+
"twitter:site": title,
|
|
129
|
+
"twitter:domain": config["sketches-host"],
|
|
130
|
+
"twitter:app:name:ipad": themeConfig["twitter_app_name_ipad"],
|
|
131
|
+
"twitter:app:name:googleplay": themeConfig["twitter_app_name_googleplay"],
|
|
132
|
+
"twitter:app:id:googleplay": themeConfig["twitter_app_id_googleplay"],
|
|
133
|
+
"twitter:app:name:iphone": themeConfig["twitter_app_name_iphone"],
|
|
134
|
+
"twitter:app:id:iphone": themeConfig["twitter_app_id_iphone"],
|
|
135
|
+
"apple-itunes-app": themeConfig["apple_itunes_app"],
|
|
136
|
+
"google-play-app": themeConfig["google_play_app"],
|
|
137
|
+
"fb:app_id": get__default["default"](publicIntegrations, ['facebook', 'app-id']) || get__default["default"](themeConfig, ["fb_app_id"]),
|
|
138
|
+
"fb:pages": themeConfig["fb_pages"],
|
|
139
|
+
"og:site_name": title
|
|
126
140
|
};
|
|
127
141
|
|
|
128
|
-
return
|
|
142
|
+
return omitBy__default["default"](staticData, isUndefined__default["default"]);
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
function
|
|
132
|
-
|
|
145
|
+
function generateImageObject(config = {}) {
|
|
146
|
+
const { "theme-attributes": themeConfig = {} } = config;
|
|
147
|
+
return {
|
|
148
|
+
"@context": "http://schema.org",
|
|
149
|
+
"@type": "ImageObject",
|
|
150
|
+
"author": config['publisher-name'],
|
|
151
|
+
"contentUrl": themeConfig.logo,
|
|
152
|
+
"url": themeConfig.logo,
|
|
153
|
+
"name": "logo",
|
|
154
|
+
"width": themeConfig.logo && getQueryParams(themeConfig.logo).width,
|
|
155
|
+
"height": themeConfig.logo && getQueryParams(themeConfig.logo).height
|
|
156
|
+
};
|
|
157
|
+
}
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
const title =
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
function generateStructuredData(config = {}) {
|
|
160
|
+
const title = getTitle$1(config);
|
|
161
|
+
const { "theme-attributes": themeConfig, "social-links": socialLinks, "seo-metadata": seoMetadata = [] } = config;
|
|
162
|
+
const homePageSeo = seoMetadata.find(page => page["owner-type"] === "home") || {};
|
|
163
|
+
const { "page-title": pageTitle = "", description = "", keywords = "" } = get__default["default"](homePageSeo, ["data"], {});
|
|
164
|
+
if (!themeConfig || !themeConfig.logo) {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
let enableStructuredDataForNewsArticle = themeConfig['structured_data_news_article'] || false;
|
|
168
|
+
if (config.hasOwnProperty('enableStructuredDataForNewsArticle') && typeof config.enableStructuredDataForNewsArticle !== "undefined") {
|
|
169
|
+
enableStructuredDataForNewsArticle = config.enableStructuredDataForNewsArticle;
|
|
170
|
+
}
|
|
141
171
|
|
|
142
172
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
173
|
+
organization: {
|
|
174
|
+
name: title,
|
|
175
|
+
url: config["sketches-host"],
|
|
176
|
+
logo: generateImageObject(config),
|
|
177
|
+
sameAs: socialLinks ? Object.values(socialLinks) : []
|
|
178
|
+
},
|
|
179
|
+
enableNewsArticle: !!enableStructuredDataForNewsArticle,
|
|
180
|
+
storyUrlAsMainEntityUrl: !!enableStructuredDataForNewsArticle,
|
|
181
|
+
enableVideo: !themeConfig['structured_data_enable_video'],
|
|
182
|
+
enableLiveBlog: !themeConfig['structured_data_enable_live_blog'],
|
|
183
|
+
website: {
|
|
184
|
+
url: config["sketches-host"],
|
|
185
|
+
searchpath: "search?q={query}",
|
|
186
|
+
queryinput: "required name=query",
|
|
187
|
+
name: pageTitle || title,
|
|
188
|
+
headline: description,
|
|
189
|
+
keywords
|
|
190
|
+
}
|
|
151
191
|
};
|
|
152
192
|
}
|
|
153
193
|
|
|
154
|
-
function
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
|
|
194
|
+
function pickImageFromCard(story, cardId) {
|
|
195
|
+
const { metadata = {} } = story.cards.find(card => card.id === cardId) || {};
|
|
196
|
+
if (metadata && !lodash.isEmpty(metadata) && lodash.get(metadata, ["social-share", "image", "key"], false)) {
|
|
197
|
+
const alt = metadata["social-share"].image.attribution || metadata["social-share"].title || metadata["social-share"].message || getAttribution(story);
|
|
198
|
+
return {
|
|
199
|
+
image: new quintypeJs.FocusedImage(metadata["social-share"].image.key, metadata["social-share"].image.metadata || {}),
|
|
200
|
+
alt
|
|
201
|
+
};
|
|
158
202
|
}
|
|
159
|
-
return {};
|
|
160
203
|
}
|
|
161
204
|
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (lodash.isEmpty(seoData) && lodash.isEmpty(customSeo)) return;
|
|
205
|
+
function getAttribution(story) {
|
|
206
|
+
return story["hero-image-attribution"] || story.summary || lodash.get(story, ["alternative", "home", "default", "headline"]) || story.headline;
|
|
207
|
+
}
|
|
166
208
|
|
|
167
|
-
|
|
209
|
+
/**
|
|
210
|
+
* priority:
|
|
211
|
+
* 1. alternate social image
|
|
212
|
+
* 2. alternate hero image
|
|
213
|
+
* 3. hero image
|
|
214
|
+
* 4. "fallbackSocialImage" from seo config
|
|
215
|
+
* 5. logo_url from /api/v1/config > theme-attributes
|
|
216
|
+
* 5. logo from /api/v1/config > theme-attributes
|
|
217
|
+
* 6. undefined (meta tag won't get created)
|
|
218
|
+
*/
|
|
219
|
+
function pickImageFromStory({ story, config, seoConfig }) {
|
|
220
|
+
function getAlt(type, key, fallback) {
|
|
221
|
+
return lodash.get(story, ["alternative", `${type}`, "default", "hero-image", `${key}`], fallback);
|
|
222
|
+
}
|
|
168
223
|
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
title,
|
|
178
|
-
"page-title": pageTitle,
|
|
179
|
-
description,
|
|
180
|
-
keywords: `${title},${config["publisher-name"]}`,
|
|
181
|
-
canonicalUrl: staticPageUrl,
|
|
182
|
-
ogUrl: staticPageUrl,
|
|
183
|
-
ogTitle,
|
|
184
|
-
ogDescription,
|
|
185
|
-
keywords: customSeo.keywords || keywords
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// The findRelevantConfig method call has no ownerId for home page.
|
|
190
|
-
// This causes the seoMetadata to be undefined.
|
|
191
|
-
// So the default value for the ownerId is set to null.
|
|
192
|
-
|
|
193
|
-
function getSeoData(config, pageType, data, url = {}, seoConfig = {}) {
|
|
194
|
-
function findRelevantConfig(ownerType, ownerId = null) {
|
|
195
|
-
const seoMetadata = config["seo-metadata"].find(page => page["owner-type"] === ownerType && page["owner-id"] === ownerId) || {};
|
|
196
|
-
const { sections = [] } = config;
|
|
197
|
-
const section = sections.find(section => ownerType == "section" && section.id === ownerId) || {};
|
|
198
|
-
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
199
|
-
if (seoMetadata.data || section.id || !lodash.isEmpty(customSeo)) {
|
|
200
|
-
const result = Object.assign({}, {
|
|
201
|
-
"page-title": customSeo["page-title"] || section.name,
|
|
202
|
-
title: customSeo.title || section.name,
|
|
203
|
-
canonicalUrl: customSeo["canonicalUrl"] || section["section-url"] || undefined
|
|
204
|
-
}, seoMetadata.data);
|
|
205
|
-
|
|
206
|
-
if (!result.description) {
|
|
207
|
-
const homeSeoData = config["seo-metadata"].find(page => page["owner-type"] === "home") || {
|
|
208
|
-
data: { description: "" }
|
|
209
|
-
};
|
|
210
|
-
result.description = customSeo.description || homeSeoData.data.description;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
result.ogTitle = customSeo.ogTitle || result.title;
|
|
214
|
-
result.ogDescription = customSeo.ogDescription || result.description;
|
|
215
|
-
return result;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (seoConfig.customTags && seoConfig.customTags[pageType]) {
|
|
220
|
-
return buildCustomTags(seoConfig.customTags, pageType);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function getShellSeoData(config) {
|
|
224
|
-
const seoMetadata = config["seo-metadata"].find(meta => meta["owner-type"] === "home") || {};
|
|
225
|
-
return Object.assign({}, seoMetadata.data, { canonicalUrl: SKIP_CANONICAL });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
switch (pageType) {
|
|
229
|
-
case "home-page":
|
|
230
|
-
return findRelevantConfig("home");
|
|
231
|
-
case "section-page":
|
|
232
|
-
return findRelevantConfig("section", lodash.get(data, ["data", "section", "id"])) || getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url);
|
|
233
|
-
case "collection-page":
|
|
234
|
-
return getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url);
|
|
235
|
-
case "tag-page":
|
|
236
|
-
return buildTagsFromTopic(config, lodash.get(data, ["data", "tag"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
237
|
-
case "story-page":
|
|
238
|
-
return buildTagsFromStory(config, lodash.get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
239
|
-
case "visual-story":
|
|
240
|
-
return buildTagsFromStory(config, lodash.get(data, ["story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
241
|
-
case "story-page-amp":
|
|
242
|
-
return buildTagsFromStory(config, lodash.get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
243
|
-
case "author-page":
|
|
244
|
-
return buildTagsFromAuthor(config, lodash.get(data, ["data", "author"], {}), url, data) || getSeoData(config, "home-page", data, url);
|
|
245
|
-
case "static-page":
|
|
246
|
-
return buildTagsFromStaticPage(config, lodash.get(data, ["data", "page"], {}), url, data) || getSeoData(config, "home-page", data, url);
|
|
247
|
-
case "shell":
|
|
248
|
-
return getShellSeoData(config);
|
|
249
|
-
default:
|
|
250
|
-
return getSeoData(config, "home-page", data, url);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function getSeoDataFromCollection(config, data) {
|
|
255
|
-
if (lodash.get(data, ["data", "collection", "name"])) {
|
|
256
|
-
let { name, summary } = lodash.get(data, ["data", "collection"]);
|
|
257
|
-
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
258
|
-
|
|
259
|
-
if (!summary) {
|
|
260
|
-
summary = (getSeoData(config, "home-page", data) || {}).description;
|
|
261
|
-
}
|
|
262
|
-
const title = customSeo.title || name;
|
|
263
|
-
const pageTitle = customSeo["page-title"] || name;
|
|
264
|
-
const ogTitle = customSeo.ogTitle || title;
|
|
265
|
-
const description = customSeo.description || summary;
|
|
266
|
-
const ogDescription = customSeo.ogDescription || summary;
|
|
267
|
-
return {
|
|
268
|
-
"page-title": pageTitle,
|
|
269
|
-
title,
|
|
270
|
-
ogTitle,
|
|
271
|
-
description,
|
|
272
|
-
ogDescription,
|
|
273
|
-
canonicalUrl: SKIP_CANONICAL
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const SKIP_CANONICAL = "__SKIP__CANONICAL__";
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* TextTags adds the majority of basic tags, such as
|
|
282
|
-
* * Canonical URLs
|
|
283
|
-
* * Title and Description
|
|
284
|
-
* * Keywords
|
|
285
|
-
*
|
|
286
|
-
* If the current URL contains a cardId in the query parameters, then the title and description will come from *card["social-share"]*
|
|
287
|
-
*
|
|
288
|
-
* @extends Generator
|
|
289
|
-
* @param {*} seoConfig
|
|
290
|
-
* @param {boolean} seoConfig.enableOgTags Add og tags for Facebook
|
|
291
|
-
* @param {boolean} seoConfig.enableTwitterCards Add twitter tags
|
|
292
|
-
* @param {boolean} seoConfig.enableNews Add tags for Google News, like news_keywords
|
|
293
|
-
* @param {Object} seoConfig.customTags Add tags for a custom page type. Usually looks like `{"custom-page": {"title": "value", "canonicalUrl": "value"}}`
|
|
294
|
-
* @param {...*} params See {@link Generator} for other Parameters
|
|
295
|
-
*/
|
|
296
|
-
function TextTags(seoConfig, config, pageType, data, { url }) {
|
|
297
|
-
const seoData = getSeoData(config, pageType, data, url, seoConfig);
|
|
298
|
-
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
299
|
-
|
|
300
|
-
if (!seoData) return [];
|
|
301
|
-
|
|
302
|
-
const currentUrl = `${config["sketches-host"]}${url.pathname}`;
|
|
303
|
-
|
|
304
|
-
const basicTags = {
|
|
305
|
-
description: customSeo.description || seoData.description,
|
|
306
|
-
title: customSeo.title || seoData.title,
|
|
307
|
-
keywords: customSeo.keywords || seoData.keywords
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const ogUrl = customSeo.ogUrl || seoData.ogUrl || seoData.canonicalUrl || currentUrl;
|
|
311
|
-
const ogTags = seoConfig.enableOgTags ? {
|
|
312
|
-
"og:type": pageType === "story-page" || pageType === "story-page-amp" ? "article" : "website",
|
|
313
|
-
"og:url": ogUrl === SKIP_CANONICAL ? undefined : ogUrl,
|
|
314
|
-
"og:title": customSeo.ogTitle || seoData.ogTitle,
|
|
315
|
-
"og:description": customSeo.ogDescription || seoData.ogDescription
|
|
316
|
-
} : undefined;
|
|
317
|
-
|
|
318
|
-
const twitterTags = seoConfig.enableTwitterCards ? {
|
|
319
|
-
"twitter:card": "summary_large_image",
|
|
320
|
-
"twitter:title": customSeo.twitterTitle || seoData.ogTitle,
|
|
321
|
-
"twitter:description": customSeo.twitterDescription || seoData.ogDescription
|
|
322
|
-
} : undefined;
|
|
323
|
-
|
|
324
|
-
const allTags = Object.assign(basicTags, ogTags, twitterTags);
|
|
325
|
-
|
|
326
|
-
const commonTags = [{ tag: "title", children: customSeo.title || data.title || seoData["page-title"] }];
|
|
327
|
-
|
|
328
|
-
const canonical = seoData.canonicalUrl || currentUrl;
|
|
329
|
-
if (canonical != SKIP_CANONICAL) {
|
|
330
|
-
commonTags.push({ tag: "link", rel: "canonical", href: canonical });
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (pageType === "story-page" || pageType === "story-page-amp") {
|
|
334
|
-
commonTags.push({ name: "author", content: seoData.author });
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if ((pageType === "story-page" || pageType === "story-page-amp") && seoConfig.enableNews) {
|
|
338
|
-
commonTags.push({ name: "news_keywords", content: seoData.keywords });
|
|
339
|
-
if (lodash.get(data, ["data", "story", "seo", "meta-google-news-standout"])) commonTags.push({ tag: "link", rel: "standout", href: seoData.storyUrl || currentUrl });
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return commonTags.concat(objectToTags(allTags));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function getTitle$1(seoConfig, config, pageType, data, params) {
|
|
346
|
-
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
347
|
-
|
|
348
|
-
if (lodash.get(data, ["title"])) return customSeo.title || lodash.get(data, ["title"]);
|
|
349
|
-
|
|
350
|
-
if (lodash.get(data, ["data", "title"])) return customSeo.title || lodash.get(data, ["data", "title"]);
|
|
351
|
-
|
|
352
|
-
const seoData = getSeoData(config, pageType, data, undefined, seoConfig) || {};
|
|
353
|
-
return customSeo.title || seoData["page-title"];
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* StaticTags puts whatever tags you've passed to it
|
|
358
|
-
*
|
|
359
|
-
* @extends Generator
|
|
360
|
-
* @param {*} seoConfig
|
|
361
|
-
* @param {Object} seoConfig.staticTags List of tags to be added. ex: `{"viewport": "width=device-width,initial-scale=1.0"}`
|
|
362
|
-
* @param {...*} params See {@link Generator} for other Parameters
|
|
363
|
-
*/
|
|
364
|
-
function StaticTags(seoConfig, config, pageType, data, { url }) {
|
|
365
|
-
return objectToTags(seoConfig.staticTags || {});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* AuthorTags adds the twitter:creator tag for story pages
|
|
370
|
-
*
|
|
371
|
-
* @extends Generator
|
|
372
|
-
* @param {*} seoConfig
|
|
373
|
-
* @param {...*} params See {@link Generator} for other Parameters
|
|
374
|
-
*/
|
|
375
|
-
function AuthorTags(seoConfig, config, pageType, data, { url }) {
|
|
376
|
-
if (pageType != "story-page" || pageType != "story-page-amp") return [];
|
|
377
|
-
|
|
378
|
-
return [{
|
|
379
|
-
name: "twitter:creator",
|
|
380
|
-
content: lodash.get(data, ["data", "story", "author-name"])
|
|
381
|
-
}];
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function pickImageFromCard(story, cardId) {
|
|
385
|
-
const { metadata = {} } = story.cards.find(card => card.id === cardId) || {};
|
|
386
|
-
if (metadata && !lodash.isEmpty(metadata) && lodash.get(metadata, ["social-share", "image", "key"], false)) {
|
|
387
|
-
const alt = metadata["social-share"].image.attribution || metadata["social-share"].title || metadata["social-share"].message || getAttribution(story);
|
|
388
|
-
return {
|
|
389
|
-
image: new quintypeJs.FocusedImage(metadata["social-share"].image.key, metadata["social-share"].image.metadata || {}),
|
|
390
|
-
alt
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function getAttribution(story) {
|
|
396
|
-
return story["hero-image-attribution"] || story.summary || lodash.get(story, ["alternative", "home", "default", "headline"]) || story.headline;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* priority:
|
|
401
|
-
* 1. alternate social image
|
|
402
|
-
* 2. alternate hero image
|
|
403
|
-
* 3. hero image
|
|
404
|
-
* 4. "fallbackSocialImage" from seo config
|
|
405
|
-
* 5. logo_url from /api/v1/config > theme-attributes
|
|
406
|
-
* 5. logo from /api/v1/config > theme-attributes
|
|
407
|
-
* 6. undefined (meta tag won't get created)
|
|
408
|
-
*/
|
|
409
|
-
function pickImageFromStory({ story, config, seoConfig }) {
|
|
410
|
-
function getAlt(type, key, fallback) {
|
|
411
|
-
return lodash.get(story, ["alternative", `${type}`, "default", "hero-image", `${key}`], fallback);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const alt = getAttribution(story);
|
|
415
|
-
const fallbackSocialImage = lodash.get(seoConfig, ["fallbackSocialImage"]);
|
|
416
|
-
const altHeroImg = getAlt("home", "hero-image-s3-key", null);
|
|
417
|
-
const altSocialHeroImg = getAlt("social", "hero-image-s3-key", null);
|
|
418
|
-
const storyHeroImage = lodash.get(story, ["hero-image-s3-key"]);
|
|
419
|
-
const logo_url = lodash.get(config, ["theme-attributes", "logo_url"]);
|
|
420
|
-
const logo = lodash.get(config, ["theme-attributes", "logo"]);
|
|
224
|
+
const alt = getAttribution(story);
|
|
225
|
+
const fallbackSocialImage = lodash.get(seoConfig, ["fallbackSocialImage"]);
|
|
226
|
+
const altHeroImg = getAlt("home", "hero-image-s3-key", null);
|
|
227
|
+
const altSocialHeroImg = getAlt("social", "hero-image-s3-key", null);
|
|
228
|
+
const storyHeroImage = lodash.get(story, ["hero-image-s3-key"]);
|
|
229
|
+
const logo_url = lodash.get(config, ["theme-attributes", "logo_url"]);
|
|
230
|
+
const logo = lodash.get(config, ["theme-attributes", "logo"]);
|
|
421
231
|
|
|
422
232
|
if (altSocialHeroImg) {
|
|
423
233
|
const metadata = getAlt("social", "hero-image-metadata", {});
|
|
@@ -523,6 +333,18 @@ function ImageTags(seoConfig, config, pageType, data, { url = {} }) {
|
|
|
523
333
|
return tags;
|
|
524
334
|
}
|
|
525
335
|
|
|
336
|
+
/**
|
|
337
|
+
* StaticTags puts whatever tags you've passed to it
|
|
338
|
+
*
|
|
339
|
+
* @extends Generator
|
|
340
|
+
* @param {*} seoConfig
|
|
341
|
+
* @param {Object} seoConfig.staticTags List of tags to be added. ex: `{"viewport": "width=device-width,initial-scale=1.0"}`
|
|
342
|
+
* @param {...*} params See {@link Generator} for other Parameters
|
|
343
|
+
*/
|
|
344
|
+
function StaticTags(seoConfig, config, pageType, data, { url }) {
|
|
345
|
+
return objectToTags(seoConfig.staticTags || {});
|
|
346
|
+
}
|
|
347
|
+
|
|
526
348
|
const getSchemaContext = { "@context": "http://schema.org" };
|
|
527
349
|
|
|
528
350
|
function getSchemaType(type) {
|
|
@@ -548,10 +370,11 @@ function getSchemaHeader({ cssSelector }) {
|
|
|
548
370
|
}) : {};
|
|
549
371
|
}
|
|
550
372
|
|
|
551
|
-
function getSchemaBlogPosting(card = {}, author = {}, headline = "", image = "", structuredData = {}, story = {}, timezone) {
|
|
373
|
+
function getSchemaBlogPosting(card = {}, author = {}, headline = "", image = "", structuredData = {}, story = {}, timezone, articleBody) {
|
|
552
374
|
const { website: { url = "" } = {} } = structuredData;
|
|
553
375
|
const orgUrl = lodash.get(structuredData, ["organization", "url"], "");
|
|
554
376
|
return Object.assign({}, getSchemaType("BlogPosting"), getSchemaMainEntityOfPage(`${url}/${story.slug}`), getSchemaPublisher(structuredData.organization, orgUrl), {
|
|
377
|
+
articleBody: articleBody,
|
|
555
378
|
dateModified: stripMillisecondsFromTime(new Date(card["card-updated-at"]), timezone),
|
|
556
379
|
dateCreated: stripMillisecondsFromTime(new Date(card["card-added-at"]), timezone),
|
|
557
380
|
datePublished: stripMillisecondsFromTime(new Date(card["card-updated-at"]), timezone),
|
|
@@ -811,6 +634,8 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
|
|
|
811
634
|
const imageWidth = 1200;
|
|
812
635
|
const imageHeight = 675;
|
|
813
636
|
const authorSchema = structuredData.authorSchema && structuredData.authorSchema(story) || [];
|
|
637
|
+
const storyKeysPresence = Object.keys(story).length > 0;
|
|
638
|
+
const articleBody = storyKeysPresence && getCompleteText(story, structuredData.stripHtmlFromArticleBody) || "";
|
|
814
639
|
return {
|
|
815
640
|
headline: story.headline,
|
|
816
641
|
description: story.summary || story.headline,
|
|
@@ -818,7 +643,8 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
|
|
|
818
643
|
coverageEndTime: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
|
|
819
644
|
coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
|
|
820
645
|
dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
|
|
821
|
-
|
|
646
|
+
|
|
647
|
+
liveBlogUpdate: story.cards.map(card => getSchemaBlogPosting(card, authorData(story.authors, authorSchema, publisherConfig), findStoryElementField(card, "title", "text", story.headline), imageUrl(publisherConfig, findStoryElementField(card, "image", "image-s3-key", story["hero-image-s3-key"]), imageWidth, imageHeight), structuredData, story, timezone, articleBody))
|
|
822
648
|
};
|
|
823
649
|
}
|
|
824
650
|
|
|
@@ -1073,136 +899,314 @@ function StructuredDataTags({ structuredData = {} }, config, pageType, response
|
|
|
1073
899
|
return tags;
|
|
1074
900
|
}
|
|
1075
901
|
|
|
1076
|
-
function
|
|
1077
|
-
if (!
|
|
1078
|
-
return false;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
if (ampStoryPages === 'public' && !isStoryPublic(story)) {
|
|
1082
|
-
return false;
|
|
1083
|
-
}
|
|
902
|
+
function buildTagsFromStory(config, story, url = {}, data = {}) {
|
|
903
|
+
if (!story) return;
|
|
1084
904
|
|
|
1085
|
-
|
|
1086
|
-
}
|
|
905
|
+
function getStoryCardMetadata(cardId) {
|
|
906
|
+
const { metadata = {} } = story.cards.find(card => card.id === cardId) || {};
|
|
907
|
+
const urlWithCardId = `${config["sketches-host"]}/${story.slug}?cardId=${cardId}`;
|
|
1087
908
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
909
|
+
if (metadata && !lodash.isEmpty(metadata) && metadata["social-share"]) {
|
|
910
|
+
return {
|
|
911
|
+
title: metadata["social-share"].title || story.headline,
|
|
912
|
+
description: metadata["social-share"].message || story.summary,
|
|
913
|
+
ogUrl: urlWithCardId,
|
|
914
|
+
ogTitle: metadata["social-share"].title || story.headline,
|
|
915
|
+
ogDescription: metadata["social-share"].message || story.summary
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
return metadata;
|
|
1094
919
|
}
|
|
1095
|
-
};
|
|
1096
920
|
|
|
1097
|
-
|
|
1098
|
-
* StoryAmpTags adds the amphref to stories which support amp.
|
|
1099
|
-
*
|
|
1100
|
-
* @extends Generator
|
|
1101
|
-
* @param {*} seoConfig
|
|
1102
|
-
* @param {(boolean|"public")} seoConfig.ampStoryPages Should amp story pages be shown for all stories (true), not shown (false), or only be shown for public stories ("public"). Default: true
|
|
1103
|
-
* @param {(boolean)} seoConfig.appendHostToAmpUrl If set to true, the url to be appended to the slug is computed based on the currentHostUrl and the domain slug, else the url is taken as the sketches host. Default: false
|
|
1104
|
-
* @param {(boolean)} seoConfig.decodeAmpUrl If set to true, the storySlug that goes as the amp href url is decoded, else the storyslug is encoded. Default: false
|
|
1105
|
-
* @param {...*} params See {@link Generator} for other Parameters
|
|
1106
|
-
*/
|
|
1107
|
-
function StoryAmpTags(seoConfig, config, pageType, data = {}, opts) {
|
|
921
|
+
const seo = story.seo || {};
|
|
1108
922
|
|
|
1109
|
-
const
|
|
1110
|
-
const { currentHostUrl = '', domainSlug } = data;
|
|
1111
|
-
// TODO: Remove this condition and always make absolute URL if that's better for AMP discoverability.
|
|
1112
|
-
const ampUrlAppend = seoConfig.appendHostToAmpUrl ? getDomain(currentHostUrl, domainSlug) || config['sketches-host'] : '';
|
|
1113
|
-
const storySlug = seoConfig.decodeAmpUrl ? decodeURIComponent(story.slug) : encodeURIComponent(story.slug);
|
|
1114
|
-
const ampUrl = story["story-template"] === "visual-story" ? `${ampUrlAppend}/${storySlug}` : `${ampUrlAppend}/amp/story/${storySlug}`;
|
|
923
|
+
const storyUrl = story.url || `${config["sketches-host"]}/${story.slug}`;
|
|
1115
924
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
925
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
926
|
+
const authors = lodash.get(story, ["authors"], []).map(author => author.name);
|
|
927
|
+
const title = customSeo.title || seo["meta-title"] || story.headline;
|
|
928
|
+
const pageTitle = customSeo["page-title"] || seo["meta-title"] || story.headline;
|
|
929
|
+
const description = customSeo.description || seo["meta-description"] || story.summary;
|
|
930
|
+
const keywords = (customSeo.keywords || seo["meta-keywords"] || (story.tags || []).map(tag => tag.name)).join(",");
|
|
931
|
+
const ogUrl = customSeo.ogUrl || lodash.get(seo, ["og", "url"]) || storyUrl;
|
|
932
|
+
const getOgTitle = customSeo.ogTitle || lodash.get(story, ["alternative", "social", "default", "headline"], story.headline) || story.headline;
|
|
933
|
+
const ogDescription = customSeo.ogDescription || story.summary;
|
|
934
|
+
const storyMetaData = {
|
|
935
|
+
title,
|
|
936
|
+
"page-title": pageTitle,
|
|
937
|
+
description,
|
|
938
|
+
keywords,
|
|
939
|
+
canonicalUrl: story["canonical-url"] || storyUrl,
|
|
940
|
+
ogUrl,
|
|
941
|
+
ogTitle: getOgTitle,
|
|
942
|
+
ogDescription,
|
|
943
|
+
storyUrl,
|
|
944
|
+
author: authors
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
if (url.query && url.query.cardId) {
|
|
948
|
+
const storyCardMetadata = getStoryCardMetadata(url.query.cardId);
|
|
949
|
+
return Object.assign({}, storyMetaData, storyCardMetadata); //TODO rewrite in spread syntax, add babel plugin
|
|
1124
950
|
}
|
|
951
|
+
|
|
952
|
+
return storyMetaData;
|
|
1125
953
|
}
|
|
1126
954
|
|
|
1127
|
-
function
|
|
1128
|
-
|
|
955
|
+
function buildTagsFromTopic(config, tag, url = {}, data) {
|
|
956
|
+
if (lodash.isEmpty(tag)) return;
|
|
957
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
958
|
+
const tagName = customSeo.title || tag["meta-title"] || tag.name;
|
|
959
|
+
const pageTitle = customSeo["page-title"] || tagName;
|
|
960
|
+
const tagDescription = customSeo.description || tag["meta-description"];
|
|
961
|
+
const description = `Read stories listed under on ${tag.name}`;
|
|
962
|
+
const tagUrl = `${config["sketches-host"]}${url.pathname}`;
|
|
963
|
+
const canonicalSlug = tag["canonical-slug"] || url.pathname;
|
|
964
|
+
const canonicalUrl = `${config["sketches-host"]}${canonicalSlug}`;
|
|
965
|
+
const ogTitle = customSeo.ogTitle || tagName;
|
|
966
|
+
const ogDescription = customSeo.ogDescription || description;
|
|
967
|
+
const topicMetaData = {
|
|
968
|
+
title: tagName,
|
|
969
|
+
"page-title": pageTitle,
|
|
970
|
+
description: tagDescription || description,
|
|
971
|
+
keywords: tagName,
|
|
972
|
+
canonicalUrl,
|
|
973
|
+
ogUrl: tagUrl,
|
|
974
|
+
ogTitle,
|
|
975
|
+
ogDescription
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
return topicMetaData;
|
|
1129
979
|
}
|
|
1130
980
|
|
|
1131
|
-
function
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
"
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
981
|
+
function buildTagsFromAuthor(config, author, url = {}, data) {
|
|
982
|
+
if (lodash.isEmpty(author)) return;
|
|
983
|
+
|
|
984
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
985
|
+
const title = customSeo.title || author.name;
|
|
986
|
+
const pageTitle = customSeo["page-title"] || title;
|
|
987
|
+
const description = customSeo.description || author.bio || `View all articles written by ${title}`;
|
|
988
|
+
const ogTitle = customSeo.ogTitle || author.name;
|
|
989
|
+
const authorUrl = `${config["sketches-host"]}${url.pathname}`;
|
|
990
|
+
const ogDescription = customSeo.ogDescription || description;
|
|
991
|
+
|
|
992
|
+
return {
|
|
993
|
+
title,
|
|
994
|
+
"page-title": pageTitle,
|
|
995
|
+
description,
|
|
996
|
+
keywords: `${title},${config["publisher-name"]}`,
|
|
997
|
+
canonicalUrl: authorUrl,
|
|
998
|
+
ogUrl: authorUrl,
|
|
999
|
+
ogTitle,
|
|
1000
|
+
ogDescription
|
|
1148
1001
|
};
|
|
1002
|
+
}
|
|
1149
1003
|
|
|
1150
|
-
|
|
1004
|
+
function buildCustomTags(customTags = {}, pageType = "") {
|
|
1005
|
+
const configObject = customTags[pageType];
|
|
1006
|
+
if (configObject) {
|
|
1007
|
+
return configObject;
|
|
1008
|
+
}
|
|
1009
|
+
return {};
|
|
1151
1010
|
}
|
|
1152
1011
|
|
|
1153
|
-
function
|
|
1154
|
-
const
|
|
1012
|
+
function buildTagsFromStaticPage(config, page, url = {}, data) {
|
|
1013
|
+
const seoData = lodash.get(page, ["metadata", "seo"], {});
|
|
1014
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
1015
|
+
if (lodash.isEmpty(seoData) && lodash.isEmpty(customSeo)) return;
|
|
1016
|
+
|
|
1017
|
+
const { "meta-title": metaTitle, "meta-description": metaDescription, "meta-keywords": keywords } = seoData;
|
|
1018
|
+
|
|
1019
|
+
const title = customSeo.title || metaTitle || page.title;
|
|
1020
|
+
const pageTitle = customSeo["page-title"] || title;
|
|
1021
|
+
const description = customSeo.description || metaDescription;
|
|
1022
|
+
const ogTitle = customSeo.ogTitle || title;
|
|
1023
|
+
const staticPageUrl = `${config["sketches-host"]}${url.pathname}`;
|
|
1024
|
+
const ogDescription = customSeo.ogDescription || description;
|
|
1025
|
+
|
|
1155
1026
|
return {
|
|
1156
|
-
|
|
1157
|
-
"
|
|
1158
|
-
|
|
1159
|
-
"
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1027
|
+
title,
|
|
1028
|
+
"page-title": pageTitle,
|
|
1029
|
+
description,
|
|
1030
|
+
keywords: `${title},${config["publisher-name"]}`,
|
|
1031
|
+
canonicalUrl: staticPageUrl,
|
|
1032
|
+
ogUrl: staticPageUrl,
|
|
1033
|
+
ogTitle,
|
|
1034
|
+
ogDescription,
|
|
1035
|
+
keywords: customSeo.keywords || keywords
|
|
1164
1036
|
};
|
|
1165
1037
|
}
|
|
1166
1038
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1039
|
+
// The findRelevantConfig method call has no ownerId for home page.
|
|
1040
|
+
// This causes the seoMetadata to be undefined.
|
|
1041
|
+
// So the default value for the ownerId is set to null.
|
|
1042
|
+
|
|
1043
|
+
function getSeoData(config, pageType, data, url = {}, seoConfig = {}) {
|
|
1044
|
+
function findRelevantConfig(ownerType, ownerId = null) {
|
|
1045
|
+
const seoMetadata = config["seo-metadata"].find(page => page["owner-type"] === ownerType && page["owner-id"] === ownerId) || {};
|
|
1046
|
+
const { sections = [] } = config;
|
|
1047
|
+
const section = sections.find(section => ownerType == "section" && section.id === ownerId) || {};
|
|
1048
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
1049
|
+
if (seoMetadata.data || section.id || !lodash.isEmpty(customSeo)) {
|
|
1050
|
+
const result = Object.assign({}, {
|
|
1051
|
+
"page-title": customSeo["page-title"] || section.name,
|
|
1052
|
+
title: customSeo.title || section.name,
|
|
1053
|
+
canonicalUrl: customSeo["canonicalUrl"] || section["section-url"] || undefined
|
|
1054
|
+
}, seoMetadata.data);
|
|
1055
|
+
|
|
1056
|
+
if (!result.description) {
|
|
1057
|
+
const homeSeoData = config["seo-metadata"].find(page => page["owner-type"] === "home") || {
|
|
1058
|
+
data: { description: "" }
|
|
1059
|
+
};
|
|
1060
|
+
result.description = customSeo.description || homeSeoData.data.description;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
result.ogTitle = customSeo.ogTitle || result.title;
|
|
1064
|
+
result.ogDescription = customSeo.ogDescription || result.description;
|
|
1065
|
+
return result;
|
|
1066
|
+
}
|
|
1174
1067
|
}
|
|
1175
|
-
|
|
1176
|
-
if (
|
|
1177
|
-
|
|
1068
|
+
|
|
1069
|
+
if (seoConfig.customTags && seoConfig.customTags[pageType]) {
|
|
1070
|
+
return buildCustomTags(seoConfig.customTags, pageType);
|
|
1178
1071
|
}
|
|
1179
1072
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1073
|
+
function getShellSeoData(config) {
|
|
1074
|
+
const seoMetadata = config["seo-metadata"].find(meta => meta["owner-type"] === "home") || {};
|
|
1075
|
+
return Object.assign({}, seoMetadata.data, { canonicalUrl: SKIP_CANONICAL });
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
switch (pageType) {
|
|
1079
|
+
case "home-page":
|
|
1080
|
+
return findRelevantConfig("home");
|
|
1081
|
+
case "section-page":
|
|
1082
|
+
return findRelevantConfig("section", lodash.get(data, ["data", "section", "id"])) || getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url);
|
|
1083
|
+
case "collection-page":
|
|
1084
|
+
return getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url);
|
|
1085
|
+
case "tag-page":
|
|
1086
|
+
return buildTagsFromTopic(config, lodash.get(data, ["data", "tag"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
1087
|
+
case "story-page":
|
|
1088
|
+
return buildTagsFromStory(config, lodash.get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
1089
|
+
case "visual-story":
|
|
1090
|
+
return buildTagsFromStory(config, lodash.get(data, ["story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
1091
|
+
case "story-page-amp":
|
|
1092
|
+
return buildTagsFromStory(config, lodash.get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url);
|
|
1093
|
+
case "author-page":
|
|
1094
|
+
return buildTagsFromAuthor(config, lodash.get(data, ["data", "author"], {}), url, data) || getSeoData(config, "home-page", data, url);
|
|
1095
|
+
case "static-page":
|
|
1096
|
+
return buildTagsFromStaticPage(config, lodash.get(data, ["data", "page"], {}), url, data) || getSeoData(config, "home-page", data, url);
|
|
1097
|
+
case "shell":
|
|
1098
|
+
return getShellSeoData(config);
|
|
1099
|
+
default:
|
|
1100
|
+
return getSeoData(config, "home-page", data, url);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function getSeoDataFromCollection(config, data) {
|
|
1105
|
+
if (lodash.get(data, ["data", "collection", "name"])) {
|
|
1106
|
+
let { name, summary } = lodash.get(data, ["data", "collection"]);
|
|
1107
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
1108
|
+
|
|
1109
|
+
if (!summary) {
|
|
1110
|
+
summary = (getSeoData(config, "home-page", data) || {}).description;
|
|
1198
1111
|
}
|
|
1112
|
+
const title = customSeo.title || name;
|
|
1113
|
+
const pageTitle = customSeo["page-title"] || name;
|
|
1114
|
+
const ogTitle = customSeo.ogTitle || title;
|
|
1115
|
+
const description = customSeo.description || summary;
|
|
1116
|
+
const ogDescription = customSeo.ogDescription || summary;
|
|
1117
|
+
return {
|
|
1118
|
+
"page-title": pageTitle,
|
|
1119
|
+
title,
|
|
1120
|
+
ogTitle,
|
|
1121
|
+
description,
|
|
1122
|
+
ogDescription,
|
|
1123
|
+
canonicalUrl: SKIP_CANONICAL
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const SKIP_CANONICAL = "__SKIP__CANONICAL__";
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* TextTags adds the majority of basic tags, such as
|
|
1132
|
+
* * Canonical URLs
|
|
1133
|
+
* * Title and Description
|
|
1134
|
+
* * Keywords
|
|
1135
|
+
*
|
|
1136
|
+
* If the current URL contains a cardId in the query parameters, then the title and description will come from *card["social-share"]*
|
|
1137
|
+
*
|
|
1138
|
+
* @extends Generator
|
|
1139
|
+
* @param {*} seoConfig
|
|
1140
|
+
* @param {boolean} seoConfig.enableOgTags Add og tags for Facebook
|
|
1141
|
+
* @param {boolean} seoConfig.enableTwitterCards Add twitter tags
|
|
1142
|
+
* @param {boolean} seoConfig.enableNews Add tags for Google News, like news_keywords
|
|
1143
|
+
* @param {Object} seoConfig.customTags Add tags for a custom page type. Usually looks like `{"custom-page": {"title": "value", "canonicalUrl": "value"}}`
|
|
1144
|
+
* @param {...*} params See {@link Generator} for other Parameters
|
|
1145
|
+
*/
|
|
1146
|
+
function TextTags(seoConfig, config, pageType, data, { url }) {
|
|
1147
|
+
const seoData = getSeoData(config, pageType, data, url, seoConfig);
|
|
1148
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
1149
|
+
|
|
1150
|
+
if (!seoData) return [];
|
|
1151
|
+
|
|
1152
|
+
const currentUrl = `${config["sketches-host"]}${url.pathname}`;
|
|
1153
|
+
|
|
1154
|
+
const basicTags = {
|
|
1155
|
+
description: customSeo.description || seoData.description,
|
|
1156
|
+
title: customSeo.title || seoData.title,
|
|
1157
|
+
keywords: customSeo.keywords || seoData.keywords
|
|
1199
1158
|
};
|
|
1159
|
+
|
|
1160
|
+
const ogUrl = customSeo.ogUrl || seoData.ogUrl || seoData.canonicalUrl || currentUrl;
|
|
1161
|
+
const ogTags = seoConfig.enableOgTags ? {
|
|
1162
|
+
"og:type": pageType === "story-page" || pageType === "story-page-amp" ? "article" : "website",
|
|
1163
|
+
"og:url": ogUrl === SKIP_CANONICAL ? undefined : ogUrl,
|
|
1164
|
+
"og:title": customSeo.ogTitle || seoData.ogTitle,
|
|
1165
|
+
"og:description": customSeo.ogDescription || seoData.ogDescription
|
|
1166
|
+
} : undefined;
|
|
1167
|
+
|
|
1168
|
+
const twitterTags = seoConfig.enableTwitterCards ? {
|
|
1169
|
+
"twitter:card": "summary_large_image",
|
|
1170
|
+
"twitter:title": customSeo.twitterTitle || seoData.ogTitle,
|
|
1171
|
+
"twitter:description": customSeo.twitterDescription || seoData.ogDescription
|
|
1172
|
+
} : undefined;
|
|
1173
|
+
|
|
1174
|
+
const allTags = Object.assign(basicTags, ogTags, twitterTags);
|
|
1175
|
+
|
|
1176
|
+
const commonTags = [{ tag: "title", children: customSeo.title || data.title || seoData["page-title"] }];
|
|
1177
|
+
|
|
1178
|
+
const canonical = seoData.canonicalUrl || currentUrl;
|
|
1179
|
+
if (canonical != SKIP_CANONICAL) {
|
|
1180
|
+
commonTags.push({ tag: "link", rel: "canonical", href: canonical });
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (pageType === "story-page" || pageType === "story-page-amp") {
|
|
1184
|
+
commonTags.push({ name: "author", content: seoData.author });
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if ((pageType === "story-page" || pageType === "story-page-amp") && seoConfig.enableNews) {
|
|
1188
|
+
commonTags.push({ name: "news_keywords", content: seoData.keywords });
|
|
1189
|
+
if (lodash.get(data, ["data", "story", "seo", "meta-google-news-standout"])) commonTags.push({ tag: "link", rel: "standout", href: seoData.storyUrl || currentUrl });
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return commonTags.concat(objectToTags(allTags));
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function getTitle(seoConfig, config, pageType, data, params) {
|
|
1196
|
+
const customSeo = lodash.get(data, ["data", "customSeo"], {});
|
|
1197
|
+
|
|
1198
|
+
if (lodash.get(data, ["title"])) return customSeo.title || lodash.get(data, ["title"]);
|
|
1199
|
+
|
|
1200
|
+
if (lodash.get(data, ["data", "title"])) return customSeo.title || lodash.get(data, ["data", "title"]);
|
|
1201
|
+
|
|
1202
|
+
const seoData = getSeoData(config, pageType, data, undefined, seoConfig) || {};
|
|
1203
|
+
return customSeo.title || seoData["page-title"];
|
|
1200
1204
|
}
|
|
1201
1205
|
|
|
1202
1206
|
function tagToKey(tag) {
|
|
1203
1207
|
switch (tag.tag || "meta") {
|
|
1204
1208
|
case "meta":
|
|
1205
|
-
return `meta-${tag.name || "name"}-${tag.property || "property"}`;
|
|
1209
|
+
return `meta-${tag.name || tag.itemprop || "name"}-${tag.property || "property"}`;
|
|
1206
1210
|
case "link":
|
|
1207
1211
|
return `link-${tag.rel}`;
|
|
1208
1212
|
case "title":
|
|
@@ -1293,7 +1297,7 @@ class SEO {
|
|
|
1293
1297
|
}
|
|
1294
1298
|
|
|
1295
1299
|
getTitle(config, pageType, data, params = {}) {
|
|
1296
|
-
return getTitle
|
|
1300
|
+
return getTitle(this.seoConfig, config, pageType, data);
|
|
1297
1301
|
}
|
|
1298
1302
|
}
|
|
1299
1303
|
|