@thg-altitude/schemaorg 1.0.23 → 1.0.24

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/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import Product from './src/components/Product.astro'
2
+ import EnhancedProduct from './src/components/EnhancedProduct.astro'
2
3
  import Breadcrumb from './src/components/Breadcrumb.astro'
3
4
  import CollectionPage from './src/components/CollectionPage.astro'
4
5
  import Organization from './src/components/Organization.astro'
@@ -7,6 +8,7 @@ import WebSite from './src/components/WebSite.astro'
7
8
 
8
9
  export {
9
10
  Product,
11
+ EnhancedProduct,
10
12
  Breadcrumb,
11
13
  CollectionPage,
12
14
  Organization,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thg-altitude/schemaorg",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -13,4 +13,4 @@
13
13
  "peerDependencies": {
14
14
  "astro": "^4.0.8 || ^5.0.0"
15
15
  }
16
- }
16
+ }
@@ -0,0 +1,286 @@
1
+ ---
2
+ const {
3
+ image,
4
+ sku,
5
+ name,
6
+ gtin13,
7
+ description,
8
+ variants,
9
+ currency,
10
+ brand,
11
+ urlPath,
12
+ reviews,
13
+ reviewsCount,
14
+ reviewsAverage,
15
+ breadcrumbs,
16
+ country,
17
+ faqs,
18
+ keywords,
19
+ saleEventTitle,
20
+ saleEventDescription,
21
+ saleEventStartDate,
22
+ saleEventEndDate,
23
+ streetAddress,
24
+ addressLocality,
25
+ postalCode,
26
+ } = Astro.props;
27
+
28
+ let url;
29
+ let schema = null;
30
+ let schemaError = false;
31
+ let offersArray = [];
32
+ let graphArray = [];
33
+ let imageUrl;
34
+ let pageUrl;
35
+
36
+ try {
37
+ url = import.meta.env.DEV
38
+ ? Astro.url.origin
39
+ : `${Astro.request.headers.get("X-forwarded-Proto")}://${Astro.request.headers.get("X-forwarded-Host")?.split(", ")[0]}`;
40
+ imageUrl = image
41
+ ? `${import.meta.env.IMAGE_PROXY_URL}?url=${image}&format=webp&width=1500&height=1500&fit=cover`
42
+ : null;
43
+ pageUrl = urlPath ? `${url}${urlPath}` : url;
44
+
45
+ if (!name || !sku || !image) {
46
+ throw new Error("Invalid data. Unable to produce Product Schema");
47
+ }
48
+
49
+ generateProductSchema();
50
+ generateSaleEventSchema();
51
+ generateBreadcrumbListSchema();
52
+ generateFaqPageSchema();
53
+
54
+ if (graphArray.length > 0) {
55
+ schema = {
56
+ "@context": "https://schema.org/",
57
+ "@graph": graphArray,
58
+ };
59
+ }
60
+ } catch (e) {
61
+ console.error("Invalid data. Unable to produce Product Schema");
62
+ schemaError = true;
63
+ }
64
+
65
+ function generateProductSchema() {
66
+ if (variants && variants.length > 0 && currency) {
67
+ offersArray = variants
68
+ .filter((variant) => variant?.sku && variant?.price?.price?.amount)
69
+ .map((variant) => ({
70
+ "@type": "Offer",
71
+ sku: variant.sku.toString(),
72
+ url:
73
+ variants.length > 1
74
+ ? `${pageUrl}?variation=${variant?.sku}`
75
+ : pageUrl,
76
+ price: variant.price.price.amount,
77
+ priceCurrency: currency,
78
+ itemCondition: "http://schema.org/NewCondition",
79
+ availability: variant?.inStock
80
+ ? "https://schema.org/InStock"
81
+ : "https://schema.org/OutOfStock",
82
+ ...(variant?.barcode && { gtin13: variant.barcode.toString() }),
83
+ ...(brand && {
84
+ seller: {
85
+ "@type": "Organization",
86
+ name: brand,
87
+ },
88
+ }),
89
+ priceSpecification: [
90
+ {
91
+ "@type": "UnitPriceSpecification",
92
+ priceCurrency: currency,
93
+ price: variant.price.price.amount,
94
+ valueAddedTaxIncluded: true,
95
+ },
96
+ ...(variant?.price?.rrp?.amount
97
+ ? [
98
+ {
99
+ "@type": "UnitPriceSpecification",
100
+ priceCurrency: currency,
101
+ price: variant.price.rrp.amount,
102
+ valueAddedTaxIncluded: true,
103
+ priceType: "https://schema.org/ListPrice",
104
+ },
105
+ ]
106
+ : []),
107
+ ],
108
+ }));
109
+ }
110
+
111
+ const category =
112
+ breadcrumbs?.length > 2
113
+ ? breadcrumbs?.[breadcrumbs.length - 2]?.displayName
114
+ : null;
115
+
116
+ const reviewsArray = generateReviewsSchema();
117
+
118
+ if (name && sku) {
119
+ const productSchema = {
120
+ "@type": "Product",
121
+ "@id": pageUrl,
122
+ url: pageUrl,
123
+ sku: sku.toString(),
124
+ name: name,
125
+ ...(gtin13 && { gtin13: gtin13.toString() }),
126
+ ...(description && { description }),
127
+ ...(imageUrl && { image: imageUrl }),
128
+ ...(brand && {
129
+ brand: {
130
+ "@type": "Brand",
131
+ name: brand,
132
+ },
133
+ }),
134
+ ...(reviewsCount > 0 &&
135
+ reviewsAverage && {
136
+ aggregateRating: {
137
+ "@type": "AggregateRating",
138
+ ratingValue: reviewsAverage,
139
+ reviewCount: reviewsCount,
140
+ bestRating: 5,
141
+ worstRating: 1,
142
+ },
143
+ }),
144
+ ...(reviewsArray && reviewsArray.length > 0 && { review: reviewsArray }),
145
+ ...(category && { category }),
146
+ ...(keywords && { keywords }),
147
+ ...(offersArray.length > 0 && { offers: offersArray }),
148
+ };
149
+
150
+ graphArray.push(productSchema);
151
+ }
152
+ }
153
+
154
+ function generateSaleEventSchema() {
155
+ if (saleEventTitle && saleEventDescription && saleEventStartDate && brand) {
156
+ let saleEventSchema = {
157
+ "@type": "SaleEvent",
158
+ name: saleEventTitle,
159
+ url: pageUrl,
160
+ description: saleEventDescription,
161
+ ...(imageUrl && { image: imageUrl }),
162
+ eventStatus: "https://schema.org/EventScheduled",
163
+ eventAttendanceMode: "https://schema.org/OnlineEventAttendanceMode",
164
+ organizer: {
165
+ "@type": "Organization",
166
+ name: brand,
167
+ url: url,
168
+ },
169
+ location: {
170
+ "@type": "Place",
171
+ name: brand,
172
+ url: url,
173
+ },
174
+ startDate: saleEventStartDate,
175
+ ...(saleEventEndDate && { endDate: saleEventEndDate }),
176
+ };
177
+
178
+ if (streetAddress && addressLocality && postalCode && country) {
179
+ saleEventSchema.location.address = {
180
+ "@type": "PostalAddress",
181
+ streetAddress: streetAddress,
182
+ addressLocality: addressLocality,
183
+ postalCode: postalCode,
184
+ addressCountry: country,
185
+ };
186
+ }
187
+
188
+ offersArray.length > 0 && (saleEventSchema.offers = offersArray);
189
+
190
+ graphArray.push(saleEventSchema);
191
+ }
192
+ }
193
+
194
+ function generateBreadcrumbListSchema() {
195
+ if (breadcrumbs && breadcrumbs.length > 0) {
196
+ let breadcrumbArray = breadcrumbs
197
+ .filter((breadcrumb) => breadcrumb?.displayName && breadcrumb?.pagePath)
198
+ .map((breadcrumb, index) => ({
199
+ "@type": "ListItem",
200
+ position: index + 1,
201
+ name: breadcrumb.displayName,
202
+ item: `${url}${breadcrumb.pagePath}`,
203
+ }));
204
+
205
+ if (breadcrumbArray.length > 0) {
206
+ const breadcrumbListSchema = {
207
+ "@type": "BreadcrumbList",
208
+ itemListElement: breadcrumbArray,
209
+ };
210
+
211
+ graphArray.push(breadcrumbListSchema);
212
+ }
213
+ }
214
+ }
215
+
216
+ function generateFaqPageSchema() {
217
+ if (faqs && faqs.length > 0) {
218
+ let faqArray = faqs
219
+ .filter((faq) => faq?.title && faq?.body)
220
+ .map((faq) => ({
221
+ "@type": "Question",
222
+ name: faq.title,
223
+ acceptedAnswer: {
224
+ "@type": "Answer",
225
+ text: faq.body,
226
+ },
227
+ }));
228
+
229
+ if (faqArray.length > 0) {
230
+ const faqPageSchema = {
231
+ "@type": "FAQPage",
232
+ mainEntity: faqArray,
233
+ };
234
+ graphArray.push(faqPageSchema);
235
+ }
236
+ }
237
+ }
238
+
239
+ function generateReviewsSchema() {
240
+ if (reviews && reviews.length > 0 && name) {
241
+ let reviewsArray = reviews
242
+ .filter(
243
+ (review) => review?.elements && review?.authorName && review?.posted
244
+ )
245
+ .map((review) => {
246
+ const content = review?.elements?.find(
247
+ (el) => el.key === "content"
248
+ )?.value;
249
+ const score = review?.elements?.find((el) => el.key === "score")?.score;
250
+
251
+ if (!content || !score) return;
252
+
253
+ return {
254
+ "@type": "Review",
255
+ description: content,
256
+ datePublished: review.posted,
257
+ itemReviewed: {
258
+ "@id": pageUrl,
259
+ },
260
+ reviewRating: {
261
+ "@type": "Rating",
262
+ worstRating: 1,
263
+ bestRating: 5,
264
+ ratingValue: score,
265
+ },
266
+ author: {
267
+ "@type": "Person",
268
+ name: review.authorName,
269
+ },
270
+ };
271
+ });
272
+
273
+ if (reviewsArray && reviewsArray.length > 0) {
274
+ return reviewsArray;
275
+ } else {
276
+ return null;
277
+ }
278
+ }
279
+ }
280
+ ---
281
+
282
+ {
283
+ schema && !schemaError && (
284
+ <script type="application/ld+json" set:html={JSON.stringify(schema)} />
285
+ )
286
+ }
@@ -38,7 +38,9 @@ Astro.props.reviews?.forEach((review)=>{
38
38
  let aggregateRating = Astro.props.reviewsCount > 0 ? {
39
39
  "@type": "AggregateRating",
40
40
  "ratingValue": Astro.props.reviewsAverage,
41
- "reviewCount": Astro.props.reviewsCount
41
+ "reviewCount": Astro.props.reviewsCount,
42
+ bestRating: 5,
43
+ worstRating: 1,
42
44
  } : undefined
43
45
 
44
46
  let schema = {