@masters-ws/react-seo 1.2.1 → 1.4.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.
@@ -0,0 +1,567 @@
1
+ // src/core/schemas.ts
2
+ function generateOrganizationSchema(config) {
3
+ return {
4
+ "@context": "https://schema.org",
5
+ "@type": "Organization",
6
+ "name": config.name,
7
+ "url": config.url,
8
+ "logo": config.logo,
9
+ "sameAs": config.socialLinks || []
10
+ };
11
+ }
12
+ function generateWebSiteSchema(config) {
13
+ return {
14
+ "@context": "https://schema.org",
15
+ "@type": "WebSite",
16
+ "name": config.name,
17
+ "url": config.url,
18
+ "potentialAction": {
19
+ "@type": "SearchAction",
20
+ "target": `${config.url}/search?q={search_term_string}`,
21
+ "query-input": "required name=search_term_string"
22
+ }
23
+ };
24
+ }
25
+ function generateArticleSchema(data, config) {
26
+ const org = generateOrganizationSchema(config);
27
+ return {
28
+ "@context": "https://schema.org",
29
+ "@type": "NewsArticle",
30
+ "headline": data.title,
31
+ "description": data.description,
32
+ "image": data.image || config.logo,
33
+ "datePublished": data.publishedTime,
34
+ "dateModified": data.modifiedTime || data.publishedTime,
35
+ "mainEntityOfPage": data.url,
36
+ "author": data.author ? {
37
+ "@type": "Person",
38
+ "name": data.author.name,
39
+ "url": data.author.url
40
+ } : org,
41
+ "publisher": org
42
+ };
43
+ }
44
+ function generateProductSchema(data) {
45
+ return {
46
+ "@context": "https://schema.org",
47
+ "@type": "Product",
48
+ "name": data.name,
49
+ "description": data.description,
50
+ "image": data.image,
51
+ "sku": data.sku,
52
+ "brand": data.brand ? { "@type": "Brand", "name": data.brand } : void 0,
53
+ "offers": {
54
+ "@type": "Offer",
55
+ "url": data.url,
56
+ "priceCurrency": data.currency || "USD",
57
+ "price": data.price,
58
+ "availability": data.availability || "https://schema.org/InStock"
59
+ },
60
+ "aggregateRating": data.rating ? {
61
+ "@type": "AggregateRating",
62
+ "ratingValue": data.rating,
63
+ "reviewCount": data.reviewCount || 1
64
+ } : void 0
65
+ };
66
+ }
67
+ function generateFAQSchema(questions) {
68
+ return {
69
+ "@context": "https://schema.org",
70
+ "@type": "FAQPage",
71
+ "mainEntity": questions.map((item) => ({
72
+ "@type": "Question",
73
+ "name": item.q,
74
+ "acceptedAnswer": {
75
+ "@type": "Answer",
76
+ "text": item.a
77
+ }
78
+ }))
79
+ };
80
+ }
81
+ function generateBreadcrumbSchema(items) {
82
+ return {
83
+ "@context": "https://schema.org",
84
+ "@type": "BreadcrumbList",
85
+ "itemListElement": items.map((item, index) => ({
86
+ "@type": "ListItem",
87
+ "position": index + 1,
88
+ "name": item.name,
89
+ "item": item.item
90
+ }))
91
+ };
92
+ }
93
+ function generateVideoSchema(data) {
94
+ return {
95
+ "@context": "https://schema.org",
96
+ "@type": "VideoObject",
97
+ "name": data.name,
98
+ "description": data.description,
99
+ "thumbnailUrl": data.thumbnailUrl,
100
+ "uploadDate": data.uploadDate,
101
+ "duration": data.duration,
102
+ "contentUrl": data.contentUrl,
103
+ "embedUrl": data.embedUrl
104
+ };
105
+ }
106
+ function generateEventSchema(data) {
107
+ const isOnline = data.location && "url" in data.location;
108
+ return {
109
+ "@context": "https://schema.org",
110
+ "@type": "Event",
111
+ "name": data.name,
112
+ "description": data.description,
113
+ "startDate": data.startDate,
114
+ "endDate": data.endDate,
115
+ "eventAttendanceMode": isOnline ? "https://schema.org/OnlineEventAttendanceMode" : "https://schema.org/OfflineEventAttendanceMode",
116
+ "location": isOnline ? {
117
+ "@type": "VirtualLocation",
118
+ "url": data.location.url
119
+ } : data.location ? {
120
+ "@type": "Place",
121
+ "name": data.location.name,
122
+ "address": data.location.address
123
+ } : void 0,
124
+ "image": data.image,
125
+ "offers": data.offers ? {
126
+ "@type": "Offer",
127
+ "price": data.offers.price,
128
+ "priceCurrency": data.offers.currency,
129
+ "url": data.offers.url
130
+ } : void 0
131
+ };
132
+ }
133
+ function generateLocalBusinessSchema(data) {
134
+ return {
135
+ "@context": "https://schema.org",
136
+ "@type": "LocalBusiness",
137
+ "name": data.name,
138
+ "description": data.description,
139
+ "image": data.image,
140
+ "telephone": data.telephone,
141
+ "address": {
142
+ "@type": "PostalAddress",
143
+ "streetAddress": data.address.street,
144
+ "addressLocality": data.address.city,
145
+ "addressRegion": data.address.region,
146
+ "postalCode": data.address.postalCode,
147
+ "addressCountry": data.address.country
148
+ },
149
+ "geo": data.geo ? {
150
+ "@type": "GeoCoordinates",
151
+ "latitude": data.geo.lat,
152
+ "longitude": data.geo.lng
153
+ } : void 0,
154
+ "openingHours": data.openingHours,
155
+ "priceRange": data.priceRange
156
+ };
157
+ }
158
+ function generateSoftwareSchema(data) {
159
+ return {
160
+ "@context": "https://schema.org",
161
+ "@type": "SoftwareApplication",
162
+ "name": data.name,
163
+ "description": data.description,
164
+ "operatingSystem": data.operatingSystem,
165
+ "applicationCategory": data.applicationCategory,
166
+ "offers": data.offers ? {
167
+ "@type": "Offer",
168
+ "price": data.offers.price,
169
+ "priceCurrency": data.offers.currency
170
+ } : void 0,
171
+ "aggregateRating": data.rating ? {
172
+ "@type": "AggregateRating",
173
+ "ratingValue": data.rating,
174
+ "reviewCount": data.reviewCount || 1
175
+ } : void 0,
176
+ "downloadUrl": data.downloadUrl,
177
+ "screenshot": data.screenshot
178
+ };
179
+ }
180
+ function generateBookSchema(data) {
181
+ return {
182
+ "@context": "https://schema.org",
183
+ "@type": "Book",
184
+ "name": data.name,
185
+ "author": typeof data.author === "string" ? {
186
+ "@type": "Person",
187
+ "name": data.author
188
+ } : {
189
+ "@type": "Person",
190
+ "name": data.author.name,
191
+ "url": data.author.url
192
+ },
193
+ "description": data.description,
194
+ "isbn": data.isbn,
195
+ "numberOfPages": data.numberOfPages,
196
+ "publisher": data.publisher ? {
197
+ "@type": "Organization",
198
+ "name": data.publisher
199
+ } : void 0,
200
+ "datePublished": data.datePublished,
201
+ "image": data.image,
202
+ "inLanguage": data.inLanguage,
203
+ "genre": data.genre
204
+ };
205
+ }
206
+ function generateMovieSchema(data) {
207
+ return {
208
+ "@context": "https://schema.org",
209
+ "@type": "Movie",
210
+ "name": data.name,
211
+ "description": data.description,
212
+ "image": data.image,
213
+ "director": data.director ? {
214
+ "@type": "Person",
215
+ "name": data.director
216
+ } : void 0,
217
+ "actor": data.actor?.map((name) => ({
218
+ "@type": "Person",
219
+ "name": name
220
+ })),
221
+ "dateCreated": data.dateCreated,
222
+ "duration": data.duration,
223
+ "genre": data.genre,
224
+ "aggregateRating": data.rating ? {
225
+ "@type": "AggregateRating",
226
+ "ratingValue": data.rating,
227
+ "reviewCount": data.reviewCount || 1
228
+ } : void 0
229
+ };
230
+ }
231
+ function generatePodcastSchema(data) {
232
+ return {
233
+ "@context": "https://schema.org",
234
+ "@type": "PodcastSeries",
235
+ "name": data.name,
236
+ "description": data.description,
237
+ "image": data.image,
238
+ "author": data.author ? {
239
+ "@type": "Person",
240
+ "name": data.author
241
+ } : void 0,
242
+ "webFeed": data.webFeed,
243
+ "url": data.url,
244
+ "genre": data.genre
245
+ };
246
+ }
247
+ function generatePodcastEpisodeSchema(data) {
248
+ return {
249
+ "@context": "https://schema.org",
250
+ "@type": "PodcastEpisode",
251
+ "name": data.name,
252
+ "description": data.description,
253
+ "datePublished": data.datePublished,
254
+ "timeRequired": data.duration,
255
+ "url": data.url,
256
+ "associatedMedia": data.audio ? {
257
+ "@type": "AudioObject",
258
+ "contentUrl": data.audio
259
+ } : void 0,
260
+ "partOfSeries": data.partOfSeries ? {
261
+ "@type": "PodcastSeries",
262
+ "name": data.partOfSeries.name,
263
+ "url": data.partOfSeries.url
264
+ } : void 0
265
+ };
266
+ }
267
+
268
+ // src/core/metadata.ts
269
+ function toNextMetadata(props, config) {
270
+ const title = props.title ? `${props.title} | ${config.name}` : config.name;
271
+ const description = props.description || config.description;
272
+ const url = props.canonical || config.url;
273
+ const image = props.image || config.logo;
274
+ const metadata = {
275
+ title,
276
+ description,
277
+ keywords: props.keywords,
278
+ robots: props.noindex ? "noindex, nofollow" : props.robots || "index, follow",
279
+ alternates: {
280
+ canonical: props.noindex ? void 0 : url
281
+ },
282
+ openGraph: {
283
+ title: props.ogTitle || title,
284
+ description: props.ogDescription || description,
285
+ url,
286
+ siteName: config.name,
287
+ images: props.ogImage || image ? [{
288
+ url: props.ogImage || image,
289
+ width: props.ogImageWidth || 1200,
290
+ height: props.ogImageHeight || 630,
291
+ alt: props.ogImageAlt || title
292
+ }] : [],
293
+ type: props.ogType || (props.type === "article" ? "article" : "website"),
294
+ locale: props.ogLocale || config.language || "ar_SA"
295
+ },
296
+ twitter: {
297
+ card: props.twitterCard || "summary_large_image",
298
+ title: props.twitterTitle || title,
299
+ description: props.twitterDescription || description,
300
+ images: props.twitterImage || image ? [{
301
+ url: props.twitterImage || image,
302
+ alt: props.twitterImageAlt || title
303
+ }] : [],
304
+ site: config.twitterHandle,
305
+ creator: config.twitterHandle
306
+ },
307
+ other: {}
308
+ };
309
+ if (config.facebookAppId) {
310
+ metadata.other["fb:app_id"] = config.facebookAppId;
311
+ }
312
+ if (props.alternates && props.alternates.length > 0) {
313
+ const languages = {};
314
+ props.alternates.forEach((alt) => {
315
+ languages[alt.hreflang] = alt.href;
316
+ });
317
+ metadata.alternates.languages = languages;
318
+ }
319
+ if (props.prev) {
320
+ metadata.alternates.prev = props.prev;
321
+ }
322
+ if (props.next) {
323
+ metadata.alternates.next = props.next;
324
+ }
325
+ metadata.appleWebApp = {
326
+ capable: true,
327
+ title: config.name,
328
+ statusBarStyle: "default"
329
+ };
330
+ if (config.themeColor) metadata.themeColor = config.themeColor;
331
+ if (config.manifest) metadata.manifest = config.manifest;
332
+ if (props.type === "article") {
333
+ if (props.publishedTime) {
334
+ metadata.openGraph.publishedTime = props.publishedTime;
335
+ }
336
+ if (props.modifiedTime) {
337
+ metadata.openGraph.modifiedTime = props.modifiedTime;
338
+ }
339
+ if (props.author) {
340
+ metadata.openGraph.authors = [props.author.name];
341
+ }
342
+ if (props.section) {
343
+ metadata.openGraph.section = props.section;
344
+ }
345
+ if (props.tags?.length) {
346
+ metadata.openGraph.tags = props.tags;
347
+ }
348
+ }
349
+ if (props.type === "product" && props.product) {
350
+ if (props.product.price !== void 0 && props.product.currency) {
351
+ metadata.other["product:price:amount"] = props.product.price.toString();
352
+ metadata.other["product:price:currency"] = props.product.currency;
353
+ }
354
+ if (props.product.availability) {
355
+ metadata.other["product:availability"] = props.product.availability;
356
+ }
357
+ if (props.product.brand) {
358
+ metadata.other["product:brand"] = props.product.brand;
359
+ }
360
+ }
361
+ if (props.readingTime) {
362
+ metadata.other["twitter:label1"] = "Reading time";
363
+ metadata.other["twitter:data1"] = `${props.readingTime} min`;
364
+ }
365
+ if (props.whatsappImage) {
366
+ metadata.other["og:image:secure_url"] = props.whatsappImage;
367
+ }
368
+ if (Object.keys(metadata.other).length === 0) {
369
+ delete metadata.other;
370
+ }
371
+ return metadata;
372
+ }
373
+ function generatePaginationLinks(baseUrl, currentPage, totalPages) {
374
+ const hasNext = currentPage < totalPages;
375
+ const hasPrev = currentPage > 1;
376
+ const cleanBase = baseUrl.split("?")[0];
377
+ return {
378
+ next: hasNext ? `${cleanBase}?page=${currentPage + 1}` : void 0,
379
+ prev: hasPrev ? currentPage === 2 ? cleanBase : `${cleanBase}?page=${currentPage - 1}` : void 0,
380
+ canonical: currentPage === 1 ? cleanBase : `${cleanBase}?page=${currentPage}`
381
+ };
382
+ }
383
+ function generatePaginatedTitle(title, page, suffix = "\u0635\u0641\u062D\u0629") {
384
+ return page > 1 ? `${title} - ${suffix} ${page}` : title;
385
+ }
386
+
387
+ // src/core/JsonLd.tsx
388
+ import { Fragment, jsx } from "react/jsx-runtime";
389
+ function JsonLd({ schema }) {
390
+ const schemas = Array.isArray(schema) ? schema : [schema];
391
+ return /* @__PURE__ */ jsx(Fragment, { children: schemas.map((s, i) => /* @__PURE__ */ jsx(
392
+ "script",
393
+ {
394
+ type: "application/ld+json",
395
+ dangerouslySetInnerHTML: { __html: JSON.stringify(s) }
396
+ },
397
+ i
398
+ )) });
399
+ }
400
+
401
+ // src/core/product-metadata.ts
402
+ function generateProductMetadata(product, config) {
403
+ const seoData = {
404
+ title: product.metaTitle || product.name,
405
+ description: product.metaDescription || product.description,
406
+ image: product.ogImage || product.image,
407
+ canonical: product.canonical || product.url,
408
+ type: "product",
409
+ noindex: product.noindex,
410
+ ogTitle: product.name,
411
+ ogDescription: product.description,
412
+ ogImage: product.ogImage || product.image,
413
+ ogImageWidth: product.ogImageWidth || 1200,
414
+ ogImageHeight: product.ogImageHeight || 630,
415
+ ogType: "product",
416
+ product: {
417
+ sku: product.sku,
418
+ brand: product.brand,
419
+ price: product.price,
420
+ currency: product.currency,
421
+ availability: product.availability,
422
+ rating: product.rating,
423
+ reviewCount: product.reviewCount
424
+ }
425
+ };
426
+ const metadata = toNextMetadata(seoData, config);
427
+ if (product.price !== void 0 && product.currency) {
428
+ metadata.other = {
429
+ ...metadata.other,
430
+ "product:price:amount": product.price.toString(),
431
+ "product:price:currency": product.currency
432
+ };
433
+ if (product.availability) {
434
+ metadata.other["product:availability"] = product.availability;
435
+ }
436
+ if (product.brand) {
437
+ metadata.other["product:brand"] = product.brand;
438
+ }
439
+ }
440
+ const productSchema = generateProductSchema({
441
+ name: product.name,
442
+ description: product.description,
443
+ image: product.image,
444
+ sku: product.sku,
445
+ brand: product.brand,
446
+ price: product.price,
447
+ currency: product.currency,
448
+ availability: product.availability,
449
+ rating: product.rating,
450
+ reviewCount: product.reviewCount,
451
+ url: product.url
452
+ });
453
+ const breadcrumbItems = product.breadcrumbs || [
454
+ { name: "\u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629", item: config.url },
455
+ { name: "\u0627\u0644\u0645\u062A\u062C\u0631", item: `${config.url}/shop` },
456
+ ...product.category ? [{ name: product.category, item: `${config.url}/categories/${encodeURIComponent(product.category)}` }] : [],
457
+ { name: product.name, item: product.url }
458
+ ];
459
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
460
+ const organizationSchema = generateOrganizationSchema(config);
461
+ const websiteSchema = generateWebSiteSchema(config);
462
+ return {
463
+ metadata,
464
+ schemas: [productSchema, breadcrumbSchema, organizationSchema, websiteSchema],
465
+ productSchema,
466
+ breadcrumbSchema,
467
+ organizationSchema,
468
+ websiteSchema
469
+ };
470
+ }
471
+
472
+ // src/core/article-metadata.ts
473
+ function generateArticleMetadata(article, config) {
474
+ const seoData = {
475
+ title: article.metaTitle || article.title,
476
+ description: article.metaDescription || article.description,
477
+ image: article.ogImage || article.image,
478
+ canonical: article.canonical || article.url,
479
+ type: "article",
480
+ noindex: article.noindex,
481
+ publishedTime: article.publishedTime,
482
+ modifiedTime: article.modifiedTime,
483
+ author: article.author,
484
+ section: article.category,
485
+ tags: article.tags,
486
+ readingTime: article.readingTime,
487
+ ogTitle: article.title,
488
+ ogDescription: article.description,
489
+ ogImage: article.ogImage || article.image,
490
+ ogImageWidth: article.ogImageWidth || 1200,
491
+ ogImageHeight: article.ogImageHeight || 630,
492
+ ogType: "article"
493
+ };
494
+ const metadata = toNextMetadata(seoData, config);
495
+ if (article.publishedTime) {
496
+ metadata.other = {
497
+ ...metadata.other,
498
+ "article:published_time": article.publishedTime
499
+ };
500
+ }
501
+ if (article.modifiedTime) {
502
+ metadata.other = {
503
+ ...metadata.other,
504
+ "article:modified_time": article.modifiedTime
505
+ };
506
+ }
507
+ if (article.category) {
508
+ metadata.other = {
509
+ ...metadata.other,
510
+ "article:section": article.category
511
+ };
512
+ }
513
+ if (article.tags?.length) {
514
+ metadata.other = {
515
+ ...metadata.other,
516
+ "article:tag": article.tags.join(",")
517
+ };
518
+ }
519
+ const articleSchema = generateArticleSchema({
520
+ title: article.title,
521
+ description: article.description,
522
+ image: article.image,
523
+ publishedTime: article.publishedTime,
524
+ modifiedTime: article.modifiedTime,
525
+ author: article.author,
526
+ url: article.url
527
+ }, config);
528
+ const breadcrumbItems = article.breadcrumbs || [
529
+ { name: "\u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629", item: config.url },
530
+ ...article.category ? [{ name: article.category, item: `${config.url}/category/${encodeURIComponent(article.category)}` }] : [],
531
+ { name: article.title, item: article.url }
532
+ ];
533
+ const breadcrumbSchema = generateBreadcrumbSchema(breadcrumbItems);
534
+ const organizationSchema = generateOrganizationSchema(config);
535
+ const websiteSchema = generateWebSiteSchema(config);
536
+ return {
537
+ metadata,
538
+ schemas: [articleSchema, breadcrumbSchema, organizationSchema, websiteSchema],
539
+ articleSchema,
540
+ breadcrumbSchema,
541
+ organizationSchema,
542
+ websiteSchema
543
+ };
544
+ }
545
+
546
+ export {
547
+ generateOrganizationSchema,
548
+ generateWebSiteSchema,
549
+ generateArticleSchema,
550
+ generateProductSchema,
551
+ generateFAQSchema,
552
+ generateBreadcrumbSchema,
553
+ generateVideoSchema,
554
+ generateEventSchema,
555
+ generateLocalBusinessSchema,
556
+ generateSoftwareSchema,
557
+ generateBookSchema,
558
+ generateMovieSchema,
559
+ generatePodcastSchema,
560
+ generatePodcastEpisodeSchema,
561
+ toNextMetadata,
562
+ generatePaginationLinks,
563
+ generatePaginatedTitle,
564
+ JsonLd,
565
+ generateProductMetadata,
566
+ generateArticleMetadata
567
+ };