@websolutespa/bom-mixer-models 1.8.0 → 1.8.2

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
@@ -1,5 +1,17 @@
1
1
  # @websolutespa/bom-mixer-models
2
2
 
3
+ ## 1.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Modified: SiteMapService, StructuredData
8
+
9
+ ## 1.8.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Modified: getErrorPageLayout
14
+
3
15
  ## 1.8.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -257,11 +257,14 @@ type StructuredDataAddress = {
257
257
  streetAddress: string;
258
258
  };
259
259
  type StructuredDataPlace = {
260
+ '@context'?: 'https://schema.org';
260
261
  '@type': 'Place';
261
262
  name: string;
262
263
  address: StructuredDataAddress;
264
+ url: string;
263
265
  };
264
266
  type StructuredDataOrganization = {
267
+ '@context'?: 'https://schema.org';
265
268
  '@type': 'Organization';
266
269
  address?: StructuredDataAddress;
267
270
  alumni?: StructuredDataPerson[];
@@ -270,16 +273,27 @@ type StructuredDataOrganization = {
270
273
  member?: StructuredDataOrganization[];
271
274
  name: string;
272
275
  telephone?: string;
276
+ brand?: StructuredDataBrand;
273
277
  url?: string;
274
278
  };
279
+ type StructuredDataProduct = {
280
+ '@context'?: 'https://schema.org';
281
+ '@type': 'Product';
282
+ name: string;
283
+ description?: string;
284
+ image?: string;
285
+ brand: StructuredDataBrand;
286
+ url: string;
287
+ };
275
288
  type StructuredDataArticle = {
276
- '@context': 'https://schema.org';
289
+ '@context'?: 'https://schema.org';
277
290
  '@type': 'Article' | 'BlogPosting' | 'NewsArticle';
278
291
  author: (StructuredDataPerson | StructuredDataOrganization)[];
279
292
  dateModified: Date | string;
280
293
  datePublished: Date | string;
281
294
  headline: string;
282
295
  image: string[];
296
+ url: string;
283
297
  };
284
298
  type StructuredDataEvent = {
285
299
  '@context': 'https://schema.org';
@@ -293,11 +307,30 @@ type StructuredDataEvent = {
293
307
  offers?: StructuredDataOffer;
294
308
  performer?: StructuredDataPerformingGroup;
295
309
  organizer?: StructuredDataOrganization;
310
+ url: string;
311
+ };
312
+ type StructuredDataBrand = {
313
+ '@context'?: 'https://schema.org';
314
+ '@type': 'Brand';
315
+ name: string;
316
+ description?: string;
317
+ logo?: string;
318
+ url: string;
319
+ };
320
+ type IBrand = {
321
+ name: string;
322
+ description?: string;
323
+ logo?: string;
324
+ url?: string;
296
325
  };
297
- type StructuredDataKeys = 'article';
326
+ type StructuredDataKeys = 'product' | 'article' | 'contact' | 'brand';
298
327
  type StructuredDataCollections = Record<StructuredDataKeys, string[]>;
299
328
  declare function resolveTemplate(value: any): string | undefined;
300
329
  declare function resolveMediaSrc(media: IMedia): string | undefined;
301
- declare function resolveStructuredData(page: IPage, websiteName?: string, types?: StructuredDataCollections): string | undefined;
330
+ declare function resolveStructuredDataProduct(page: IPage, brand?: IBrand): StructuredDataProduct;
331
+ declare function resolveStructuredDataArticle(page: IPage, brand?: IBrand): StructuredDataArticle;
332
+ declare function resolveStructuredDataOrganization(page: IPage, brand?: IBrand): StructuredDataOrganization;
333
+ declare function resolveStructuredDataBrand(page: IPage, brand?: IBrand): StructuredDataBrand;
334
+ declare function resolveStructuredData(page: IPage, types?: Partial<StructuredDataCollections>, brand?: IBrand): string | undefined;
302
335
 
303
- export { IAddress, IAddressOptions, IAppProps, IApplication, IApplicationProps, ICompanyAddress, IFeatureType, ILazyComponent, ILazyComponentFunc, ILazyComponentProps, ILazyFuncProps, ILazyModules, ILazyProps, ILazyStaticProps, ILazyStaticPropsFunc, ILazyableProps, ILazyedProps, ILink, IModelStore, INullableLazyableProps, ISiteMap, LAZY_PROPS, NotFound, PartialPageProps, SeoWeight, StaticPath, StructuredDataAddress, StructuredDataArticle, StructuredDataCollections, StructuredDataEvent, StructuredDataKeys, StructuredDataOffer, StructuredDataOrganization, StructuredDataPerformingGroup, StructuredDataPerson, StructuredDataPlace, categoryToRouteLink, findManyPages, findOnePage, getAddressOptions, getBreadcrumbFromSegments, getCaptionsVttProps, getCategories, getCategory, getCountries, getCountry, getDecoratedComponents, getErrorPageLayout, getFeatureType, getFeatureTypes, getLabel, getLabels, getLayout, getLocale, getLocaleFromProps, getLocales, getMarket, getMarkets, getMenu, getMenus, getPage, getPageCategory, getPageProps, getPageRoutes, getPreviewProps, getProvince, getProvinces, getPublicUrl, getRedirect, getRedirects, getRegion, getRegions, getRoute, getRouteLinkTree, getRoutes, getRoutesForSchemas, getRoutesForTemplates, getSegments, getSeoWeight, getSiteMapIndex, getSiteMapIndexProps, getSiteMapXML, getSiteMapXMLProps, getSiteMapXSL, getSiteMapXSLProps, getStaticPathsForSchema, newRouteLink, redirectTo, resolveHref, resolveLabel, resolveMediaSrc, resolveRoute, resolveStructuredData, resolveTemplate, routeInterceptor, routeRevalidateHandler, routeToRouteLink, withLazyProps };
336
+ export { IAddress, IAddressOptions, IAppProps, IApplication, IApplicationProps, IBrand, ICompanyAddress, IFeatureType, ILazyComponent, ILazyComponentFunc, ILazyComponentProps, ILazyFuncProps, ILazyModules, ILazyProps, ILazyStaticProps, ILazyStaticPropsFunc, ILazyableProps, ILazyedProps, ILink, IModelStore, INullableLazyableProps, ISiteMap, LAZY_PROPS, NotFound, PartialPageProps, SeoWeight, StaticPath, StructuredDataAddress, StructuredDataArticle, StructuredDataBrand, StructuredDataCollections, StructuredDataEvent, StructuredDataKeys, StructuredDataOffer, StructuredDataOrganization, StructuredDataPerformingGroup, StructuredDataPerson, StructuredDataPlace, StructuredDataProduct, categoryToRouteLink, findManyPages, findOnePage, getAddressOptions, getBreadcrumbFromSegments, getCaptionsVttProps, getCategories, getCategory, getCountries, getCountry, getDecoratedComponents, getErrorPageLayout, getFeatureType, getFeatureTypes, getLabel, getLabels, getLayout, getLocale, getLocaleFromProps, getLocales, getMarket, getMarkets, getMenu, getMenus, getPage, getPageCategory, getPageProps, getPageRoutes, getPreviewProps, getProvince, getProvinces, getPublicUrl, getRedirect, getRedirects, getRegion, getRegions, getRoute, getRouteLinkTree, getRoutes, getRoutesForSchemas, getRoutesForTemplates, getSegments, getSeoWeight, getSiteMapIndex, getSiteMapIndexProps, getSiteMapXML, getSiteMapXMLProps, getSiteMapXSL, getSiteMapXSLProps, getStaticPathsForSchema, newRouteLink, redirectTo, resolveHref, resolveLabel, resolveMediaSrc, resolveRoute, resolveStructuredData, resolveStructuredDataArticle, resolveStructuredDataBrand, resolveStructuredDataOrganization, resolveStructuredDataProduct, resolveTemplate, routeInterceptor, routeRevalidateHandler, routeToRouteLink, withLazyProps };
package/dist/index.js CHANGED
@@ -79,6 +79,10 @@ __export(src_exports, {
79
79
  resolveMediaSrc: () => resolveMediaSrc,
80
80
  resolveRoute: () => resolveRoute,
81
81
  resolveStructuredData: () => resolveStructuredData,
82
+ resolveStructuredDataArticle: () => resolveStructuredDataArticle,
83
+ resolveStructuredDataBrand: () => resolveStructuredDataBrand,
84
+ resolveStructuredDataOrganization: () => resolveStructuredDataOrganization,
85
+ resolveStructuredDataProduct: () => resolveStructuredDataProduct,
82
86
  resolveTemplate: () => resolveTemplate,
83
87
  routeInterceptor: () => routeInterceptor,
84
88
  routeRevalidateHandler: () => routeRevalidateHandler,
@@ -621,16 +625,22 @@ async function getErrorPageLayout() {
621
625
  const layout = await getLayout(import_bom_core3.defaultMarket, import_bom_core3.defaultLocale);
622
626
  const title = resolveLabel(layout.labels, "notfound.title");
623
627
  const abstract = resolveLabel(layout.labels, "notfound.abstract");
628
+ const category = layout.tree ? {
629
+ id: layout.tree.category,
630
+ title: layout.tree.title,
631
+ href: layout.tree.href,
632
+ slug: ""
633
+ } : {
634
+ id: "homepage",
635
+ title: "Homepage",
636
+ slug: "",
637
+ href: "/"
638
+ };
624
639
  const page = {
625
640
  abstract,
626
641
  alternates: [],
627
642
  breadcrumb: [],
628
- category: {
629
- id: "homepage",
630
- title: "homepage",
631
- slug: "",
632
- href: ""
633
- },
643
+ category,
634
644
  slug: "",
635
645
  href: "",
636
646
  id: "notfound",
@@ -825,7 +835,7 @@ function getSiteMapXMLLastMod(canonical) {
825
835
  function getSiteMapXMLImage(canonical) {
826
836
  return canonical.media ? `
827
837
  <image:image>
828
- <image:loc>${canonical.media.src}</image:loc>
838
+ <image:loc>${canonical.media.src || canonical.media.url}</image:loc>
829
839
  </image:image>` : "";
830
840
  }
831
841
  function getSiteMapXMLAlternates(origin, canonical) {
@@ -1091,7 +1101,10 @@ var getSiteMapXSLProps = async (context) => {
1091
1101
  // src/structured_data/structured_data.ts
1092
1102
  var import_bom_core7 = require("@websolutespa/bom-core");
1093
1103
  var DefaultStructuredDataCollections = {
1094
- article: ["news_detail"]
1104
+ product: ["product_detail"],
1105
+ article: ["news_detail"],
1106
+ contact: ["contact"],
1107
+ brand: ["homepage"]
1095
1108
  };
1096
1109
  function resolveTemplate(value) {
1097
1110
  return (0, import_bom_core7.asEquatableString)(value);
@@ -1099,28 +1112,87 @@ function resolveTemplate(value) {
1099
1112
  function resolveMediaSrc(media) {
1100
1113
  return resolveHref(media.src || media.url);
1101
1114
  }
1102
- function resolveStructuredData(page, websiteName = "WebsiteName", types = DefaultStructuredDataCollections) {
1103
- let schema = null;
1115
+ function resolveStructuredDataProduct(page, brand) {
1116
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1117
+ const schema = {
1118
+ "@context": "https://schema.org",
1119
+ "@type": "Product",
1120
+ name: page.title,
1121
+ image: mediaSrc || "",
1122
+ brand: resolveStructuredDataBrand(page, brand),
1123
+ url: resolveHref(page.href)
1124
+ };
1125
+ return schema;
1126
+ }
1127
+ function resolveStructuredDataArticle(page, brand) {
1128
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1129
+ const schema = {
1130
+ "@context": "https://schema.org",
1131
+ "@type": "Article",
1132
+ headline: page.title,
1133
+ image: mediaSrc ? [mediaSrc] : [],
1134
+ datePublished: page.createdAt,
1135
+ dateModified: page.updatedAt,
1136
+ author: [
1137
+ resolveStructuredDataOrganization(page, brand)
1138
+ ],
1139
+ url: resolveHref(page.href)
1140
+ };
1141
+ return schema;
1142
+ }
1143
+ function resolveStructuredDataOrganization(page, brand = {
1144
+ name: "WebsiteName"
1145
+ }) {
1146
+ const schema = {
1147
+ "@context": "https://schema.org",
1148
+ "@type": "Organization",
1149
+ name: brand.name,
1150
+ brand: resolveStructuredDataBrand(page, brand),
1151
+ url: resolveHref(page.href)
1152
+ };
1153
+ return schema;
1154
+ }
1155
+ function resolveStructuredDataBrand(page, brand = {
1156
+ name: "WebsiteName"
1157
+ }) {
1158
+ const schema = {
1159
+ "@context": "https://schema.org",
1160
+ "@type": "Brand",
1161
+ name: brand.name,
1162
+ url: brand.url || getPublicUrl()
1163
+ };
1164
+ if (brand.description) {
1165
+ schema.description = brand.description;
1166
+ }
1167
+ if (brand.logo) {
1168
+ schema.logo = resolveHref(brand.logo);
1169
+ }
1170
+ return schema;
1171
+ }
1172
+ function resolveStructuredData(page, types, brand) {
1173
+ const structuredDataCollections = {
1174
+ ...DefaultStructuredDataCollections,
1175
+ ...types
1176
+ };
1104
1177
  let pageType = page.schema || "";
1105
1178
  if (page.template) {
1106
1179
  pageType = (0, import_bom_core7.asEquatableString)(page.template);
1107
1180
  }
1108
- if (types.article.includes(pageType)) {
1109
- const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1110
- schema = {
1111
- "@context": "https://schema.org",
1112
- "@type": "NewsArticle",
1113
- headline: page.title,
1114
- image: mediaSrc ? [mediaSrc] : [],
1115
- datePublished: page.createdAt,
1116
- dateModified: page.updatedAt,
1117
- author: [{
1118
- "@type": "Person",
1119
- name: websiteName,
1120
- url: getPublicUrl()
1121
- }]
1122
- };
1123
- }
1181
+ const schema = Object.entries(structuredDataCollections).reduce((p, kv) => {
1182
+ if (p) {
1183
+ return p;
1184
+ }
1185
+ switch (kv[0]) {
1186
+ case "product":
1187
+ return kv[1].includes(pageType) ? resolveStructuredDataProduct(page, brand) : null;
1188
+ case "article":
1189
+ return kv[1].includes(pageType) ? resolveStructuredDataArticle(page, brand) : null;
1190
+ case "contact":
1191
+ return kv[1].includes(pageType) ? resolveStructuredDataOrganization(page, brand) : null;
1192
+ default:
1193
+ return resolveStructuredDataBrand(page, brand);
1194
+ }
1195
+ }, null);
1124
1196
  if (schema) {
1125
1197
  return JSON.stringify(schema);
1126
1198
  }
@@ -1187,6 +1259,10 @@ function resolveStructuredData(page, websiteName = "WebsiteName", types = Defaul
1187
1259
  resolveMediaSrc,
1188
1260
  resolveRoute,
1189
1261
  resolveStructuredData,
1262
+ resolveStructuredDataArticle,
1263
+ resolveStructuredDataBrand,
1264
+ resolveStructuredDataOrganization,
1265
+ resolveStructuredDataProduct,
1190
1266
  resolveTemplate,
1191
1267
  routeInterceptor,
1192
1268
  routeRevalidateHandler,
package/dist/index.mjs CHANGED
@@ -532,16 +532,22 @@ async function getErrorPageLayout() {
532
532
  const layout = await getLayout(defaultMarket2, defaultLocale2);
533
533
  const title = resolveLabel(layout.labels, "notfound.title");
534
534
  const abstract = resolveLabel(layout.labels, "notfound.abstract");
535
+ const category = layout.tree ? {
536
+ id: layout.tree.category,
537
+ title: layout.tree.title,
538
+ href: layout.tree.href,
539
+ slug: ""
540
+ } : {
541
+ id: "homepage",
542
+ title: "Homepage",
543
+ slug: "",
544
+ href: "/"
545
+ };
535
546
  const page = {
536
547
  abstract,
537
548
  alternates: [],
538
549
  breadcrumb: [],
539
- category: {
540
- id: "homepage",
541
- title: "homepage",
542
- slug: "",
543
- href: ""
544
- },
550
+ category,
545
551
  slug: "",
546
552
  href: "",
547
553
  id: "notfound",
@@ -736,7 +742,7 @@ function getSiteMapXMLLastMod(canonical) {
736
742
  function getSiteMapXMLImage(canonical) {
737
743
  return canonical.media ? `
738
744
  <image:image>
739
- <image:loc>${canonical.media.src}</image:loc>
745
+ <image:loc>${canonical.media.src || canonical.media.url}</image:loc>
740
746
  </image:image>` : "";
741
747
  }
742
748
  function getSiteMapXMLAlternates(origin, canonical) {
@@ -1002,7 +1008,10 @@ var getSiteMapXSLProps = async (context) => {
1002
1008
  // src/structured_data/structured_data.ts
1003
1009
  import { asEquatableString } from "@websolutespa/bom-core";
1004
1010
  var DefaultStructuredDataCollections = {
1005
- article: ["news_detail"]
1011
+ product: ["product_detail"],
1012
+ article: ["news_detail"],
1013
+ contact: ["contact"],
1014
+ brand: ["homepage"]
1006
1015
  };
1007
1016
  function resolveTemplate(value) {
1008
1017
  return asEquatableString(value);
@@ -1010,28 +1019,87 @@ function resolveTemplate(value) {
1010
1019
  function resolveMediaSrc(media) {
1011
1020
  return resolveHref(media.src || media.url);
1012
1021
  }
1013
- function resolveStructuredData(page, websiteName = "WebsiteName", types = DefaultStructuredDataCollections) {
1014
- let schema = null;
1022
+ function resolveStructuredDataProduct(page, brand) {
1023
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1024
+ const schema = {
1025
+ "@context": "https://schema.org",
1026
+ "@type": "Product",
1027
+ name: page.title,
1028
+ image: mediaSrc || "",
1029
+ brand: resolveStructuredDataBrand(page, brand),
1030
+ url: resolveHref(page.href)
1031
+ };
1032
+ return schema;
1033
+ }
1034
+ function resolveStructuredDataArticle(page, brand) {
1035
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1036
+ const schema = {
1037
+ "@context": "https://schema.org",
1038
+ "@type": "Article",
1039
+ headline: page.title,
1040
+ image: mediaSrc ? [mediaSrc] : [],
1041
+ datePublished: page.createdAt,
1042
+ dateModified: page.updatedAt,
1043
+ author: [
1044
+ resolveStructuredDataOrganization(page, brand)
1045
+ ],
1046
+ url: resolveHref(page.href)
1047
+ };
1048
+ return schema;
1049
+ }
1050
+ function resolveStructuredDataOrganization(page, brand = {
1051
+ name: "WebsiteName"
1052
+ }) {
1053
+ const schema = {
1054
+ "@context": "https://schema.org",
1055
+ "@type": "Organization",
1056
+ name: brand.name,
1057
+ brand: resolveStructuredDataBrand(page, brand),
1058
+ url: resolveHref(page.href)
1059
+ };
1060
+ return schema;
1061
+ }
1062
+ function resolveStructuredDataBrand(page, brand = {
1063
+ name: "WebsiteName"
1064
+ }) {
1065
+ const schema = {
1066
+ "@context": "https://schema.org",
1067
+ "@type": "Brand",
1068
+ name: brand.name,
1069
+ url: brand.url || getPublicUrl()
1070
+ };
1071
+ if (brand.description) {
1072
+ schema.description = brand.description;
1073
+ }
1074
+ if (brand.logo) {
1075
+ schema.logo = resolveHref(brand.logo);
1076
+ }
1077
+ return schema;
1078
+ }
1079
+ function resolveStructuredData(page, types, brand) {
1080
+ const structuredDataCollections = {
1081
+ ...DefaultStructuredDataCollections,
1082
+ ...types
1083
+ };
1015
1084
  let pageType = page.schema || "";
1016
1085
  if (page.template) {
1017
1086
  pageType = asEquatableString(page.template);
1018
1087
  }
1019
- if (types.article.includes(pageType)) {
1020
- const mediaSrc = page.media ? resolveMediaSrc(page.media) : void 0;
1021
- schema = {
1022
- "@context": "https://schema.org",
1023
- "@type": "NewsArticle",
1024
- headline: page.title,
1025
- image: mediaSrc ? [mediaSrc] : [],
1026
- datePublished: page.createdAt,
1027
- dateModified: page.updatedAt,
1028
- author: [{
1029
- "@type": "Person",
1030
- name: websiteName,
1031
- url: getPublicUrl()
1032
- }]
1033
- };
1034
- }
1088
+ const schema = Object.entries(structuredDataCollections).reduce((p, kv) => {
1089
+ if (p) {
1090
+ return p;
1091
+ }
1092
+ switch (kv[0]) {
1093
+ case "product":
1094
+ return kv[1].includes(pageType) ? resolveStructuredDataProduct(page, brand) : null;
1095
+ case "article":
1096
+ return kv[1].includes(pageType) ? resolveStructuredDataArticle(page, brand) : null;
1097
+ case "contact":
1098
+ return kv[1].includes(pageType) ? resolveStructuredDataOrganization(page, brand) : null;
1099
+ default:
1100
+ return resolveStructuredDataBrand(page, brand);
1101
+ }
1102
+ }, null);
1035
1103
  if (schema) {
1036
1104
  return JSON.stringify(schema);
1037
1105
  }
@@ -1097,6 +1165,10 @@ export {
1097
1165
  resolveMediaSrc,
1098
1166
  resolveRoute,
1099
1167
  resolveStructuredData,
1168
+ resolveStructuredDataArticle,
1169
+ resolveStructuredDataBrand,
1170
+ resolveStructuredDataOrganization,
1171
+ resolveStructuredDataProduct,
1100
1172
  resolveTemplate,
1101
1173
  routeInterceptor,
1102
1174
  routeRevalidateHandler,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@websolutespa/bom-mixer-models",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "Mixer Models module of the BOM Repository",
5
5
  "keywords": [
6
6
  "bom",
@@ -157,21 +157,28 @@ export async function getPageCategory<T extends ICategorized>(schema: string, pa
157
157
 
158
158
  export async function getErrorPageLayout(): Promise<{ layout: ILayout, page: IPage }> {
159
159
  const layout = await getLayout(defaultMarket, defaultLocale);
160
- // console.log('getErrorPageLayout', layout);
160
+ // console.log('getErrorPageLayout', 'layout', layout);
161
161
  const title = resolveLabel(layout.labels, 'notfound.title');
162
- // console.log('title', title);
162
+ // console.log('getErrorPageLayout', 'title', title);
163
163
  const abstract = resolveLabel(layout.labels, 'notfound.abstract');
164
- // console.log('abstract', abstract);
164
+ // console.log('getErrorPageLayout', 'abstract', abstract);
165
+ const category = layout.tree ? {
166
+ id: layout.tree.category,
167
+ title: layout.tree.title,
168
+ href: layout.tree.href,
169
+ slug: '',
170
+ } : {
171
+ id: 'homepage',
172
+ title: 'Homepage',
173
+ slug: '',
174
+ href: '/',
175
+ };
176
+ // console.log('getErrorPageLayout', 'category', category);
165
177
  const page: IPage = {
166
178
  abstract,
167
179
  alternates: [],
168
180
  breadcrumb: [],
169
- category: {
170
- id: 'homepage',
171
- title: 'homepage',
172
- slug: '',
173
- href: '',
174
- },
181
+ category,
175
182
  slug: '',
176
183
  href: '',
177
184
  id: 'notfound',
@@ -187,7 +194,7 @@ export async function getErrorPageLayout(): Promise<{ layout: ILayout, page: IPa
187
194
  schema: 'notfound' as SchemaType,
188
195
  title,
189
196
  };
190
- // console.log('page', page);
197
+ // console.log('getErrorPageLayout', 'page', page);
191
198
  return { layout, page };
192
199
  }
193
200
 
@@ -46,7 +46,7 @@ function getSiteMapXMLLastMod(canonical: IRouteCanonical): string {
46
46
  function getSiteMapXMLImage(canonical: IRouteCanonical): string {
47
47
  return canonical.media ? `
48
48
  <image:image>
49
- <image:loc>${canonical.media.src}</image:loc>
49
+ <image:loc>${canonical.media.src || canonical.media.url}</image:loc>
50
50
  </image:image>` : '';
51
51
  }
52
52
 
@@ -31,12 +31,15 @@ export type StructuredDataAddress = {
31
31
  };
32
32
 
33
33
  export type StructuredDataPlace = {
34
+ '@context'?: 'https://schema.org';
34
35
  '@type': 'Place';
35
36
  name: string;
36
37
  address: StructuredDataAddress;
38
+ url: string;
37
39
  };
38
40
 
39
41
  export type StructuredDataOrganization = {
42
+ '@context'?: 'https://schema.org';
40
43
  '@type': 'Organization';
41
44
  address?: StructuredDataAddress;
42
45
  alumni?: StructuredDataPerson[];
@@ -45,17 +48,29 @@ export type StructuredDataOrganization = {
45
48
  member?: StructuredDataOrganization[];
46
49
  name: string;
47
50
  telephone?: string;
51
+ brand?: StructuredDataBrand;
48
52
  url?: string;
49
53
  };
50
54
 
55
+ export type StructuredDataProduct = {
56
+ '@context'?: 'https://schema.org';
57
+ '@type': 'Product';
58
+ name: string;
59
+ description?: string;
60
+ image?: string;
61
+ brand: StructuredDataBrand;
62
+ url: string;
63
+ };
64
+
51
65
  export type StructuredDataArticle = {
52
- '@context': 'https://schema.org';
66
+ '@context'?: 'https://schema.org';
53
67
  '@type': 'Article' | 'BlogPosting' | 'NewsArticle';
54
68
  author: (StructuredDataPerson | StructuredDataOrganization)[];
55
69
  dateModified: Date | string;
56
70
  datePublished: Date | string;
57
71
  headline: string;
58
72
  image: string[];
73
+ url: string;
59
74
  };
60
75
 
61
76
  export type StructuredDataEvent = {
@@ -74,13 +89,33 @@ export type StructuredDataEvent = {
74
89
  offers?: StructuredDataOffer,
75
90
  performer?: StructuredDataPerformingGroup,
76
91
  organizer?: StructuredDataOrganization;
92
+ url: string;
77
93
  };
78
94
 
79
- export type StructuredDataKeys = 'article';
95
+ export type StructuredDataBrand = {
96
+ '@context'?: 'https://schema.org';
97
+ '@type': 'Brand';
98
+ name: string;
99
+ description?: string;
100
+ logo?: string;
101
+ url: string;
102
+ };
103
+
104
+ export type IBrand = {
105
+ name: string;
106
+ description?: string;
107
+ logo?: string;
108
+ url?: string;
109
+ };
110
+
111
+ export type StructuredDataKeys = 'product' | 'article' | 'contact' | 'brand';
80
112
  export type StructuredDataCollections = Record<StructuredDataKeys, string[]>;
81
113
 
82
114
  const DefaultStructuredDataCollections: StructuredDataCollections = {
115
+ product: ['product_detail'],
83
116
  article: ['news_detail'],
117
+ contact: ['contact'],
118
+ brand: ['homepage'],
84
119
  };
85
120
 
86
121
  export function resolveTemplate(value: any): string | undefined {
@@ -91,32 +126,107 @@ export function resolveMediaSrc(media: IMedia): string | undefined {
91
126
  return resolveHref(media.src || media.url);
92
127
  }
93
128
 
129
+ export function resolveStructuredDataProduct(
130
+ page: IPage,
131
+ brand?: IBrand
132
+ ): StructuredDataProduct {
133
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : undefined;
134
+ const schema: StructuredDataProduct = {
135
+ '@context': 'https://schema.org',
136
+ '@type': 'Product',
137
+ name: page.title,
138
+ image: mediaSrc || '',
139
+ brand: resolveStructuredDataBrand(page, brand),
140
+ url: resolveHref(page.href),
141
+ };
142
+ return schema;
143
+ }
144
+
145
+ export function resolveStructuredDataArticle(
146
+ page: IPage,
147
+ brand?: IBrand
148
+ ): StructuredDataArticle {
149
+ const mediaSrc = page.media ? resolveMediaSrc(page.media) : undefined;
150
+ const schema: StructuredDataArticle = {
151
+ '@context': 'https://schema.org',
152
+ '@type': 'Article',
153
+ headline: page.title,
154
+ image: mediaSrc ? [mediaSrc] : [],
155
+ datePublished: page.createdAt,
156
+ dateModified: page.updatedAt,
157
+ author: [
158
+ resolveStructuredDataOrganization(page, brand),
159
+ ],
160
+ url: resolveHref(page.href),
161
+ } as StructuredDataArticle;
162
+ return schema;
163
+ }
164
+
165
+ export function resolveStructuredDataOrganization(
166
+ page: IPage,
167
+ brand: IBrand = {
168
+ name: 'WebsiteName',
169
+ }
170
+ ): StructuredDataOrganization {
171
+ const schema: StructuredDataOrganization = {
172
+ '@context': 'https://schema.org',
173
+ '@type': 'Organization',
174
+ name: brand.name,
175
+ brand: resolveStructuredDataBrand(page, brand),
176
+ url: resolveHref(page.href),
177
+ };
178
+ return schema;
179
+ }
180
+
181
+ export function resolveStructuredDataBrand(
182
+ page: IPage,
183
+ brand: IBrand = {
184
+ name: 'WebsiteName',
185
+ }
186
+ ): StructuredDataBrand {
187
+ const schema: StructuredDataBrand = {
188
+ '@context': 'https://schema.org',
189
+ '@type': 'Brand',
190
+ name: brand.name,
191
+ url: brand.url || getPublicUrl(),
192
+ };
193
+ if (brand.description) {
194
+ schema.description = brand.description;
195
+ }
196
+ if (brand.logo) {
197
+ schema.logo = resolveHref(brand.logo);
198
+ }
199
+ return schema;
200
+ }
201
+
94
202
  export function resolveStructuredData(
95
203
  page: IPage,
96
- websiteName = 'WebsiteName',
97
- types: StructuredDataCollections = DefaultStructuredDataCollections
204
+ types?: Partial<StructuredDataCollections>,
205
+ brand?: IBrand
98
206
  ): string | undefined {
99
- let schema: {} | null = null;
207
+ const structuredDataCollections: StructuredDataCollections = {
208
+ ...DefaultStructuredDataCollections,
209
+ ...types,
210
+ };
100
211
  let pageType = page.schema || '';
101
212
  if (page.template) {
102
213
  pageType = asEquatableString(page.template);
103
214
  }
104
- if (types.article.includes(pageType)) {
105
- const mediaSrc = page.media ? resolveMediaSrc(page.media) : undefined;
106
- schema = {
107
- '@context': 'https://schema.org',
108
- '@type': 'NewsArticle',
109
- headline: page.title,
110
- image: mediaSrc ? [mediaSrc] : [],
111
- datePublished: page.createdAt,
112
- dateModified: page.updatedAt,
113
- author: [{
114
- '@type': 'Person',
115
- name: websiteName,
116
- url: getPublicUrl(),
117
- }],
118
- } as StructuredDataArticle;
119
- }
215
+ const schema = Object.entries(structuredDataCollections).reduce<null | {}>((p, kv) => {
216
+ if (p) {
217
+ return p;
218
+ }
219
+ switch (kv[0]) {
220
+ case 'product':
221
+ return kv[1].includes(pageType) ? resolveStructuredDataProduct(page, brand) : null;
222
+ case 'article':
223
+ return kv[1].includes(pageType) ? resolveStructuredDataArticle(page, brand) : null;
224
+ case 'contact':
225
+ return kv[1].includes(pageType) ? resolveStructuredDataOrganization(page, brand) : null;
226
+ default:
227
+ return resolveStructuredDataBrand(page, brand);
228
+ }
229
+ }, null);
120
230
  if (schema) {
121
231
  return JSON.stringify(schema);
122
232
  }