@masters-ws/react-seo 1.2.1 → 1.4.1

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.
@@ -0,0 +1,945 @@
1
+ // src/core/utils.ts
2
+ function cleanSchema(obj) {
3
+ if (Array.isArray(obj)) {
4
+ return obj.filter((item) => item !== void 0 && item !== null).map((item) => typeof item === "object" ? cleanSchema(item) : item);
5
+ }
6
+ const cleaned = {};
7
+ for (const [key, value] of Object.entries(obj)) {
8
+ if (value === void 0 || value === null) continue;
9
+ if (Array.isArray(value)) {
10
+ const cleanedArr = value.filter((item) => item !== void 0 && item !== null).map((item) => typeof item === "object" && item !== null ? cleanSchema(item) : item);
11
+ if (cleanedArr.length > 0) {
12
+ cleaned[key] = cleanedArr;
13
+ }
14
+ } else if (typeof value === "object") {
15
+ cleaned[key] = cleanSchema(value);
16
+ } else {
17
+ cleaned[key] = value;
18
+ }
19
+ }
20
+ return cleaned;
21
+ }
22
+ function validateSEO(schemaType, data, requiredFields) {
23
+ const warnings = [];
24
+ for (const field of requiredFields) {
25
+ const value = data[field];
26
+ if (value === void 0 || value === null || value === "") {
27
+ warnings.push(`[react-seo] Warning: "${field}" is missing in ${schemaType} schema. Google may not show rich results.`);
28
+ }
29
+ }
30
+ if (warnings.length > 0 && typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
31
+ warnings.forEach((w) => console.warn(w));
32
+ }
33
+ return warnings;
34
+ }
35
+
36
+ // src/core/schemas.ts
37
+ function generateOrganizationSchema(config) {
38
+ return cleanSchema({
39
+ "@context": "https://schema.org",
40
+ "@type": "Organization",
41
+ "name": config.name,
42
+ "url": config.url,
43
+ "logo": config.logo,
44
+ "description": config.description,
45
+ "sameAs": config.socialLinks || []
46
+ });
47
+ }
48
+ function generateWebSiteSchema(config) {
49
+ return cleanSchema({
50
+ "@context": "https://schema.org",
51
+ "@type": "WebSite",
52
+ "name": config.name,
53
+ "url": config.url,
54
+ "description": config.description,
55
+ "publisher": {
56
+ "@type": "Organization",
57
+ "name": config.name,
58
+ "logo": config.logo
59
+ },
60
+ "potentialAction": {
61
+ "@type": "SearchAction",
62
+ "target": `${config.url}/search?q={search_term_string}`,
63
+ "query-input": "required name=search_term_string"
64
+ }
65
+ });
66
+ }
67
+ function generateWebPageSchema(data, config) {
68
+ return cleanSchema({
69
+ "@context": "https://schema.org",
70
+ "@type": "WebPage",
71
+ "name": data.name,
72
+ "description": data.description,
73
+ "url": data.url,
74
+ "image": data.image,
75
+ "datePublished": data.datePublished,
76
+ "dateModified": data.dateModified,
77
+ "isPartOf": {
78
+ "@type": "WebSite",
79
+ "name": config.name,
80
+ "url": config.url
81
+ },
82
+ "breadcrumb": data.breadcrumb ? generateBreadcrumbSchema(data.breadcrumb) : void 0
83
+ });
84
+ }
85
+ function generateCollectionPageSchema(data, config) {
86
+ return cleanSchema({
87
+ "@context": "https://schema.org",
88
+ "@type": "CollectionPage",
89
+ "name": data.name,
90
+ "description": data.description,
91
+ "url": data.url,
92
+ "image": data.image,
93
+ "numberOfItems": data.numberOfItems,
94
+ "isPartOf": {
95
+ "@type": "WebSite",
96
+ "name": config.name,
97
+ "url": config.url
98
+ }
99
+ });
100
+ }
101
+ function generateItemListSchema(data) {
102
+ return cleanSchema({
103
+ "@context": "https://schema.org",
104
+ "@type": "ItemList",
105
+ "name": data.name,
106
+ "url": data.url,
107
+ "itemListOrder": data.itemListOrder === "ascending" ? "https://schema.org/ItemListOrderAscending" : data.itemListOrder === "descending" ? "https://schema.org/ItemListOrderDescending" : "https://schema.org/ItemListUnordered",
108
+ "numberOfItems": data.items.length,
109
+ "itemListElement": data.items.map((item, index) => ({
110
+ "@type": "ListItem",
111
+ "position": item.position || index + 1,
112
+ "name": item.name,
113
+ "url": item.url,
114
+ "image": item.image
115
+ }))
116
+ });
117
+ }
118
+ function generateArticleSchema(data, config) {
119
+ validateSEO("NewsArticle", data, ["title", "description", "image", "publishedTime", "author"]);
120
+ const org = generateOrganizationSchema(config);
121
+ return cleanSchema({
122
+ "@context": "https://schema.org",
123
+ "@type": "NewsArticle",
124
+ "headline": data.title,
125
+ "description": data.description,
126
+ "image": data.image,
127
+ "datePublished": data.publishedTime,
128
+ "dateModified": data.modifiedTime || data.publishedTime,
129
+ "mainEntityOfPage": data.url,
130
+ "wordCount": data.wordCount,
131
+ "author": data.author ? {
132
+ "@type": "Person",
133
+ "name": data.author.name,
134
+ "url": data.author.url
135
+ } : org,
136
+ "publisher": org
137
+ });
138
+ }
139
+ function generateProductSchema(data) {
140
+ validateSEO("Product", data, ["name", "description", "image", "price"]);
141
+ let offers;
142
+ if (data.variants && data.variants.length > 0) {
143
+ const prices = data.variants.map((v) => v.price);
144
+ offers = {
145
+ "@type": "AggregateOffer",
146
+ "lowPrice": Math.min(...prices),
147
+ "highPrice": Math.max(...prices),
148
+ "priceCurrency": data.currency || data.variants[0]?.currency || "USD",
149
+ "offerCount": data.variants.length,
150
+ "offers": data.variants.map((v) => cleanSchema({
151
+ "@type": "Offer",
152
+ "name": v.name,
153
+ "sku": v.sku,
154
+ "price": v.price,
155
+ "priceCurrency": v.currency || data.currency || "USD",
156
+ "availability": v.availability || data.availability || "https://schema.org/InStock",
157
+ "url": v.url || data.url,
158
+ "image": v.image,
159
+ "itemCondition": data.condition ? `https://schema.org/${data.condition}` : void 0
160
+ }))
161
+ };
162
+ } else {
163
+ offers = cleanSchema({
164
+ "@type": "Offer",
165
+ "url": data.url,
166
+ "priceCurrency": data.currency || "USD",
167
+ "price": data.price,
168
+ "availability": data.availability || "https://schema.org/InStock",
169
+ "itemCondition": data.condition ? `https://schema.org/${data.condition}` : void 0,
170
+ "seller": data.seller ? {
171
+ "@type": "Organization",
172
+ "name": data.seller.name,
173
+ "url": data.seller.url
174
+ } : void 0,
175
+ "hasMerchantReturnPolicy": data.returnPolicy ? cleanSchema({
176
+ "@type": "MerchantReturnPolicy",
177
+ "applicableCountry": data.shipping?.shippingDestination,
178
+ "returnPolicyCategory": data.returnPolicy.returnPolicyCategory ? `https://schema.org/${data.returnPolicy.returnPolicyCategory}` : "https://schema.org/MerchantReturnFiniteReturnWindow",
179
+ "merchantReturnDays": data.returnPolicy.returnWithin,
180
+ "returnMethod": data.returnPolicy.returnMethod ? `https://schema.org/${data.returnPolicy.returnMethod}` : void 0,
181
+ "returnFees": data.returnPolicy.returnFees ? `https://schema.org/${data.returnPolicy.returnFees}` : void 0
182
+ }) : void 0,
183
+ "shippingDetails": data.shipping ? cleanSchema({
184
+ "@type": "OfferShippingDetails",
185
+ "shippingRate": data.shipping.shippingRate ? {
186
+ "@type": "MonetaryAmount",
187
+ "value": data.shipping.shippingRate.value,
188
+ "currency": data.shipping.shippingRate.currency
189
+ } : void 0,
190
+ "shippingDestination": data.shipping.shippingDestination ? {
191
+ "@type": "DefinedRegion",
192
+ "addressCountry": data.shipping.shippingDestination
193
+ } : void 0,
194
+ "deliveryTime": data.shipping.deliveryTime ? {
195
+ "@type": "ShippingDeliveryTime",
196
+ "handlingTime": {
197
+ "@type": "QuantitativeValue",
198
+ "minValue": 0,
199
+ "maxValue": 1,
200
+ "unitCode": "DAY"
201
+ },
202
+ "transitTime": {
203
+ "@type": "QuantitativeValue",
204
+ "minValue": data.shipping.deliveryTime.minDays,
205
+ "maxValue": data.shipping.deliveryTime.maxDays,
206
+ "unitCode": "DAY"
207
+ }
208
+ } : void 0,
209
+ "freeShippingThreshold": data.shipping.freeShippingThreshold ? {
210
+ "@type": "MonetaryAmount",
211
+ "value": data.shipping.freeShippingThreshold,
212
+ "currency": data.shipping.shippingRate?.currency || data.currency || "USD"
213
+ } : void 0
214
+ }) : void 0
215
+ });
216
+ }
217
+ const reviewList = data.reviews?.map((r) => cleanSchema({
218
+ "@type": "Review",
219
+ "author": { "@type": "Person", "name": r.author },
220
+ "datePublished": r.datePublished,
221
+ "reviewBody": r.reviewBody,
222
+ "reviewRating": {
223
+ "@type": "Rating",
224
+ "ratingValue": r.ratingValue,
225
+ "bestRating": r.bestRating || 5
226
+ }
227
+ }));
228
+ return cleanSchema({
229
+ "@context": "https://schema.org",
230
+ "@type": "Product",
231
+ "name": data.name,
232
+ "description": data.description,
233
+ "image": data.image,
234
+ "sku": data.sku,
235
+ "gtin": data.gtin,
236
+ "mpn": data.mpn,
237
+ "brand": data.brand ? { "@type": "Brand", "name": data.brand } : void 0,
238
+ "offers": offers,
239
+ "aggregateRating": data.rating ? {
240
+ "@type": "AggregateRating",
241
+ "ratingValue": data.rating,
242
+ "reviewCount": data.reviewCount || 1
243
+ } : void 0,
244
+ "review": reviewList && reviewList.length > 0 ? reviewList : void 0
245
+ });
246
+ }
247
+ function generateFAQSchema(questions) {
248
+ return cleanSchema({
249
+ "@context": "https://schema.org",
250
+ "@type": "FAQPage",
251
+ "mainEntity": questions.map((item) => ({
252
+ "@type": "Question",
253
+ "name": item.q,
254
+ "acceptedAnswer": {
255
+ "@type": "Answer",
256
+ "text": item.a
257
+ }
258
+ }))
259
+ });
260
+ }
261
+ function generateBreadcrumbSchema(items) {
262
+ return cleanSchema({
263
+ "@context": "https://schema.org",
264
+ "@type": "BreadcrumbList",
265
+ "itemListElement": items.map((item, index) => ({
266
+ "@type": "ListItem",
267
+ "position": index + 1,
268
+ "name": item.name,
269
+ "item": item.item
270
+ }))
271
+ });
272
+ }
273
+ function generateVideoSchema(data) {
274
+ validateSEO("VideoObject", data, ["name", "description", "thumbnailUrl", "uploadDate"]);
275
+ return cleanSchema({
276
+ "@context": "https://schema.org",
277
+ "@type": "VideoObject",
278
+ "name": data.name,
279
+ "description": data.description,
280
+ "thumbnailUrl": data.thumbnailUrl,
281
+ "uploadDate": data.uploadDate,
282
+ "duration": data.duration,
283
+ "contentUrl": data.contentUrl,
284
+ "embedUrl": data.embedUrl
285
+ });
286
+ }
287
+ function generateEventSchema(data) {
288
+ const isOnline = data.location && "url" in data.location;
289
+ return cleanSchema({
290
+ "@context": "https://schema.org",
291
+ "@type": "Event",
292
+ "name": data.name,
293
+ "description": data.description,
294
+ "startDate": data.startDate,
295
+ "endDate": data.endDate,
296
+ "eventAttendanceMode": isOnline ? "https://schema.org/OnlineEventAttendanceMode" : "https://schema.org/OfflineEventAttendanceMode",
297
+ "location": isOnline ? {
298
+ "@type": "VirtualLocation",
299
+ "url": data.location.url
300
+ } : data.location ? {
301
+ "@type": "Place",
302
+ "name": data.location.name,
303
+ "address": data.location.address
304
+ } : void 0,
305
+ "image": data.image,
306
+ "offers": data.offers ? {
307
+ "@type": "Offer",
308
+ "price": data.offers.price,
309
+ "priceCurrency": data.offers.currency,
310
+ "url": data.offers.url
311
+ } : void 0
312
+ });
313
+ }
314
+ function generateLocalBusinessSchema(data) {
315
+ return cleanSchema({
316
+ "@context": "https://schema.org",
317
+ "@type": "LocalBusiness",
318
+ "name": data.name,
319
+ "description": data.description,
320
+ "image": data.image,
321
+ "telephone": data.telephone,
322
+ "address": {
323
+ "@type": "PostalAddress",
324
+ "streetAddress": data.address.street,
325
+ "addressLocality": data.address.city,
326
+ "addressRegion": data.address.region,
327
+ "postalCode": data.address.postalCode,
328
+ "addressCountry": data.address.country
329
+ },
330
+ "geo": data.geo ? {
331
+ "@type": "GeoCoordinates",
332
+ "latitude": data.geo.lat,
333
+ "longitude": data.geo.lng
334
+ } : void 0,
335
+ "openingHours": data.openingHours,
336
+ "priceRange": data.priceRange
337
+ });
338
+ }
339
+ function generateSoftwareSchema(data) {
340
+ return cleanSchema({
341
+ "@context": "https://schema.org",
342
+ "@type": "SoftwareApplication",
343
+ "name": data.name,
344
+ "description": data.description,
345
+ "operatingSystem": data.operatingSystem,
346
+ "applicationCategory": data.applicationCategory,
347
+ "offers": data.offers ? {
348
+ "@type": "Offer",
349
+ "price": data.offers.price,
350
+ "priceCurrency": data.offers.currency
351
+ } : void 0,
352
+ "aggregateRating": data.rating ? {
353
+ "@type": "AggregateRating",
354
+ "ratingValue": data.rating,
355
+ "reviewCount": data.reviewCount || 1
356
+ } : void 0,
357
+ "downloadUrl": data.downloadUrl,
358
+ "screenshot": data.screenshot
359
+ });
360
+ }
361
+ function generateBookSchema(data) {
362
+ return cleanSchema({
363
+ "@context": "https://schema.org",
364
+ "@type": "Book",
365
+ "name": data.name,
366
+ "author": typeof data.author === "string" ? {
367
+ "@type": "Person",
368
+ "name": data.author
369
+ } : {
370
+ "@type": "Person",
371
+ "name": data.author.name,
372
+ "url": data.author.url
373
+ },
374
+ "description": data.description,
375
+ "isbn": data.isbn,
376
+ "numberOfPages": data.numberOfPages,
377
+ "publisher": data.publisher ? {
378
+ "@type": "Organization",
379
+ "name": data.publisher
380
+ } : void 0,
381
+ "datePublished": data.datePublished,
382
+ "image": data.image,
383
+ "inLanguage": data.inLanguage,
384
+ "genre": data.genre
385
+ });
386
+ }
387
+ function generateMovieSchema(data) {
388
+ return cleanSchema({
389
+ "@context": "https://schema.org",
390
+ "@type": "Movie",
391
+ "name": data.name,
392
+ "description": data.description,
393
+ "image": data.image,
394
+ "director": data.director ? {
395
+ "@type": "Person",
396
+ "name": data.director
397
+ } : void 0,
398
+ "actor": data.actor?.map((name) => ({
399
+ "@type": "Person",
400
+ "name": name
401
+ })),
402
+ "dateCreated": data.dateCreated,
403
+ "duration": data.duration,
404
+ "genre": data.genre,
405
+ "aggregateRating": data.rating ? {
406
+ "@type": "AggregateRating",
407
+ "ratingValue": data.rating,
408
+ "reviewCount": data.reviewCount || 1
409
+ } : void 0
410
+ });
411
+ }
412
+ function generatePodcastSchema(data) {
413
+ return cleanSchema({
414
+ "@context": "https://schema.org",
415
+ "@type": "PodcastSeries",
416
+ "name": data.name,
417
+ "description": data.description,
418
+ "image": data.image,
419
+ "author": data.author ? {
420
+ "@type": "Person",
421
+ "name": data.author
422
+ } : void 0,
423
+ "webFeed": data.webFeed,
424
+ "url": data.url,
425
+ "genre": data.genre
426
+ });
427
+ }
428
+ function generatePodcastEpisodeSchema(data) {
429
+ return cleanSchema({
430
+ "@context": "https://schema.org",
431
+ "@type": "PodcastEpisode",
432
+ "name": data.name,
433
+ "description": data.description,
434
+ "datePublished": data.datePublished,
435
+ "timeRequired": data.duration,
436
+ "url": data.url,
437
+ "associatedMedia": data.audio ? {
438
+ "@type": "AudioObject",
439
+ "contentUrl": data.audio
440
+ } : void 0,
441
+ "partOfSeries": data.partOfSeries ? {
442
+ "@type": "PodcastSeries",
443
+ "name": data.partOfSeries.name,
444
+ "url": data.partOfSeries.url
445
+ } : void 0
446
+ });
447
+ }
448
+ function generateHowToSchema(data) {
449
+ return cleanSchema({
450
+ "@context": "https://schema.org",
451
+ "@type": "HowTo",
452
+ "name": data.name,
453
+ "description": data.description,
454
+ "image": data.image,
455
+ "totalTime": data.totalTime,
456
+ "estimatedCost": data.estimatedCost ? {
457
+ "@type": "MonetaryAmount",
458
+ "currency": data.estimatedCost.currency,
459
+ "value": data.estimatedCost.value
460
+ } : void 0,
461
+ "supply": data.supply?.map((s) => ({ "@type": "HowToSupply", "name": s })),
462
+ "tool": data.tool?.map((t) => ({ "@type": "HowToTool", "name": t })),
463
+ "step": data.steps.map((step, index) => {
464
+ if (typeof step === "string") {
465
+ return { "@type": "HowToStep", "position": index + 1, "text": step };
466
+ }
467
+ return cleanSchema({
468
+ "@type": "HowToStep",
469
+ "position": index + 1,
470
+ "name": step.name,
471
+ "text": step.text,
472
+ "image": step.image,
473
+ "url": step.url
474
+ });
475
+ })
476
+ });
477
+ }
478
+ function generateRecipeSchema(data) {
479
+ return cleanSchema({
480
+ "@context": "https://schema.org",
481
+ "@type": "Recipe",
482
+ "name": data.name,
483
+ "description": data.description,
484
+ "image": data.image,
485
+ "author": { "@type": "Person", "name": data.author },
486
+ "datePublished": data.publishedDate,
487
+ "prepTime": data.prepTime,
488
+ "cookTime": data.cookTime,
489
+ "totalTime": data.totalTime,
490
+ "recipeYield": data.recipeYield,
491
+ "recipeCategory": data.recipeCategory,
492
+ "recipeCuisine": data.recipeCuisine,
493
+ "recipeIngredient": data.ingredients,
494
+ "recipeInstructions": data.instructions.map((step) => cleanSchema({
495
+ "@type": "HowToStep",
496
+ "name": step.name,
497
+ "text": step.text,
498
+ "image": step.image
499
+ })),
500
+ "aggregateRating": data.rating ? {
501
+ "@type": "AggregateRating",
502
+ "ratingValue": data.rating,
503
+ "reviewCount": data.reviewCount || 1
504
+ } : void 0
505
+ });
506
+ }
507
+ function generateJobPostingSchema(data) {
508
+ return cleanSchema({
509
+ "@context": "https://schema.org",
510
+ "@type": "JobPosting",
511
+ "title": data.title,
512
+ "description": data.description,
513
+ "datePosted": data.datePosted,
514
+ "validThrough": data.validThrough,
515
+ "employmentType": data.employmentType,
516
+ "jobLocationType": data.remote ? "TELECOMMUTE" : void 0,
517
+ "hiringOrganization": {
518
+ "@type": "Organization",
519
+ "name": data.hiringOrganization.name,
520
+ "sameAs": data.hiringOrganization.sameAs,
521
+ "logo": data.hiringOrganization.logo
522
+ },
523
+ "jobLocation": {
524
+ "@type": "Place",
525
+ "address": {
526
+ "@type": "PostalAddress",
527
+ "streetAddress": data.jobLocation.streetAddress,
528
+ "addressLocality": data.jobLocation.addressLocality,
529
+ "addressRegion": data.jobLocation.addressRegion,
530
+ "postalCode": data.jobLocation.postalCode,
531
+ "addressCountry": data.jobLocation.addressCountry
532
+ }
533
+ },
534
+ "baseSalary": data.baseSalary ? {
535
+ "@type": "MonetaryAmount",
536
+ "currency": data.baseSalary.currency,
537
+ "value": typeof data.baseSalary.value === "number" ? {
538
+ "@type": "QuantitativeValue",
539
+ "value": data.baseSalary.value,
540
+ "unitText": data.baseSalary.unitText || "MONTH"
541
+ } : {
542
+ "@type": "QuantitativeValue",
543
+ "minValue": data.baseSalary.value.minValue,
544
+ "maxValue": data.baseSalary.value.maxValue,
545
+ "unitText": data.baseSalary.unitText || "MONTH"
546
+ }
547
+ } : void 0
548
+ });
549
+ }
550
+
551
+ // src/core/metadata.ts
552
+ function toNextMetadata(props, config) {
553
+ const title = props.title ? `${props.title} | ${config.name}` : config.name;
554
+ const description = props.description || config.description;
555
+ const url = props.canonical || config.url;
556
+ const image = props.image || config.logo;
557
+ const metadata = {
558
+ title,
559
+ description,
560
+ keywords: props.keywords,
561
+ robots: props.noindex ? "noindex, nofollow" : props.robots || "index, follow",
562
+ alternates: {
563
+ canonical: props.noindex ? void 0 : url
564
+ },
565
+ openGraph: {
566
+ title: props.ogTitle || title,
567
+ description: props.ogDescription || description,
568
+ url,
569
+ siteName: config.name,
570
+ images: props.ogImage || image ? [{
571
+ url: props.ogImage || image,
572
+ width: props.ogImageWidth || 1200,
573
+ height: props.ogImageHeight || 630,
574
+ alt: props.ogImageAlt || title
575
+ }] : [],
576
+ type: props.ogType || (props.type === "article" ? "article" : "website"),
577
+ locale: props.ogLocale || config.language || "ar_SA"
578
+ },
579
+ twitter: {
580
+ card: props.twitterCard || "summary_large_image",
581
+ title: props.twitterTitle || title,
582
+ description: props.twitterDescription || description,
583
+ images: props.twitterImage || image ? [{
584
+ url: props.twitterImage || image,
585
+ alt: props.twitterImageAlt || title
586
+ }] : [],
587
+ site: config.twitterHandle,
588
+ creator: config.twitterHandle
589
+ },
590
+ other: {}
591
+ };
592
+ if (config.facebookAppId) {
593
+ metadata.other["fb:app_id"] = config.facebookAppId;
594
+ }
595
+ if (props.alternates && props.alternates.length > 0) {
596
+ const languages = {};
597
+ props.alternates.forEach((alt) => {
598
+ languages[alt.hreflang] = alt.href;
599
+ });
600
+ metadata.alternates.languages = languages;
601
+ }
602
+ if (props.prev) {
603
+ metadata.alternates.prev = props.prev;
604
+ }
605
+ if (props.next) {
606
+ metadata.alternates.next = props.next;
607
+ }
608
+ metadata.appleWebApp = {
609
+ capable: true,
610
+ title: config.name,
611
+ statusBarStyle: "default"
612
+ };
613
+ if (config.themeColor) metadata.themeColor = config.themeColor;
614
+ if (config.manifest) metadata.manifest = config.manifest;
615
+ if (props.type === "article") {
616
+ if (props.publishedTime) {
617
+ metadata.openGraph.publishedTime = props.publishedTime;
618
+ }
619
+ if (props.modifiedTime) {
620
+ metadata.openGraph.modifiedTime = props.modifiedTime;
621
+ }
622
+ if (props.author) {
623
+ metadata.openGraph.authors = [props.author.name];
624
+ }
625
+ if (props.section) {
626
+ metadata.openGraph.section = props.section;
627
+ }
628
+ if (props.tags?.length) {
629
+ metadata.openGraph.tags = props.tags;
630
+ }
631
+ }
632
+ if (props.type === "product" && props.product) {
633
+ if (props.product.price !== void 0 && props.product.currency) {
634
+ metadata.other["product:price:amount"] = props.product.price.toString();
635
+ metadata.other["product:price:currency"] = props.product.currency;
636
+ }
637
+ if (props.product.availability) {
638
+ metadata.other["product:availability"] = props.product.availability;
639
+ }
640
+ if (props.product.brand) {
641
+ metadata.other["product:brand"] = props.product.brand;
642
+ }
643
+ }
644
+ if (props.readingTime) {
645
+ metadata.other["twitter:label1"] = "Reading time";
646
+ metadata.other["twitter:data1"] = `${props.readingTime} min`;
647
+ }
648
+ if (props.whatsappImage) {
649
+ metadata.other["og:image:secure_url"] = props.whatsappImage;
650
+ }
651
+ if (Object.keys(metadata.other).length === 0) {
652
+ delete metadata.other;
653
+ }
654
+ return metadata;
655
+ }
656
+ function generatePaginationLinks(baseUrl, currentPage, totalPages) {
657
+ const hasNext = currentPage < totalPages;
658
+ const hasPrev = currentPage > 1;
659
+ const cleanBase = baseUrl.split("?")[0];
660
+ return {
661
+ next: hasNext ? `${cleanBase}?page=${currentPage + 1}` : void 0,
662
+ prev: hasPrev ? currentPage === 2 ? cleanBase : `${cleanBase}?page=${currentPage - 1}` : void 0,
663
+ canonical: currentPage === 1 ? cleanBase : `${cleanBase}?page=${currentPage}`
664
+ };
665
+ }
666
+ function generatePaginatedTitle(title, page, suffix = "Page") {
667
+ return page > 1 ? `${title} - ${suffix} ${page}` : title;
668
+ }
669
+
670
+ // src/core/JsonLd.tsx
671
+ import { Fragment, jsx } from "react/jsx-runtime";
672
+ function JsonLd({ schema, graph = false }) {
673
+ const schemas = Array.isArray(schema) ? schema : [schema];
674
+ if (graph && schemas.length > 1) {
675
+ const graphData = {
676
+ "@context": "https://schema.org",
677
+ "@graph": schemas.map((s) => {
678
+ const { "@context": _, ...rest } = s;
679
+ return rest;
680
+ })
681
+ };
682
+ return /* @__PURE__ */ jsx(
683
+ "script",
684
+ {
685
+ type: "application/ld+json",
686
+ dangerouslySetInnerHTML: { __html: JSON.stringify(graphData) }
687
+ }
688
+ );
689
+ }
690
+ return /* @__PURE__ */ jsx(Fragment, { children: schemas.map((s, i) => /* @__PURE__ */ jsx(
691
+ "script",
692
+ {
693
+ type: "application/ld+json",
694
+ dangerouslySetInnerHTML: { __html: JSON.stringify(s) }
695
+ },
696
+ i
697
+ )) });
698
+ }
699
+
700
+ // src/core/product-metadata.ts
701
+ function generateProductMetadata(product, config) {
702
+ const primaryImage = Array.isArray(product.image) ? product.image[0] : product.image;
703
+ const seoData = {
704
+ title: product.metaTitle || product.name,
705
+ description: product.metaDescription || product.description,
706
+ image: product.ogImage || primaryImage,
707
+ canonical: product.canonical || product.url,
708
+ type: "product",
709
+ noindex: product.noindex,
710
+ ogTitle: product.name,
711
+ ogDescription: product.description,
712
+ ogImage: product.ogImage || primaryImage,
713
+ ogImageWidth: product.ogImageWidth || 1200,
714
+ ogImageHeight: product.ogImageHeight || 630,
715
+ ogType: "product",
716
+ product: {
717
+ sku: product.sku,
718
+ brand: product.brand,
719
+ price: product.price,
720
+ currency: product.currency,
721
+ availability: product.availability,
722
+ rating: product.rating,
723
+ reviewCount: product.reviewCount
724
+ }
725
+ };
726
+ const metadata = toNextMetadata(seoData, config);
727
+ if (product.price !== void 0 && product.currency) {
728
+ metadata.other = {
729
+ ...metadata.other,
730
+ "product:price:amount": product.price.toString(),
731
+ "product:price:currency": product.currency
732
+ };
733
+ if (product.availability) {
734
+ metadata.other["product:availability"] = product.availability;
735
+ }
736
+ if (product.brand) {
737
+ metadata.other["product:brand"] = product.brand;
738
+ }
739
+ if (product.condition) {
740
+ metadata.other["product:condition"] = product.condition;
741
+ }
742
+ }
743
+ const productSchema = generateProductSchema({
744
+ name: product.name,
745
+ description: product.description,
746
+ image: product.image,
747
+ sku: product.sku,
748
+ gtin: product.gtin,
749
+ mpn: product.mpn,
750
+ brand: product.brand,
751
+ price: product.price,
752
+ currency: product.currency,
753
+ availability: product.availability,
754
+ rating: product.rating,
755
+ reviewCount: product.reviewCount,
756
+ url: product.url,
757
+ condition: product.condition,
758
+ reviews: product.reviews,
759
+ returnPolicy: product.returnPolicy,
760
+ shipping: product.shipping,
761
+ variants: product.variants,
762
+ seller: product.seller
763
+ });
764
+ const breadcrumbItems = product.breadcrumbs || [
765
+ { name: "Home", item: config.url },
766
+ { name: "Shop", item: `${config.url}/shop` },
767
+ ...product.category ? [{ name: product.category, item: `${config.url}/categories/${encodeURIComponent(product.category)}` }] : [],
768
+ { name: product.name, item: product.url }
769
+ ];
770
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
771
+ const organizationSchema = generateOrganizationSchema(config);
772
+ const websiteSchema = generateWebSiteSchema(config);
773
+ return {
774
+ metadata,
775
+ schemas: [productSchema, breadcrumbSchema, organizationSchema, websiteSchema],
776
+ productSchema,
777
+ breadcrumbSchema,
778
+ organizationSchema,
779
+ websiteSchema
780
+ };
781
+ }
782
+
783
+ // src/core/article-metadata.ts
784
+ function generateArticleMetadata(article, config) {
785
+ const primaryImage = Array.isArray(article.image) ? article.image[0] : article.image;
786
+ const seoData = {
787
+ title: article.metaTitle || article.title,
788
+ description: article.metaDescription || article.description,
789
+ image: article.ogImage || primaryImage,
790
+ canonical: article.canonical || article.url,
791
+ type: "article",
792
+ noindex: article.noindex,
793
+ publishedTime: article.publishedTime,
794
+ modifiedTime: article.modifiedTime,
795
+ author: article.author,
796
+ section: article.category,
797
+ tags: article.tags,
798
+ readingTime: article.readingTime,
799
+ ogTitle: article.title,
800
+ ogDescription: article.description,
801
+ ogImage: article.ogImage || primaryImage,
802
+ ogImageWidth: article.ogImageWidth || 1200,
803
+ ogImageHeight: article.ogImageHeight || 630,
804
+ ogType: "article"
805
+ };
806
+ const metadata = toNextMetadata(seoData, config);
807
+ if (article.publishedTime) {
808
+ metadata.other = {
809
+ ...metadata.other,
810
+ "article:published_time": article.publishedTime
811
+ };
812
+ }
813
+ if (article.modifiedTime) {
814
+ metadata.other = {
815
+ ...metadata.other,
816
+ "article:modified_time": article.modifiedTime
817
+ };
818
+ }
819
+ if (article.category) {
820
+ metadata.other = {
821
+ ...metadata.other,
822
+ "article:section": article.category
823
+ };
824
+ }
825
+ if (article.tags?.length) {
826
+ metadata.other = {
827
+ ...metadata.other,
828
+ "article:tag": article.tags.join(",")
829
+ };
830
+ }
831
+ const articleSchema = generateArticleSchema({
832
+ title: article.title,
833
+ description: article.description,
834
+ image: article.image,
835
+ publishedTime: article.publishedTime,
836
+ modifiedTime: article.modifiedTime,
837
+ author: article.author,
838
+ url: article.url,
839
+ wordCount: article.wordCount
840
+ }, config);
841
+ const breadcrumbItems = article.breadcrumbs || [
842
+ { name: "Home", item: config.url },
843
+ ...article.category ? [{ name: article.category, item: `${config.url}/category/${encodeURIComponent(article.category)}` }] : [],
844
+ { name: article.title, item: article.url }
845
+ ];
846
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
847
+ const organizationSchema = generateOrganizationSchema(config);
848
+ const websiteSchema = generateWebSiteSchema(config);
849
+ return {
850
+ metadata,
851
+ schemas: [articleSchema, breadcrumbSchema, organizationSchema, websiteSchema],
852
+ articleSchema,
853
+ breadcrumbSchema,
854
+ organizationSchema,
855
+ websiteSchema
856
+ };
857
+ }
858
+
859
+ // src/core/category-metadata.ts
860
+ function generateCategoryMetadata(category, config) {
861
+ const page = category.page || 1;
862
+ const totalPages = category.totalPages || 1;
863
+ const pageSuffix = category.pageSuffix || "Page";
864
+ const pagination = generatePaginationLinks(category.url, page, totalPages);
865
+ const title = generatePaginatedTitle(
866
+ category.metaTitle || category.name,
867
+ page,
868
+ pageSuffix
869
+ );
870
+ const seoData = {
871
+ title,
872
+ description: category.metaDescription || category.description,
873
+ image: category.image,
874
+ canonical: pagination.canonical,
875
+ type: "website",
876
+ noindex: category.noindex,
877
+ prev: pagination.prev,
878
+ next: pagination.next
879
+ };
880
+ const metadata = toNextMetadata(seoData, config);
881
+ const collectionPageSchema = generateCollectionPageSchema({
882
+ name: category.name,
883
+ description: category.description,
884
+ url: pagination.canonical || category.url,
885
+ image: category.image,
886
+ numberOfItems: category.items?.length
887
+ }, config);
888
+ const breadcrumbItems = category.breadcrumbs || [
889
+ { name: "Home", item: config.url },
890
+ ...category.parentCategory ? [{ name: category.parentCategory, item: `${config.url}/categories` }] : [],
891
+ { name: category.name, item: category.url }
892
+ ];
893
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
894
+ const organizationSchema = generateOrganizationSchema(config);
895
+ let itemListSchema;
896
+ if (category.items && category.items.length > 0) {
897
+ itemListSchema = generateItemListSchema({
898
+ name: category.name,
899
+ url: pagination.canonical || category.url,
900
+ items: category.items
901
+ });
902
+ }
903
+ const schemas = [collectionPageSchema, breadcrumbSchema, organizationSchema];
904
+ if (itemListSchema) schemas.push(itemListSchema);
905
+ return {
906
+ metadata,
907
+ schemas,
908
+ collectionPageSchema,
909
+ breadcrumbSchema,
910
+ organizationSchema,
911
+ itemListSchema
912
+ };
913
+ }
914
+
915
+ export {
916
+ cleanSchema,
917
+ validateSEO,
918
+ generateOrganizationSchema,
919
+ generateWebSiteSchema,
920
+ generateWebPageSchema,
921
+ generateCollectionPageSchema,
922
+ generateItemListSchema,
923
+ generateArticleSchema,
924
+ generateProductSchema,
925
+ generateFAQSchema,
926
+ generateBreadcrumbSchema,
927
+ generateVideoSchema,
928
+ generateEventSchema,
929
+ generateLocalBusinessSchema,
930
+ generateSoftwareSchema,
931
+ generateBookSchema,
932
+ generateMovieSchema,
933
+ generatePodcastSchema,
934
+ generatePodcastEpisodeSchema,
935
+ generateHowToSchema,
936
+ generateRecipeSchema,
937
+ generateJobPostingSchema,
938
+ toNextMetadata,
939
+ generatePaginationLinks,
940
+ generatePaginatedTitle,
941
+ JsonLd,
942
+ generateProductMetadata,
943
+ generateArticleMetadata,
944
+ generateCategoryMetadata
945
+ };