@quintype/seo 1.46.4-recipe-schema.4 → 1.46.4
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 +2 -0
- package/dist/index.cjs.js +19 -55
- package/package.json +1 -1
- package/src/structured-data/schema.js +1 -25
- package/src/structured-data/structured-data-tags.js +23 -27
- package/src/utils.js +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.46.4](https://github.com/quintype/quintype-node-seo/compare/v1.46.3...v1.46.4) (2024-08-14)
|
|
6
|
+
|
|
5
7
|
### [1.46.3](https://github.com/quintype/quintype-node-seo/compare/v1.46.2...v1.46.3) (2024-07-17)
|
|
6
8
|
|
|
7
9
|
### [1.46.2](https://github.com/quintype/quintype-node-seo/compare/v1.46.1...v1.46.2) (2024-06-12)
|
package/dist/index.cjs.js
CHANGED
|
@@ -24,22 +24,6 @@ function objectToTags(object) {
|
|
|
24
24
|
return lodash.entries(object).filter(([key, value]) => value).map(([key, value]) => ({ [getPropertyName(key)]: key, content: value }));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function extractTextFromHtmlString(html) {
|
|
28
|
-
const regex = /<p>(.*?)<\/p>/g;
|
|
29
|
-
const textContents = [];
|
|
30
|
-
|
|
31
|
-
let match;
|
|
32
|
-
while ((match = regex.exec(html)) !== null) {
|
|
33
|
-
textContents.push(match[1]);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return textContents;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const getCardAttributes = (card, type) => {
|
|
40
|
-
return card.metadata.attributes[type];
|
|
41
|
-
};
|
|
42
|
-
|
|
43
27
|
function getPropertyName(key) {
|
|
44
28
|
return key.startsWith("fb:") || key.startsWith("og:") ? "property" : "name";
|
|
45
29
|
}
|
|
@@ -569,28 +553,6 @@ function generateAuthorPageSchema(publisherConfig, data, url) {
|
|
|
569
553
|
};
|
|
570
554
|
}
|
|
571
555
|
|
|
572
|
-
function generateRecipePageSchema(story) {
|
|
573
|
-
const { headline, url, "author-name": authorName, description } = story;
|
|
574
|
-
|
|
575
|
-
const cardsWithAttributes = story.cards.filter(card => getCardAttributes(card, "cardtype"));
|
|
576
|
-
const cardWithIngredients = cardsWithAttributes.filter(card => getCardAttributes(card, "cardtype").includes("ingredients "));
|
|
577
|
-
const ingredientsRichText = cardWithIngredients[0]["story-elements"][0].text;
|
|
578
|
-
const ingredients = extractTextFromHtmlString(ingredientsRichText);
|
|
579
|
-
|
|
580
|
-
return {
|
|
581
|
-
"@context": "https://schema.org/",
|
|
582
|
-
"@type": "Recipe",
|
|
583
|
-
name: headline,
|
|
584
|
-
url: url,
|
|
585
|
-
author: {
|
|
586
|
-
"@type": "Person",
|
|
587
|
-
name: authorName
|
|
588
|
-
},
|
|
589
|
-
description: description,
|
|
590
|
-
recipeIngredient: ingredients
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
556
|
function getMovieEntityTags(movieJson) {
|
|
595
557
|
return getSchemaMovieReview(movieJson);
|
|
596
558
|
}
|
|
@@ -774,12 +736,20 @@ function findStoryElementField(card, type, field, defaultValue) {
|
|
|
774
736
|
if (elements.length > 0) return elements[0][field];else return defaultValue;
|
|
775
737
|
}
|
|
776
738
|
|
|
739
|
+
function getTextElementsOfOneCard(storyElements = []) {
|
|
740
|
+
return storyElements.filter(element => element.type === "text");
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function getCompleteTextOfOneCard(storyElements, stripHtmlFromArticleBody) {
|
|
744
|
+
return getTextElementsOfOneCard(storyElements).map(item => stripHtmlFromArticleBody ? getPlainText(item.text) : item.text).join(".");
|
|
745
|
+
}
|
|
746
|
+
|
|
777
747
|
function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherConfig = {}, timezone) {
|
|
778
748
|
const imageWidth = 1200;
|
|
779
749
|
const imageHeight = 675;
|
|
780
750
|
const authorSchema = structuredData.authorSchema && structuredData.authorSchema(story) || [];
|
|
781
751
|
const storyKeysPresence = Object.keys(story).length > 0;
|
|
782
|
-
|
|
752
|
+
storyKeysPresence && getCompleteText(story, structuredData.stripHtmlFromArticleBody) || "";
|
|
783
753
|
return {
|
|
784
754
|
headline: story.headline,
|
|
785
755
|
description: story.summary || story.headline,
|
|
@@ -788,16 +758,21 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
|
|
|
788
758
|
coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
|
|
789
759
|
dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
|
|
790
760
|
|
|
791
|
-
liveBlogUpdate: story.cards.map(card =>
|
|
761
|
+
liveBlogUpdate: story.cards.map(card => {
|
|
762
|
+
const storyElements = get__default["default"](card, ["story-elements"]);
|
|
763
|
+
const cardArticleBody = getCompleteTextOfOneCard(storyElements, structuredData.stripHtmlFromArticleBody) || "";
|
|
764
|
+
|
|
765
|
+
return 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, cardArticleBody);
|
|
766
|
+
})
|
|
792
767
|
};
|
|
793
768
|
}
|
|
794
769
|
|
|
795
770
|
function getEmbedUrl(cards) {
|
|
796
771
|
const playerUrlMapping = {
|
|
797
772
|
"dailymotion-embed-script": "dailymotion-url",
|
|
798
|
-
instagram: "instagram-url",
|
|
773
|
+
"instagram": "instagram-url",
|
|
799
774
|
"facebook-video": "facebook-url",
|
|
800
|
-
tweet: "tweet-url",
|
|
775
|
+
"tweet": "tweet-url",
|
|
801
776
|
"vimeo-video": "vimeo-url",
|
|
802
777
|
"brightcove-video": "player-url"
|
|
803
778
|
};
|
|
@@ -810,8 +785,7 @@ function getEmbedUrl(cards) {
|
|
|
810
785
|
if (elem.metadata && elem.metadata[playerUrlField]) {
|
|
811
786
|
return elem.metadata[playerUrlField];
|
|
812
787
|
}
|
|
813
|
-
}
|
|
814
|
-
if (elem.type === "youtube-video" && elem.subtype === null) {
|
|
788
|
+
} if (elem.type === "youtube-video" && elem.subtype === null) {
|
|
815
789
|
if (elem.url) {
|
|
816
790
|
return elem.url;
|
|
817
791
|
}
|
|
@@ -976,6 +950,7 @@ function StructuredDataTags({ structuredData = {} }, config, pageType, response
|
|
|
976
950
|
const isStructuredDataEmpty = Object.keys(structuredData).length === 0;
|
|
977
951
|
const enableBreadcrumbList = get__default["default"](structuredData, ["enableBreadcrumbList"], true);
|
|
978
952
|
const structuredDataTags = get__default["default"](structuredData, ["structuredDataTags"], []);
|
|
953
|
+
|
|
979
954
|
let articleData = {};
|
|
980
955
|
|
|
981
956
|
if (!isStructuredDataEmpty) {
|
|
@@ -1004,17 +979,6 @@ function StructuredDataTags({ structuredData = {} }, config, pageType, response
|
|
|
1004
979
|
if (!isStructuredDataEmpty && pageType === "story-page") {
|
|
1005
980
|
const newsArticleTags = generateNewsArticleTags();
|
|
1006
981
|
newsArticleTags ? tags.push(storyTags(), newsArticleTags) : tags.push(storyTags());
|
|
1007
|
-
if (story["story-template"] === "recipe") {
|
|
1008
|
-
const recipeTags = generateRecipePageSchema(story);
|
|
1009
|
-
recipeTags.image = Object.assign({
|
|
1010
|
-
"@type": "ImageObject"
|
|
1011
|
-
}, generateArticleImageData(story["hero-image-s3-key"], publisherConfig));
|
|
1012
|
-
recipeTags.video = Object.assign({
|
|
1013
|
-
"@type": "VideoObject"
|
|
1014
|
-
}, generateVideoArticleData(structuredData, story, publisherConfig, timezone));
|
|
1015
|
-
|
|
1016
|
-
tags.push(ldJson("Recipe", recipeTags));
|
|
1017
|
-
}
|
|
1018
982
|
}
|
|
1019
983
|
|
|
1020
984
|
if (!isStructuredDataEmpty && pageType === "story-page-amp") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { get } from "lodash";
|
|
2
2
|
import { getTitle } from "../generate-common-seo";
|
|
3
|
-
import {
|
|
3
|
+
import { stripMillisecondsFromTime } from "../utils";
|
|
4
4
|
export const getSchemaContext = { "@context": "http://schema.org" };
|
|
5
5
|
|
|
6
6
|
export function getSchemaType(type) {
|
|
@@ -151,27 +151,3 @@ export function generateAuthorPageSchema(publisherConfig, data, url) {
|
|
|
151
151
|
},
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
export function generateRecipePageSchema(story) {
|
|
156
|
-
const { headline, url, "author-name": authorName, description } = story;
|
|
157
|
-
|
|
158
|
-
const cardsWithAttributes = story.cards.filter((card) => getCardAttributes(card, "cardtype"));
|
|
159
|
-
const cardWithIngredients = cardsWithAttributes.filter((card) =>
|
|
160
|
-
getCardAttributes(card, "cardtype").includes("ingredients ")
|
|
161
|
-
);
|
|
162
|
-
const ingredientsRichText = cardWithIngredients[0]["story-elements"][0].text;
|
|
163
|
-
const ingredients = extractTextFromHtmlString(ingredientsRichText);
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
"@context": "https://schema.org/",
|
|
167
|
-
"@type": "Recipe",
|
|
168
|
-
name: headline,
|
|
169
|
-
url: url,
|
|
170
|
-
author: {
|
|
171
|
-
"@type": "Person",
|
|
172
|
-
name: authorName,
|
|
173
|
-
},
|
|
174
|
-
description: description,
|
|
175
|
-
recipeIngredient: ingredients,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
@@ -3,7 +3,6 @@ import { getQueryParams, stripMillisecondsFromTime } from "../utils";
|
|
|
3
3
|
import { generateTagsForEntity } from "./entity";
|
|
4
4
|
import {
|
|
5
5
|
generateAuthorPageSchema,
|
|
6
|
-
generateRecipePageSchema,
|
|
7
6
|
getSchemaBlogPosting,
|
|
8
7
|
getSchemaBreadcrumbList,
|
|
9
8
|
getSchemaContext,
|
|
@@ -229,6 +228,16 @@ function findStoryElementField(card, type, field, defaultValue) {
|
|
|
229
228
|
else return defaultValue;
|
|
230
229
|
}
|
|
231
230
|
|
|
231
|
+
function getTextElementsOfOneCard(storyElements = []) {
|
|
232
|
+
return storyElements.filter((element) => element.type === "text");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getCompleteTextOfOneCard(storyElements, stripHtmlFromArticleBody) {
|
|
236
|
+
return getTextElementsOfOneCard(storyElements)
|
|
237
|
+
.map((item) => (stripHtmlFromArticleBody ? getPlainText(item.text) : item.text))
|
|
238
|
+
.join(".");
|
|
239
|
+
}
|
|
240
|
+
|
|
232
241
|
function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherConfig = {}, timezone) {
|
|
233
242
|
const imageWidth = 1200;
|
|
234
243
|
const imageHeight = 675;
|
|
@@ -243,8 +252,11 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
|
|
|
243
252
|
coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
|
|
244
253
|
dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
|
|
245
254
|
|
|
246
|
-
liveBlogUpdate: story.cards.map((card) =>
|
|
247
|
-
|
|
255
|
+
liveBlogUpdate: story.cards.map((card) => {
|
|
256
|
+
const storyElements = get(card, ["story-elements"]);
|
|
257
|
+
const cardArticleBody = getCompleteTextOfOneCard(storyElements, structuredData.stripHtmlFromArticleBody) || "";
|
|
258
|
+
|
|
259
|
+
return getSchemaBlogPosting(
|
|
248
260
|
card,
|
|
249
261
|
authorData(story.authors, authorSchema, publisherConfig),
|
|
250
262
|
findStoryElementField(card, "title", "text", story.headline),
|
|
@@ -257,20 +269,20 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
|
|
|
257
269
|
structuredData,
|
|
258
270
|
story,
|
|
259
271
|
timezone,
|
|
260
|
-
|
|
261
|
-
)
|
|
262
|
-
),
|
|
272
|
+
cardArticleBody
|
|
273
|
+
);
|
|
274
|
+
}),
|
|
263
275
|
};
|
|
264
276
|
}
|
|
265
277
|
|
|
266
278
|
function getEmbedUrl(cards) {
|
|
267
279
|
const playerUrlMapping = {
|
|
268
280
|
"dailymotion-embed-script": "dailymotion-url",
|
|
269
|
-
instagram: "instagram-url",
|
|
281
|
+
"instagram": "instagram-url",
|
|
270
282
|
"facebook-video": "facebook-url",
|
|
271
|
-
tweet: "tweet-url",
|
|
283
|
+
"tweet": "tweet-url",
|
|
272
284
|
"vimeo-video": "vimeo-url",
|
|
273
|
-
"brightcove-video": "player-url"
|
|
285
|
+
"brightcove-video": "player-url"
|
|
274
286
|
};
|
|
275
287
|
|
|
276
288
|
for (const card of cards) {
|
|
@@ -281,7 +293,7 @@ function getEmbedUrl(cards) {
|
|
|
281
293
|
if (elem.metadata && elem.metadata[playerUrlField]) {
|
|
282
294
|
return elem.metadata[playerUrlField];
|
|
283
295
|
}
|
|
284
|
-
}
|
|
296
|
+
};
|
|
285
297
|
if (elem.type === "youtube-video" && elem.subtype === null) {
|
|
286
298
|
if (elem.url) {
|
|
287
299
|
return elem.url;
|
|
@@ -447,6 +459,7 @@ export function StructuredDataTags({ structuredData = {} }, config, pageType, re
|
|
|
447
459
|
const isStructuredDataEmpty = Object.keys(structuredData).length === 0;
|
|
448
460
|
const enableBreadcrumbList = get(structuredData, ["enableBreadcrumbList"], true);
|
|
449
461
|
const structuredDataTags = get(structuredData, ["structuredDataTags"], []);
|
|
462
|
+
|
|
450
463
|
let articleData = {};
|
|
451
464
|
|
|
452
465
|
if (!isStructuredDataEmpty) {
|
|
@@ -475,23 +488,6 @@ export function StructuredDataTags({ structuredData = {} }, config, pageType, re
|
|
|
475
488
|
if (!isStructuredDataEmpty && pageType === "story-page") {
|
|
476
489
|
const newsArticleTags = generateNewsArticleTags();
|
|
477
490
|
newsArticleTags ? tags.push(storyTags(), newsArticleTags) : tags.push(storyTags());
|
|
478
|
-
if (story["story-template"] === "recipe") {
|
|
479
|
-
const recipeTags = generateRecipePageSchema(story);
|
|
480
|
-
recipeTags.image = Object.assign(
|
|
481
|
-
{
|
|
482
|
-
"@type": "ImageObject",
|
|
483
|
-
},
|
|
484
|
-
generateArticleImageData(story["hero-image-s3-key"], publisherConfig)
|
|
485
|
-
);
|
|
486
|
-
recipeTags.video = Object.assign(
|
|
487
|
-
{
|
|
488
|
-
"@type": "VideoObject",
|
|
489
|
-
},
|
|
490
|
-
generateVideoArticleData(structuredData, story, publisherConfig, timezone)
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
tags.push(ldJson("Recipe", recipeTags));
|
|
494
|
-
}
|
|
495
491
|
}
|
|
496
492
|
|
|
497
493
|
if (!isStructuredDataEmpty && pageType === "story-page-amp") {
|
package/src/utils.js
CHANGED
|
@@ -8,22 +8,6 @@ export function objectToTags(object) {
|
|
|
8
8
|
.map(([key, value]) => ({ [getPropertyName(key)]: key, content: value }));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function extractTextFromHtmlString(html) {
|
|
12
|
-
const regex = /<p>(.*?)<\/p>/g;
|
|
13
|
-
const textContents = [];
|
|
14
|
-
|
|
15
|
-
let match;
|
|
16
|
-
while ((match = regex.exec(html)) !== null) {
|
|
17
|
-
textContents.push(match[1]);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return textContents;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const getCardAttributes = (card, type) => {
|
|
24
|
-
return card.metadata.attributes[type];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
11
|
function getPropertyName(key) {
|
|
28
12
|
return key.startsWith("fb:") || key.startsWith("og:") ? "property" : "name";
|
|
29
13
|
}
|