@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/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 buildTagsFromStory(config, story, url = {}, data = {}) {
53
- if (!story) return;
54
-
55
- function getStoryCardMetadata(cardId) {
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
- if (metadata && !lodash.isEmpty(metadata) && metadata["social-share"]) {
60
- return {
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
- const seo = story.seo || {};
61
+ return true;
62
+ }
72
63
 
73
- const storyUrl = story.url || `${config["sketches-host"]}/${story.slug}`;
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
- const customSeo = lodash.get(data, ["data", "customSeo"], {});
76
- const authors = lodash.get(story, ["authors"], []).map(author => author.name);
77
- const title = customSeo.title || seo["meta-title"] || story.headline;
78
- const pageTitle = customSeo["page-title"] || seo["meta-title"] || story.headline;
79
- const description = customSeo.description || seo["meta-description"] || story.summary;
80
- const keywords = (customSeo.keywords || seo["meta-keywords"] || (story.tags || []).map(tag => tag.name)).join(",");
81
- const ogUrl = customSeo.ogUrl || lodash.get(seo, ["og", "url"]) || storyUrl;
82
- const getOgTitle = customSeo.ogTitle || lodash.get(story, ["alternative", "social", "default", "headline"], story.headline) || story.headline;
83
- const ogDescription = customSeo.ogDescription || story.summary;
84
- const storyMetaData = {
85
- title,
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
- if (url.query && url.query.cardId) {
98
- const storyCardMetadata = getStoryCardMetadata(url.query.cardId);
99
- return Object.assign({}, storyMetaData, storyCardMetadata); //TODO rewrite in spread syntax, add babel plugin
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
- return storyMetaData;
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 buildTagsFromTopic(config, tag, url = {}, data) {
106
- if (lodash.isEmpty(tag)) return;
107
- const customSeo = lodash.get(data, ["data", "customSeo"], {});
108
- const tagName = customSeo.title || tag["meta-title"] || tag.name;
109
- const pageTitle = customSeo["page-title"] || tagName;
110
- const tagDescription = customSeo.description || tag["meta-description"];
111
- const description = `Read stories listed under on ${tag.name}`;
112
- const tagUrl = `${config["sketches-host"]}${url.pathname}`;
113
- const canonicalSlug = tag["canonical-slug"] || url.pathname;
114
- const canonicalUrl = `${config["sketches-host"]}${canonicalSlug}`;
115
- const ogTitle = customSeo.ogTitle || tagName;
116
- const ogDescription = customSeo.ogDescription || description;
117
- const topicMetaData = {
118
- title: tagName,
119
- "page-title": pageTitle,
120
- description: tagDescription || description,
121
- keywords: tagName,
122
- canonicalUrl,
123
- ogUrl: tagUrl,
124
- ogTitle,
125
- ogDescription
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 topicMetaData;
142
+ return omitBy__default["default"](staticData, isUndefined__default["default"]);
129
143
  }
130
144
 
131
- function buildTagsFromAuthor(config, author, url = {}, data) {
132
- if (lodash.isEmpty(author)) return;
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
- const customSeo = lodash.get(data, ["data", "customSeo"], {});
135
- const title = customSeo.title || author.name;
136
- const pageTitle = customSeo["page-title"] || title;
137
- const description = customSeo.description || author.bio || `View all articles written by ${title}`;
138
- const ogTitle = customSeo.ogTitle || author.name;
139
- const authorUrl = `${config["sketches-host"]}${url.pathname}`;
140
- const ogDescription = customSeo.ogDescription || description;
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
- title,
144
- "page-title": pageTitle,
145
- description,
146
- keywords: `${title},${config["publisher-name"]}`,
147
- canonicalUrl: authorUrl,
148
- ogUrl: authorUrl,
149
- ogTitle,
150
- ogDescription
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 buildCustomTags(customTags = {}, pageType = "") {
155
- const configObject = customTags[pageType];
156
- if (configObject) {
157
- return configObject;
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 buildTagsFromStaticPage(config, page, url = {}, data) {
163
- const seoData = lodash.get(page, ["metadata", "seo"], {});
164
- const customSeo = lodash.get(data, ["data", "customSeo"], {});
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
- const { "meta-title": metaTitle, "meta-description": metaDescription, "meta-keywords": keywords } = seoData;
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 title = customSeo.title || metaTitle || page.title;
170
- const pageTitle = customSeo["page-title"] || title;
171
- const description = customSeo.description || metaDescription;
172
- const ogTitle = customSeo.ogTitle || title;
173
- const staticPageUrl = `${config["sketches-host"]}${url.pathname}`;
174
- const ogDescription = customSeo.ogDescription || description;
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
- 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))
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 showAmpTag({ ampStoryPages = true }, pageType, story) {
1077
- if (!ampStoryPages || pageType !== 'story-page') {
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
- return true;
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
- const getDomain = (url, domainSlug) => {
1089
- const domain = domainSlug ? new URL(url).origin : '';
1090
- try {
1091
- return domain;
1092
- } catch (err) {
1093
- return "";
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 story = get__default["default"](data, ["data", "story"], {});
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
- if (showAmpTag(seoConfig, pageType, story)) {
1117
- return [{
1118
- tag: 'link',
1119
- rel: 'amphtml',
1120
- href: ampUrl
1121
- }];
1122
- } else {
1123
- return [];
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 getTitle(config) {
1128
- return config["publisher-settings"] ? config["publisher-settings"]["title"] : config["publisher-name"];
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 generateStaticData(config) {
1132
- const title = getTitle(config);
1133
- const themeConfig = config["theme-attributes"] || {};
1134
- const publicIntegrations = get__default["default"](config, ['public-integrations'], {});
1135
- const staticData = {
1136
- "twitter:site": title,
1137
- "twitter:domain": config["sketches-host"],
1138
- "twitter:app:name:ipad": themeConfig["twitter_app_name_ipad"],
1139
- "twitter:app:name:googleplay": themeConfig["twitter_app_name_googleplay"],
1140
- "twitter:app:id:googleplay": themeConfig["twitter_app_id_googleplay"],
1141
- "twitter:app:name:iphone": themeConfig["twitter_app_name_iphone"],
1142
- "twitter:app:id:iphone": themeConfig["twitter_app_id_iphone"],
1143
- "apple-itunes-app": themeConfig["apple_itunes_app"],
1144
- "google-play-app": themeConfig["google_play_app"],
1145
- "fb:app_id": get__default["default"](publicIntegrations, ['facebook', 'app-id']) || get__default["default"](themeConfig, ["fb_app_id"]),
1146
- "fb:pages": themeConfig["fb_pages"],
1147
- "og:site_name": title
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
- return omitBy__default["default"](staticData, isUndefined__default["default"]);
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 generateImageObject(config = {}) {
1154
- const { "theme-attributes": themeConfig = {} } = config;
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
- "@context": "http://schema.org",
1157
- "@type": "ImageObject",
1158
- "author": config['publisher-name'],
1159
- "contentUrl": themeConfig.logo,
1160
- "url": themeConfig.logo,
1161
- "name": "logo",
1162
- "width": themeConfig.logo && getQueryParams(themeConfig.logo).width,
1163
- "height": themeConfig.logo && getQueryParams(themeConfig.logo).height
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
- function generateStructuredData(config = {}) {
1168
- const title = getTitle(config);
1169
- const { "theme-attributes": themeConfig, "social-links": socialLinks, "seo-metadata": seoMetadata = [] } = config;
1170
- const homePageSeo = seoMetadata.find(page => page["owner-type"] === "home") || {};
1171
- const { "page-title": pageTitle = "", description = "", keywords = "" } = get__default["default"](homePageSeo, ["data"], {});
1172
- if (!themeConfig || !themeConfig.logo) {
1173
- return {};
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
- let enableStructuredDataForNewsArticle = themeConfig['structured_data_news_article'] || false;
1176
- if (config.hasOwnProperty('enableStructuredDataForNewsArticle') && typeof config.enableStructuredDataForNewsArticle !== "undefined") {
1177
- enableStructuredDataForNewsArticle = config.enableStructuredDataForNewsArticle;
1068
+
1069
+ if (seoConfig.customTags && seoConfig.customTags[pageType]) {
1070
+ return buildCustomTags(seoConfig.customTags, pageType);
1178
1071
  }
1179
1072
 
1180
- return {
1181
- organization: {
1182
- name: title,
1183
- url: config["sketches-host"],
1184
- logo: generateImageObject(config),
1185
- sameAs: socialLinks ? Object.values(socialLinks) : []
1186
- },
1187
- enableNewsArticle: !!enableStructuredDataForNewsArticle,
1188
- storyUrlAsMainEntityUrl: !!enableStructuredDataForNewsArticle,
1189
- enableVideo: !themeConfig['structured_data_enable_video'],
1190
- enableLiveBlog: !themeConfig['structured_data_enable_live_blog'],
1191
- website: {
1192
- url: config["sketches-host"],
1193
- searchpath: "search?q={query}",
1194
- queryinput: "required name=query",
1195
- name: pageTitle || title,
1196
- headline: description,
1197
- keywords
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$1(this.seoConfig, config, pageType, data);
1300
+ return getTitle(this.seoConfig, config, pageType, data);
1297
1301
  }
1298
1302
  }
1299
1303