@quintype/seo 1.49.3-beta.0 → 1.50.1-schema-changes-2026.0

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 CHANGED
@@ -2,7 +2,24 @@
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.49.3-beta.0](https://github.com/quintype/quintype-node-seo/compare/v1.49.2...v1.49.3-beta.0) (2025-10-15)
5
+ ## [1.50.0](https://github.com/quintype/quintype-node-seo/compare/v1.47.0...v1.50.0) (2025-12-04)
6
+
7
+
8
+ ### Features
9
+
10
+ * add event schema support when eventDetails exist in story data ([#567](https://github.com/quintype/quintype-node-seo/issues/567)) ([941b86e](https://github.com/quintype/quintype-node-seo/commit/941b86eedd93845fd7a295341a75bd667c35fc85))
11
+ * **media-gallery:** add media gallery schema to visual stories ([#565](https://github.com/quintype/quintype-node-seo/issues/565)) ([34ded03](https://github.com/quintype/quintype-node-seo/commit/34ded036225af150d8085c6d108b5c8bbacece73))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * change meta description in liveblog structured data ([0bafc42](https://github.com/quintype/quintype-node-seo/commit/0bafc42f3654dd5a588da8d944dcd59c9dd5162d))
17
+ * change meta description in liveblog structured data ([4185cf7](https://github.com/quintype/quintype-node-seo/commit/4185cf74422a478906a740037dc765b637d29e66))
18
+ * change meta description in liveblog structured data ([2792ce8](https://github.com/quintype/quintype-node-seo/commit/2792ce849e7a97b12d614f563ff1ee64c084982f))
19
+ * change meta description in liveblog structured data ([347a682](https://github.com/quintype/quintype-node-seo/commit/347a6825f06388d8e180642bee62e23535b3cae7))
20
+ * change meta description in liveblog structured data ([944252e](https://github.com/quintype/quintype-node-seo/commit/944252ed1256bd70847894d2c5b46ea717727d78))
21
+ * **schema:** fix media gallery schema ([#566](https://github.com/quintype/quintype-node-seo/issues/566)) ([549bd12](https://github.com/quintype/quintype-node-seo/commit/549bd12c2b788acb0f2017c807368c9f242e5c2e))
22
+ * support article schema when enableNewsArticle is not set to "withoutArticleSchema" value ([#569](https://github.com/quintype/quintype-node-seo/issues/569)) ([bfacda1](https://github.com/quintype/quintype-node-seo/commit/bfacda161f4d9b2e5d220acb1c3088081c0f04ec))
6
23
 
7
24
  ### [1.49.2](https://github.com/quintype/quintype-node-seo/compare/v1.49.1...v1.49.2) (2025-07-07)
8
25
 
package/dist/index.cjs.js CHANGED
@@ -206,9 +206,7 @@ function generateStaticData(config) {
206
206
  function generateImageObject(config = {}) {
207
207
  const { "theme-attributes": themeConfig = {} } = config;
208
208
  return {
209
- "@context": "http://schema.org",
210
209
  "@type": "ImageObject",
211
- author: config["publisher-name"],
212
210
  contentUrl: themeConfig.logo,
213
211
  url: themeConfig.logo,
214
212
  name: "logo",
@@ -460,7 +458,7 @@ function StaticTags(seoConfig, config, pageType, data, { url }) {
460
458
  return objectToTags(seoConfig.staticTags || {});
461
459
  }
462
460
 
463
- const getSchemaContext = { "@context": "http://schema.org" };
461
+ const getSchemaContext = { "@context": "https://schema.org" };
464
462
 
465
463
  function getSchemaType(type) {
466
464
  return { "@type": type };
@@ -499,7 +497,7 @@ function getSchemaBlogPosting(card = {}, headline = "", image = "", structuredDa
499
497
  function getSchemaPublisher(organization, orgUrl) {
500
498
  const id = { id: orgUrl };
501
499
  return {
502
- publisher: Object.assign({}, getSchemaType("Organization"), getSchemaContext, organization, id)
500
+ publisher: Object.assign({}, organization, id, getSchemaType("NewsMediaOrganization"))
503
501
  };
504
502
  }
505
503
 
@@ -587,7 +585,7 @@ function generateTagsForEntity(entity, ldJson) {
587
585
  }
588
586
 
589
587
  function getLdJsonFields(type, fields) {
590
- return Object.assign({}, fields, getSchemaType(type), getSchemaContext);
588
+ return Object.assign({}, getSchemaContext, getSchemaType(type), fields);
591
589
  }
592
590
 
593
591
  function ldJson(type, fields) {
@@ -610,7 +608,7 @@ function imageUrl(publisherConfig, s3Key, width, height) {
610
608
  function generateCommonData(structuredData = {}, story = {}, publisherConfig = {}, timezone) {
611
609
  const storyUrl = story.url || `${publisherConfig["sketches-host"]}/${story.slug}`;
612
610
  const orgUrl = get__default["default"](structuredData, ["organization", "url"], "");
613
- const mainEntityUrl = Object.keys(story).length > 0 && structuredData.storyUrlAsMainEntityUrl ? storyUrl : get__default["default"](structuredData, ["organization", "url"], "");
611
+ const mainEntityUrl = Object.keys(story).length > 0 ? storyUrl : get__default["default"](structuredData, ["organization", "url"], "");
614
612
  const imageWidth = 1200;
615
613
  const imageHeight = 675;
616
614
  return Object.assign({}, {
@@ -670,18 +668,49 @@ function generateArticleData(structuredData = {}, story = {}, publisherConfig =
670
668
  const isAccessibleForFree = storyAccessType ? {} : { isAccessibleForFree: storyAccessType };
671
669
  const metadata = get__default["default"](story, ["metadata"], {});
672
670
  const sponsor = metadata.hasOwnProperty("sponsored-by") ? { sponsor: { name: metadata["sponsored-by"] } } : {};
671
+ const inLanguage = get__default["default"](publisherConfig, ["language", "iso-code"]);
672
+ const description = get__default["default"](story, ["seo", "meta-description"]) || get__default["default"](story, ["subheadline"]) || get__default["default"](story, ["headline"]);
673
673
 
674
674
  return Object.assign({}, generateCommonData(structuredData, story, publisherConfig, timezone), {
675
+ description,
675
676
  author: authorData(authors, authorSchema, publisherConfig),
676
677
  keywords: metaKeywords.join(","),
677
678
  thumbnailUrl: imageUrl(publisherConfig, story["hero-image-s3-key"], imageWidth, imageHeight),
678
679
  articleBody: storyKeysPresence && getCompleteText(story, structuredData.stripHtmlFromArticleBody) || "",
679
680
  dateCreated: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
680
- dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
681
+ dateModified: stripMillisecondsFromTime(new Date(story["updated-at"]), timezone),
681
682
  datePublished: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
682
683
  name: storyKeysPresence && story.headline || "",
683
- image: generateArticleImageData(story["hero-image-s3-key"], publisherConfig)
684
- }, isAccessibleForFree, sponsor, { isPartOf: generateIsPartOfDataForArticle(story, publisherConfig) }, articleSectionObj(story));
684
+ image: generateArticleHeroImageData(story["hero-image-s3-key"], publisherConfig, story["hero-image-metadata"])
685
+ }, inLanguage && { inLanguage }, isAccessibleForFree, sponsor, { isPartOf: generateIsPartOfDataForArticle(story, publisherConfig) }, articleSectionObj(story));
686
+ }
687
+
688
+ function generateArticleHeroImageData(image, publisherConfig = {}, imageMetadata = {}) {
689
+ const imageWidth = 1200;
690
+ const imageHeights = [675, 900, 1200];
691
+ const hasFocusPoint = get__default["default"](imageMetadata, ["focus-point"], null);
692
+
693
+ const focusedImage = hasFocusPoint ? new quintypeJs.FocusedImage(image, imageMetadata) : null;
694
+
695
+ return imageHeights.map(height => {
696
+ let croppedImage = "";
697
+ const imageSrc = /^https?.*/.test(publisherConfig["cdn-image"]) ? publisherConfig["cdn-image"] : `https://${publisherConfig["cdn-image"]}`;
698
+
699
+ if (focusedImage) {
700
+ const path = focusedImage.path([imageWidth, height], {
701
+ w: imageWidth,
702
+ h: height,
703
+ auto: "format,compress",
704
+ fit: "crop"
705
+ });
706
+ croppedImage = `${imageSrc}/${path}`;
707
+ }
708
+ const finalUrl = focusedImage ? croppedImage : imageUrl(publisherConfig, image, imageWidth, height);
709
+ return Object.assign({
710
+ "@type": "ImageObject",
711
+ url: finalUrl
712
+ }, getQueryParams(finalUrl));
713
+ });
685
714
  }
686
715
 
687
716
  function generateArticleImageData(image, publisherConfig = {}) {
@@ -755,8 +784,7 @@ function generateNewsArticleData(structuredData = {}, story = {}, publisherConfi
755
784
  const sponsor = metadata.hasOwnProperty("sponsored-by") ? { sponsor: { name: metadata["sponsored-by"] } } : {};
756
785
 
757
786
  return Object.assign({}, {
758
- alternativeHeadline: alternative.home && alternative.home.default ? alternative.home.default.headline : "",
759
- description: story.summary
787
+ alternativeHeadline: alternative.home && alternative.home.default ? alternative.home.default.headline : ""
760
788
  }, sponsor, isAccessibleForFree, { isPartOf: generateIsPartOfDataForNewsArticle(story, publisherConfig, pageType, structuredData) }, generateHasPartData(storyAccessType));
761
789
  }
762
790
 
@@ -789,7 +817,7 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
789
817
  author: authorData(story.authors, authorSchema, publisherConfig),
790
818
  coverageEndTime: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
791
819
  coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
792
- dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
820
+ dateModified: stripMillisecondsFromTime(new Date(story["updated-at"]), timezone),
793
821
  mainEntityOfPage,
794
822
  publisher,
795
823
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quintype/seo",
3
- "version": "1.49.3-beta.0",
3
+ "version": "1.50.1-schema-changes-2026.0",
4
4
  "description": "SEO Modules for Quintype",
5
5
  "main": "dist/index.cjs.js",
6
6
  "repository": "https://github.com/quintype/quintype-node-seo",
@@ -14,7 +14,7 @@
14
14
  "coverage": "nyc --all --reporter=html npm test",
15
15
  "docs": "rimraf docs && jsdoc -c jsdoc.json",
16
16
  "prepack": "npm run build",
17
- "prepublishOnly": "npm install --legacy-peer-deps && npm test && ./bin-dev-scripts/standard-version-release.sh",
17
+ "prepublishOnly": "npm install && npm test && ./bin-dev-scripts/standard-version-release.sh",
18
18
  "test": "npm run build && mocha test",
19
19
  "sync-files-to": "npx onchange --verbose --await-write-finish=2000 'src/**/*' -- ./bin-dev-scripts/sync-to.sh "
20
20
  },
@@ -33,7 +33,7 @@
33
33
  "eslint-plugin-promise": "^5.1.0",
34
34
  "eslint-plugin-react": "^7.12.4",
35
35
  "eslint-plugin-standard": "^5.0.0",
36
- "gh-pages": "^6.3.0",
36
+ "gh-pages": "^3.2.2",
37
37
  "husky": "^7.0.0",
38
38
  "jsdoc": "^3.6.10",
39
39
  "lint-staged": "^11.0.0",
@@ -42,8 +42,8 @@
42
42
  "nyc": "^15.0.0",
43
43
  "onchange": "^7.0.2",
44
44
  "prettier": "^2.2.1",
45
- "react": "^19.2.0",
46
- "react-dom": "^19.2.0",
45
+ "react": "^17.0.1",
46
+ "react-dom": "^17.0.1",
47
47
  "rimraf": "^3.0.2",
48
48
  "rollup": "^2.0.0",
49
49
  "rollup-plugin-babel": "^3.0.2",
@@ -51,12 +51,7 @@
51
51
  "structured-data-testing-tool": "^4.5.0",
52
52
  "url": "^0.11.0"
53
53
  },
54
- "engines": {
55
- "node": ">=20.0.0",
56
- "npm": ">=10.0.0"
57
- },
58
54
  "dependencies": {
59
- "@babel/traverse": "^7.28.4",
60
55
  "date-fns": "^2.17.0",
61
56
  "date-fns-tz": "^1.1.1",
62
57
  "quintype-js": "^1.2.0"
@@ -33,9 +33,7 @@ export function generateStaticData(config) {
33
33
  export function generateImageObject(config = {}) {
34
34
  const { "theme-attributes": themeConfig = {} } = config;
35
35
  return {
36
- "@context": "http://schema.org",
37
36
  "@type": "ImageObject",
38
- author: config["publisher-name"],
39
37
  contentUrl: themeConfig.logo,
40
38
  url: themeConfig.logo,
41
39
  name: "logo",
@@ -1,7 +1,7 @@
1
1
  import { get } from "lodash";
2
2
  import { getTitle } from "../generate-common-seo";
3
3
  import { stripMillisecondsFromTime } from "../utils";
4
- export const getSchemaContext = { "@context": "http://schema.org" };
4
+ export const getSchemaContext = { "@context": "https://schema.org" };
5
5
 
6
6
  export function getSchemaType(type) {
7
7
  return { "@type": type };
@@ -57,7 +57,7 @@ export function getSchemaBlogPosting(
57
57
  export function getSchemaPublisher(organization, orgUrl) {
58
58
  const id = { id: orgUrl };
59
59
  return {
60
- publisher: Object.assign({}, getSchemaType("Organization"), getSchemaContext, organization, id),
60
+ publisher: Object.assign({}, organization, id, getSchemaType("NewsMediaOrganization")),
61
61
  };
62
62
  }
63
63
 
@@ -1,4 +1,5 @@
1
1
  import get from "lodash/get";
2
+ import { FocusedImage } from "quintype-js";
2
3
  import { getAllowedCards, getQueryParams, stripMillisecondsFromTime, stripQueryParams } from "../utils";
3
4
  import { generateTagsForEntity } from "./entity";
4
5
  import {
@@ -16,7 +17,7 @@ import {
16
17
  } from "./schema";
17
18
 
18
19
  function getLdJsonFields(type, fields) {
19
- return Object.assign({}, fields, getSchemaType(type), getSchemaContext);
20
+ return Object.assign({}, getSchemaContext, getSchemaType(type), fields);
20
21
  }
21
22
 
22
23
  function ldJson(type, fields) {
@@ -42,7 +43,7 @@ function generateCommonData(structuredData = {}, story = {}, publisherConfig = {
42
43
  const storyUrl = story.url || `${publisherConfig["sketches-host"]}/${story.slug}`;
43
44
  const orgUrl = get(structuredData, ["organization", "url"], "");
44
45
  const mainEntityUrl =
45
- Object.keys(story).length > 0 && structuredData.storyUrlAsMainEntityUrl
46
+ Object.keys(story).length > 0
46
47
  ? storyUrl
47
48
  : get(structuredData, ["organization", "url"], "");
48
49
  const imageWidth = 1200;
@@ -109,21 +110,26 @@ function generateArticleData(structuredData = {}, story = {}, publisherConfig =
109
110
  const isAccessibleForFree = storyAccessType ? {} : { isAccessibleForFree: storyAccessType };
110
111
  const metadata = get(story, ["metadata"], {});
111
112
  const sponsor = metadata.hasOwnProperty("sponsored-by") ? { sponsor: { name: metadata["sponsored-by"] } } : {};
113
+ const inLanguage = get(publisherConfig, ["language", "iso-code"]);
114
+ const description =
115
+ get(story, ["seo", "meta-description"]) || get(story, ["subheadline"]) || get(story, ["headline"]);
112
116
 
113
117
  return Object.assign(
114
118
  {},
115
119
  generateCommonData(structuredData, story, publisherConfig, timezone),
116
120
  {
121
+ description,
117
122
  author: authorData(authors, authorSchema, publisherConfig),
118
123
  keywords: metaKeywords.join(","),
119
124
  thumbnailUrl: imageUrl(publisherConfig, story["hero-image-s3-key"], imageWidth, imageHeight),
120
125
  articleBody: (storyKeysPresence && getCompleteText(story, structuredData.stripHtmlFromArticleBody)) || "",
121
126
  dateCreated: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
122
- dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
127
+ dateModified: stripMillisecondsFromTime(new Date(story["updated-at"]), timezone),
123
128
  datePublished: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
124
129
  name: (storyKeysPresence && story.headline) || "",
125
- image: generateArticleImageData(story["hero-image-s3-key"], publisherConfig),
130
+ image: generateArticleHeroImageData(story["hero-image-s3-key"], publisherConfig, story["hero-image-metadata"]),
126
131
  },
132
+ inLanguage && { inLanguage },
127
133
  isAccessibleForFree,
128
134
  sponsor,
129
135
  { isPartOf: generateIsPartOfDataForArticle(story, publisherConfig) },
@@ -131,6 +137,39 @@ function generateArticleData(structuredData = {}, story = {}, publisherConfig =
131
137
  );
132
138
  }
133
139
 
140
+ function generateArticleHeroImageData(image, publisherConfig = {}, imageMetadata = {}) {
141
+ const imageWidth = 1200;
142
+ const imageHeights = [675, 900, 1200];
143
+ const hasFocusPoint = get(imageMetadata, ["focus-point"], null);
144
+
145
+ const focusedImage = hasFocusPoint ? new FocusedImage(image, imageMetadata) : null;
146
+
147
+ return imageHeights.map((height) => {
148
+ let croppedImage = "";
149
+ const imageSrc = /^https?.*/.test(publisherConfig["cdn-image"])
150
+ ? publisherConfig["cdn-image"]
151
+ : `https://${publisherConfig["cdn-image"]}`;
152
+
153
+ if (focusedImage) {
154
+ const path = focusedImage.path([imageWidth, height], {
155
+ w: imageWidth,
156
+ h: height,
157
+ auto: "format,compress",
158
+ fit: "crop",
159
+ });
160
+ croppedImage = `${imageSrc}/${path}`;
161
+ }
162
+ const finalUrl = focusedImage ? croppedImage : imageUrl(publisherConfig, image, imageWidth, height);
163
+ return Object.assign(
164
+ {
165
+ "@type": "ImageObject",
166
+ url: finalUrl,
167
+ },
168
+ getQueryParams(finalUrl)
169
+ );
170
+ });
171
+ }
172
+
134
173
  function generateArticleImageData(image, publisherConfig = {}) {
135
174
  const imageWidth = 1200;
136
175
  const imageHeight = 675;
@@ -221,7 +260,6 @@ function generateNewsArticleData(structuredData = {}, story = {}, publisherConfi
221
260
  {},
222
261
  {
223
262
  alternativeHeadline: alternative.home && alternative.home.default ? alternative.home.default.headline : "",
224
- description: story.summary,
225
263
  },
226
264
  sponsor,
227
265
  isAccessibleForFree,
@@ -262,7 +300,7 @@ function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherC
262
300
  author: authorData(story.authors, authorSchema, publisherConfig),
263
301
  coverageEndTime: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
264
302
  coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"]), timezone),
265
- dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"]), timezone),
303
+ dateModified: stripMillisecondsFromTime(new Date(story["updated-at"]), timezone),
266
304
  mainEntityOfPage,
267
305
  publisher,
268
306
 
@@ -117,9 +117,7 @@ describe("Seo Helpers", function () {
117
117
  name: "abc",
118
118
  url: "abc.com",
119
119
  logo: {
120
- "@context": "http://schema.org",
121
120
  "@type": "ImageObject",
122
- author: "abc",
123
121
  contentUrl: "https://quintype.com/abc.png?w=300&h=300",
124
122
  url: "https://quintype.com/abc.png?w=300&h=300",
125
123
  name: "logo",
@@ -178,9 +176,7 @@ describe("Seo Helpers", function () {
178
176
  name: "abc",
179
177
  url: "abc.com",
180
178
  logo: {
181
- "@context": "http://schema.org",
182
179
  "@type": "ImageObject",
183
- author: "abc",
184
180
  contentUrl: "https://quintype.com/abc.png?w=300&h=300",
185
181
  url: "https://quintype.com/abc.png?w=300&h=300",
186
182
  name: "logo",
@@ -240,9 +236,7 @@ describe("Seo Helpers", function () {
240
236
  name: "abc",
241
237
  url: "abc.com",
242
238
  logo: {
243
- "@context": "http://schema.org",
244
239
  "@type": "ImageObject",
245
- author: "Abc",
246
240
  contentUrl: "https://quintype.com/abc.png",
247
241
  url: "https://quintype.com/abc.png",
248
242
  name: "logo",