@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 +2 -0
- package/package.json +2 -2
- package/src/components/EnhancedProduct.astro +286 -0
- package/src/components/Product.astro +3 -1
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
|
@@ -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 = {
|