@getvitops/astro 0.1.1 → 0.3.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.
Files changed (37) hide show
  1. package/dist/index.d.mts +2 -120
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +3 -181
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +30 -9
  6. package/src/components/Cards.astro +32 -0
  7. package/src/components/Details.astro +76 -0
  8. package/src/components/Drawer.astro +65 -0
  9. package/src/components/NodeRenderer.astro +27 -0
  10. package/src/components/Popover.astro +90 -0
  11. package/src/components/Subgrid.astro +32 -0
  12. package/src/components/WebComponentLoader.astro +48 -0
  13. package/src/schemas/Article.astro +69 -0
  14. package/src/schemas/BookActions.astro +81 -0
  15. package/src/schemas/Breadcrumb.astro +26 -0
  16. package/src/schemas/Carousel.astro +34 -0
  17. package/src/schemas/CourseList.astro +122 -0
  18. package/src/schemas/Dataset.astro +127 -0
  19. package/src/schemas/DiscussionForum.astro +82 -0
  20. package/src/schemas/EducationQA.astro +63 -0
  21. package/src/schemas/EmployerAggregateRating.astro +54 -0
  22. package/src/schemas/Event.astro +115 -0
  23. package/src/schemas/FAQ.astro +34 -0
  24. package/src/schemas/FactCheck.astro +84 -0
  25. package/src/schemas/ImageMetadata.astro +56 -0
  26. package/src/schemas/JobPosting.astro +138 -0
  27. package/src/schemas/LocalBusiness.astro +155 -0
  28. package/src/schemas/MathSolver.astro +54 -0
  29. package/src/schemas/MovieCarousel.astro +92 -0
  30. package/src/schemas/Organization.astro +140 -0
  31. package/src/schemas/Product.astro +250 -0
  32. package/src/schemas/ProfilePage.astro +147 -0
  33. package/src/schemas/QAPage.astro +71 -0
  34. package/src/schemas/Recipe.astro +178 -0
  35. package/src/schemas/Review.astro +138 -0
  36. package/src/schemas/SoftwareApp.astro +172 -0
  37. /package/{Head.astro → src/components/Head.astro} +0 -0
@@ -0,0 +1,147 @@
1
+ ---
2
+ export interface Props {
3
+ name: string;
4
+ alternateName?: string;
5
+ description?: string;
6
+ image?: string;
7
+ url?: string;
8
+ sameAs?: string[];
9
+ mainEntity: {
10
+ type: 'Person' | 'Organization';
11
+ name: string;
12
+ alternateName?: string;
13
+ description?: string;
14
+ image?: string;
15
+ url?: string;
16
+ sameAs?: string[];
17
+ jobTitle?: string;
18
+ worksFor?: {
19
+ name: string;
20
+ url?: string;
21
+ };
22
+ knowsAbout?: string[];
23
+ knowsLanguage?: string[];
24
+ nationality?: string;
25
+ birthDate?: string;
26
+ email?: string;
27
+ telephone?: string;
28
+ address?: {
29
+ addressLocality?: string;
30
+ addressRegion?: string;
31
+ addressCountry?: string;
32
+ };
33
+ alumniOf?: {
34
+ name: string;
35
+ url?: string;
36
+ }[];
37
+ award?: string[];
38
+ memberOf?: {
39
+ name: string;
40
+ url?: string;
41
+ }[];
42
+ interactionStatistic?: {
43
+ type: 'FollowAction' | 'LikeAction' | 'ShareAction' | 'WriteAction';
44
+ count: number;
45
+ }[];
46
+ agentInteractionStatistic?: {
47
+ type: 'FollowAction' | 'LikeAction' | 'ShareAction' | 'WriteAction';
48
+ count: number;
49
+ }[];
50
+ };
51
+ dateCreated?: string;
52
+ dateModified?: string;
53
+ }
54
+
55
+ const {
56
+ name,
57
+ alternateName,
58
+ description,
59
+ image,
60
+ url,
61
+ sameAs,
62
+ mainEntity,
63
+ dateCreated,
64
+ dateModified,
65
+ } = Astro.props;
66
+
67
+ const jsonLd = {
68
+ '@context': 'https://schema.org',
69
+ '@type': 'ProfilePage',
70
+ name,
71
+ ...(alternateName && { alternateName }),
72
+ ...(description && { description }),
73
+ ...(image && { image }),
74
+ ...(url && { url }),
75
+ ...(sameAs && { sameAs }),
76
+ ...(dateCreated && { dateCreated }),
77
+ ...(dateModified && { dateModified }),
78
+ mainEntity: {
79
+ '@type': mainEntity.type,
80
+ name: mainEntity.name,
81
+ ...(mainEntity.alternateName && { alternateName: mainEntity.alternateName }),
82
+ ...(mainEntity.description && { description: mainEntity.description }),
83
+ ...(mainEntity.image && { image: mainEntity.image }),
84
+ ...(mainEntity.url && { url: mainEntity.url }),
85
+ ...(mainEntity.sameAs && { sameAs: mainEntity.sameAs }),
86
+ ...(mainEntity.jobTitle && { jobTitle: mainEntity.jobTitle }),
87
+ ...(mainEntity.worksFor && {
88
+ worksFor: {
89
+ '@type': 'Organization',
90
+ name: mainEntity.worksFor.name,
91
+ ...(mainEntity.worksFor.url && { url: mainEntity.worksFor.url }),
92
+ },
93
+ }),
94
+ ...(mainEntity.knowsAbout && { knowsAbout: mainEntity.knowsAbout }),
95
+ ...(mainEntity.knowsLanguage && { knowsLanguage: mainEntity.knowsLanguage }),
96
+ ...(mainEntity.nationality && {
97
+ nationality: {
98
+ '@type': 'Country',
99
+ name: mainEntity.nationality,
100
+ },
101
+ }),
102
+ ...(mainEntity.birthDate && { birthDate: mainEntity.birthDate }),
103
+ ...(mainEntity.email && { email: mainEntity.email }),
104
+ ...(mainEntity.telephone && { telephone: mainEntity.telephone }),
105
+ ...(mainEntity.address && {
106
+ address: {
107
+ '@type': 'PostalAddress',
108
+ ...(mainEntity.address.addressLocality && { addressLocality: mainEntity.address.addressLocality }),
109
+ ...(mainEntity.address.addressRegion && { addressRegion: mainEntity.address.addressRegion }),
110
+ ...(mainEntity.address.addressCountry && { addressCountry: mainEntity.address.addressCountry }),
111
+ },
112
+ }),
113
+ ...(mainEntity.alumniOf && {
114
+ alumniOf: mainEntity.alumniOf.map(a => ({
115
+ '@type': 'EducationalOrganization',
116
+ name: a.name,
117
+ ...(a.url && { url: a.url }),
118
+ })),
119
+ }),
120
+ ...(mainEntity.award && { award: mainEntity.award }),
121
+ ...(mainEntity.memberOf && {
122
+ memberOf: mainEntity.memberOf.map(m => ({
123
+ '@type': 'Organization',
124
+ name: m.name,
125
+ ...(m.url && { url: m.url }),
126
+ })),
127
+ }),
128
+ ...(mainEntity.interactionStatistic && {
129
+ interactionStatistic: mainEntity.interactionStatistic.map(stat => ({
130
+ '@type': 'InteractionCounter',
131
+ interactionType: { '@type': stat.type },
132
+ userInteractionCount: stat.count,
133
+ })),
134
+ }),
135
+ ...(mainEntity.agentInteractionStatistic && {
136
+ agentInteractionStatistic: mainEntity.agentInteractionStatistic.map(stat => ({
137
+ '@type': 'InteractionCounter',
138
+ interactionType: { '@type': stat.type },
139
+ userInteractionCount: stat.count,
140
+ })),
141
+ }),
142
+ },
143
+ };
144
+ ---
145
+
146
+ <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
147
+ <slot />
@@ -0,0 +1,71 @@
1
+ ---
2
+ export interface Answer {
3
+ text: string;
4
+ dateCreated: string;
5
+ author: {
6
+ name: string;
7
+ type?: 'Person' | 'Organization';
8
+ };
9
+ upvoteCount?: number;
10
+ url?: string;
11
+ }
12
+
13
+ export interface Props {
14
+ question: string;
15
+ questionDetail?: string;
16
+ dateCreated: string;
17
+ author: {
18
+ name: string;
19
+ type?: 'Person' | 'Organization';
20
+ };
21
+ answerCount?: number;
22
+ upvoteCount?: number;
23
+ acceptedAnswer?: Answer;
24
+ suggestedAnswer?: Answer[];
25
+ }
26
+
27
+ const {
28
+ question,
29
+ questionDetail,
30
+ dateCreated,
31
+ author,
32
+ answerCount,
33
+ upvoteCount,
34
+ acceptedAnswer,
35
+ suggestedAnswer,
36
+ } = Astro.props;
37
+
38
+ const formatAnswer = (answer: Answer) => ({
39
+ '@type': 'Answer',
40
+ text: answer.text,
41
+ dateCreated: answer.dateCreated,
42
+ author: {
43
+ '@type': answer.author.type || 'Person',
44
+ name: answer.author.name,
45
+ },
46
+ ...(answer.upvoteCount !== undefined && { upvoteCount: answer.upvoteCount }),
47
+ ...(answer.url && { url: answer.url }),
48
+ });
49
+
50
+ const jsonLd = {
51
+ '@context': 'https://schema.org',
52
+ '@type': 'QAPage',
53
+ mainEntity: {
54
+ '@type': 'Question',
55
+ name: question,
56
+ ...(questionDetail && { text: questionDetail }),
57
+ dateCreated,
58
+ author: {
59
+ '@type': author.type || 'Person',
60
+ name: author.name,
61
+ },
62
+ ...(answerCount !== undefined && { answerCount }),
63
+ ...(upvoteCount !== undefined && { upvoteCount }),
64
+ ...(acceptedAnswer && { acceptedAnswer: formatAnswer(acceptedAnswer) }),
65
+ ...(suggestedAnswer && { suggestedAnswer: suggestedAnswer.map(formatAnswer) }),
66
+ },
67
+ };
68
+ ---
69
+
70
+ <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
71
+ <slot />
@@ -0,0 +1,178 @@
1
+ ---
2
+ export interface Props {
3
+ name: string;
4
+ description?: string;
5
+ image?: string | string[];
6
+ author?: {
7
+ name: string;
8
+ url?: string;
9
+ };
10
+ datePublished?: string;
11
+ prepTime?: string;
12
+ cookTime?: string;
13
+ totalTime?: string;
14
+ recipeYield?: string | number;
15
+ recipeCategory?: string;
16
+ recipeCuisine?: string;
17
+ keywords?: string[];
18
+ nutrition?: {
19
+ calories?: string;
20
+ fatContent?: string;
21
+ saturatedFatContent?: string;
22
+ cholesterolContent?: string;
23
+ sodiumContent?: string;
24
+ carbohydrateContent?: string;
25
+ fiberContent?: string;
26
+ sugarContent?: string;
27
+ proteinContent?: string;
28
+ servingSize?: string;
29
+ };
30
+ recipeIngredient: string[];
31
+ recipeInstructions: (string | { name?: string; text: string; image?: string; url?: string })[];
32
+ video?: {
33
+ name: string;
34
+ description: string;
35
+ thumbnailUrl: string | string[];
36
+ contentUrl?: string;
37
+ embedUrl?: string;
38
+ uploadDate: string;
39
+ duration?: string;
40
+ expires?: string;
41
+ };
42
+ aggregateRating?: {
43
+ ratingValue: number;
44
+ ratingCount?: number;
45
+ reviewCount?: number;
46
+ bestRating?: number;
47
+ worstRating?: number;
48
+ };
49
+ review?: {
50
+ author: string;
51
+ datePublished: string;
52
+ reviewBody?: string;
53
+ reviewRating?: {
54
+ ratingValue: number;
55
+ bestRating?: number;
56
+ worstRating?: number;
57
+ };
58
+ }[];
59
+ suitableForDiet?: ('DiabeticDiet' | 'GlutenFreeDiet' | 'HalalDiet' | 'HinduDiet' | 'KosherDiet' | 'LowCalorieDiet' | 'LowFatDiet' | 'LowLactoseDiet' | 'LowSaltDiet' | 'VeganDiet' | 'VegetarianDiet')[];
60
+ }
61
+
62
+ const {
63
+ name,
64
+ description,
65
+ image,
66
+ author,
67
+ datePublished,
68
+ prepTime,
69
+ cookTime,
70
+ totalTime,
71
+ recipeYield,
72
+ recipeCategory,
73
+ recipeCuisine,
74
+ keywords,
75
+ nutrition,
76
+ recipeIngredient,
77
+ recipeInstructions,
78
+ video,
79
+ aggregateRating,
80
+ review,
81
+ suitableForDiet,
82
+ } = Astro.props;
83
+
84
+ const jsonLd = {
85
+ '@context': 'https://schema.org',
86
+ '@type': 'Recipe',
87
+ name,
88
+ ...(description && { description }),
89
+ ...(image && { image: Array.isArray(image) ? image : [image] }),
90
+ ...(author && {
91
+ author: {
92
+ '@type': 'Person',
93
+ name: author.name,
94
+ ...(author.url && { url: author.url }),
95
+ },
96
+ }),
97
+ ...(datePublished && { datePublished }),
98
+ ...(prepTime && { prepTime }),
99
+ ...(cookTime && { cookTime }),
100
+ ...(totalTime && { totalTime }),
101
+ ...(recipeYield && { recipeYield: String(recipeYield) }),
102
+ ...(recipeCategory && { recipeCategory }),
103
+ ...(recipeCuisine && { recipeCuisine }),
104
+ ...(keywords && { keywords: keywords.join(', ') }),
105
+ ...(nutrition && {
106
+ nutrition: {
107
+ '@type': 'NutritionInformation',
108
+ ...(nutrition.calories && { calories: nutrition.calories }),
109
+ ...(nutrition.fatContent && { fatContent: nutrition.fatContent }),
110
+ ...(nutrition.saturatedFatContent && { saturatedFatContent: nutrition.saturatedFatContent }),
111
+ ...(nutrition.cholesterolContent && { cholesterolContent: nutrition.cholesterolContent }),
112
+ ...(nutrition.sodiumContent && { sodiumContent: nutrition.sodiumContent }),
113
+ ...(nutrition.carbohydrateContent && { carbohydrateContent: nutrition.carbohydrateContent }),
114
+ ...(nutrition.fiberContent && { fiberContent: nutrition.fiberContent }),
115
+ ...(nutrition.sugarContent && { sugarContent: nutrition.sugarContent }),
116
+ ...(nutrition.proteinContent && { proteinContent: nutrition.proteinContent }),
117
+ ...(nutrition.servingSize && { servingSize: nutrition.servingSize }),
118
+ },
119
+ }),
120
+ recipeIngredient,
121
+ recipeInstructions: recipeInstructions.map((step, index) =>
122
+ typeof step === 'string'
123
+ ? { '@type': 'HowToStep', text: step }
124
+ : {
125
+ '@type': 'HowToStep',
126
+ ...(step.name && { name: step.name }),
127
+ text: step.text,
128
+ ...(step.image && { image: step.image }),
129
+ ...(step.url && { url: step.url }),
130
+ }
131
+ ),
132
+ ...(video && {
133
+ video: {
134
+ '@type': 'VideoObject',
135
+ name: video.name,
136
+ description: video.description,
137
+ thumbnailUrl: video.thumbnailUrl,
138
+ ...(video.contentUrl && { contentUrl: video.contentUrl }),
139
+ ...(video.embedUrl && { embedUrl: video.embedUrl }),
140
+ uploadDate: video.uploadDate,
141
+ ...(video.duration && { duration: video.duration }),
142
+ ...(video.expires && { expires: video.expires }),
143
+ },
144
+ }),
145
+ ...(aggregateRating && {
146
+ aggregateRating: {
147
+ '@type': 'AggregateRating',
148
+ ratingValue: aggregateRating.ratingValue,
149
+ ...(aggregateRating.ratingCount && { ratingCount: aggregateRating.ratingCount }),
150
+ ...(aggregateRating.reviewCount && { reviewCount: aggregateRating.reviewCount }),
151
+ bestRating: aggregateRating.bestRating ?? 5,
152
+ worstRating: aggregateRating.worstRating ?? 1,
153
+ },
154
+ }),
155
+ ...(review && {
156
+ review: review.map(r => ({
157
+ '@type': 'Review',
158
+ author: { '@type': 'Person', name: r.author },
159
+ datePublished: r.datePublished,
160
+ ...(r.reviewBody && { reviewBody: r.reviewBody }),
161
+ ...(r.reviewRating && {
162
+ reviewRating: {
163
+ '@type': 'Rating',
164
+ ratingValue: r.reviewRating.ratingValue,
165
+ bestRating: r.reviewRating.bestRating ?? 5,
166
+ worstRating: r.reviewRating.worstRating ?? 1,
167
+ },
168
+ }),
169
+ })),
170
+ }),
171
+ ...(suitableForDiet && {
172
+ suitableForDiet: suitableForDiet.map(diet => `https://schema.org/${diet}`),
173
+ }),
174
+ };
175
+ ---
176
+
177
+ <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
178
+ <slot />
@@ -0,0 +1,138 @@
1
+ ---
2
+ export interface Props {
3
+ itemReviewed: {
4
+ type: 'Product' | 'LocalBusiness' | 'Organization' | 'Book' | 'Movie' | 'Restaurant' | 'Event' | 'CreativeWork' | 'SoftwareApplication' | 'MusicRecording' | 'Recipe';
5
+ name: string;
6
+ image?: string;
7
+ url?: string;
8
+ sameAs?: string;
9
+ author?: string;
10
+ isbn?: string;
11
+ director?: string;
12
+ datePublished?: string;
13
+ address?: {
14
+ streetAddress?: string;
15
+ addressLocality?: string;
16
+ addressRegion?: string;
17
+ postalCode?: string;
18
+ addressCountry?: string;
19
+ };
20
+ priceRange?: string;
21
+ telephone?: string;
22
+ };
23
+ reviewRating: {
24
+ ratingValue: number;
25
+ bestRating?: number;
26
+ worstRating?: number;
27
+ };
28
+ author: {
29
+ name: string;
30
+ type?: 'Person' | 'Organization';
31
+ };
32
+ datePublished?: string;
33
+ reviewBody?: string;
34
+ publisher?: {
35
+ name: string;
36
+ type?: 'Person' | 'Organization';
37
+ };
38
+ positiveNotes?: string | string[];
39
+ negativeNotes?: string | string[];
40
+ }
41
+
42
+ const {
43
+ itemReviewed,
44
+ reviewRating,
45
+ author,
46
+ datePublished,
47
+ reviewBody,
48
+ publisher,
49
+ positiveNotes,
50
+ negativeNotes,
51
+ } = Astro.props;
52
+
53
+ const buildItemReviewed = () => {
54
+ const base = {
55
+ '@type': itemReviewed.type,
56
+ name: itemReviewed.name,
57
+ ...(itemReviewed.image && { image: itemReviewed.image }),
58
+ ...(itemReviewed.url && { url: itemReviewed.url }),
59
+ ...(itemReviewed.sameAs && { sameAs: itemReviewed.sameAs }),
60
+ };
61
+
62
+ switch (itemReviewed.type) {
63
+ case 'Book':
64
+ return {
65
+ ...base,
66
+ ...(itemReviewed.author && { author: { '@type': 'Person', name: itemReviewed.author } }),
67
+ ...(itemReviewed.isbn && { isbn: itemReviewed.isbn }),
68
+ };
69
+ case 'Movie':
70
+ return {
71
+ ...base,
72
+ ...(itemReviewed.director && { director: { '@type': 'Person', name: itemReviewed.director } }),
73
+ ...(itemReviewed.datePublished && { datePublished: itemReviewed.datePublished }),
74
+ };
75
+ case 'LocalBusiness':
76
+ case 'Restaurant':
77
+ return {
78
+ ...base,
79
+ ...(itemReviewed.address && {
80
+ address: {
81
+ '@type': 'PostalAddress',
82
+ ...(itemReviewed.address.streetAddress && { streetAddress: itemReviewed.address.streetAddress }),
83
+ ...(itemReviewed.address.addressLocality && { addressLocality: itemReviewed.address.addressLocality }),
84
+ ...(itemReviewed.address.addressRegion && { addressRegion: itemReviewed.address.addressRegion }),
85
+ ...(itemReviewed.address.postalCode && { postalCode: itemReviewed.address.postalCode }),
86
+ ...(itemReviewed.address.addressCountry && { addressCountry: itemReviewed.address.addressCountry }),
87
+ },
88
+ }),
89
+ ...(itemReviewed.priceRange && { priceRange: itemReviewed.priceRange }),
90
+ ...(itemReviewed.telephone && { telephone: itemReviewed.telephone }),
91
+ };
92
+ default:
93
+ return base;
94
+ }
95
+ };
96
+
97
+ const formatNotes = (notes: string | string[] | undefined, type: 'Positive' | 'Negative') => {
98
+ if (!notes) return undefined;
99
+ const noteArray = Array.isArray(notes) ? notes : [notes];
100
+ return {
101
+ '@type': 'ItemList',
102
+ itemListElement: noteArray.map((note, index) => ({
103
+ '@type': 'ListItem',
104
+ position: index + 1,
105
+ name: note,
106
+ })),
107
+ };
108
+ };
109
+
110
+ const jsonLd = {
111
+ '@context': 'https://schema.org',
112
+ '@type': 'Review',
113
+ itemReviewed: buildItemReviewed(),
114
+ reviewRating: {
115
+ '@type': 'Rating',
116
+ ratingValue: reviewRating.ratingValue,
117
+ bestRating: reviewRating.bestRating ?? 5,
118
+ worstRating: reviewRating.worstRating ?? 1,
119
+ },
120
+ author: {
121
+ '@type': author.type || 'Person',
122
+ name: author.name,
123
+ },
124
+ ...(datePublished && { datePublished }),
125
+ ...(reviewBody && { reviewBody }),
126
+ ...(publisher && {
127
+ publisher: {
128
+ '@type': publisher.type || 'Organization',
129
+ name: publisher.name,
130
+ },
131
+ }),
132
+ ...(positiveNotes && { positiveNotes: formatNotes(positiveNotes, 'Positive') }),
133
+ ...(negativeNotes && { negativeNotes: formatNotes(negativeNotes, 'Negative') }),
134
+ };
135
+ ---
136
+
137
+ <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
138
+ <slot />
@@ -0,0 +1,172 @@
1
+ ---
2
+ export interface Props {
3
+ name: string;
4
+ description?: string;
5
+ applicationCategory?: 'GameApplication' | 'SocialNetworkingApplication' | 'TravelApplication' | 'ShoppingApplication' | 'SportsApplication' | 'LifestyleApplication' | 'BusinessApplication' | 'DesignApplication' | 'DeveloperApplication' | 'DriverApplication' | 'EducationalApplication' | 'HealthApplication' | 'FinanceApplication' | 'SecurityApplication' | 'BrowserApplication' | 'CommunicationApplication' | 'DesktopEnhancementApplication' | 'EntertainmentApplication' | 'MultimediaApplication' | 'HomeApplication' | 'UtilitiesApplication' | 'ReferenceApplication';
6
+ operatingSystem?: string;
7
+ url?: string;
8
+ image?: string;
9
+ screenshot?: string | string[];
10
+ offers?: {
11
+ price: number | string;
12
+ priceCurrency: string;
13
+ availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
14
+ };
15
+ aggregateRating?: {
16
+ ratingValue: number;
17
+ ratingCount?: number;
18
+ reviewCount?: number;
19
+ bestRating?: number;
20
+ worstRating?: number;
21
+ };
22
+ review?: {
23
+ author: string;
24
+ datePublished: string;
25
+ reviewBody?: string;
26
+ reviewRating?: {
27
+ ratingValue: number;
28
+ bestRating?: number;
29
+ worstRating?: number;
30
+ };
31
+ }[];
32
+ softwareVersion?: string;
33
+ releaseNotes?: string;
34
+ datePublished?: string;
35
+ downloadUrl?: string;
36
+ installUrl?: string;
37
+ featureList?: string | string[];
38
+ requirements?: string;
39
+ memoryRequirements?: string;
40
+ storageRequirements?: string;
41
+ permissions?: string;
42
+ author?: {
43
+ name: string;
44
+ type?: 'Person' | 'Organization';
45
+ url?: string;
46
+ };
47
+ publisher?: {
48
+ name: string;
49
+ type?: 'Person' | 'Organization';
50
+ url?: string;
51
+ };
52
+ inLanguage?: string | string[];
53
+ isAccessibleForFree?: boolean;
54
+ countriesSupported?: string[];
55
+ supportingData?: {
56
+ name: string;
57
+ url: string;
58
+ };
59
+ }
60
+
61
+ const {
62
+ name,
63
+ description,
64
+ applicationCategory,
65
+ operatingSystem,
66
+ url,
67
+ image,
68
+ screenshot,
69
+ offers,
70
+ aggregateRating,
71
+ review,
72
+ softwareVersion,
73
+ releaseNotes,
74
+ datePublished,
75
+ downloadUrl,
76
+ installUrl,
77
+ featureList,
78
+ requirements,
79
+ memoryRequirements,
80
+ storageRequirements,
81
+ permissions,
82
+ author,
83
+ publisher,
84
+ inLanguage,
85
+ isAccessibleForFree,
86
+ countriesSupported,
87
+ supportingData,
88
+ } = Astro.props;
89
+
90
+ const jsonLd = {
91
+ '@context': 'https://schema.org',
92
+ '@type': 'SoftwareApplication',
93
+ name,
94
+ ...(description && { description }),
95
+ ...(applicationCategory && { applicationCategory }),
96
+ ...(operatingSystem && { operatingSystem }),
97
+ ...(url && { url }),
98
+ ...(image && { image }),
99
+ ...(screenshot && { screenshot: Array.isArray(screenshot) ? screenshot : [screenshot] }),
100
+ ...(offers && {
101
+ offers: {
102
+ '@type': 'Offer',
103
+ price: offers.price,
104
+ priceCurrency: offers.priceCurrency,
105
+ ...(offers.availability && { availability: `https://schema.org/${offers.availability}` }),
106
+ },
107
+ }),
108
+ ...(aggregateRating && {
109
+ aggregateRating: {
110
+ '@type': 'AggregateRating',
111
+ ratingValue: aggregateRating.ratingValue,
112
+ ...(aggregateRating.ratingCount && { ratingCount: aggregateRating.ratingCount }),
113
+ ...(aggregateRating.reviewCount && { reviewCount: aggregateRating.reviewCount }),
114
+ bestRating: aggregateRating.bestRating ?? 5,
115
+ worstRating: aggregateRating.worstRating ?? 1,
116
+ },
117
+ }),
118
+ ...(review && {
119
+ review: review.map(r => ({
120
+ '@type': 'Review',
121
+ author: { '@type': 'Person', name: r.author },
122
+ datePublished: r.datePublished,
123
+ ...(r.reviewBody && { reviewBody: r.reviewBody }),
124
+ ...(r.reviewRating && {
125
+ reviewRating: {
126
+ '@type': 'Rating',
127
+ ratingValue: r.reviewRating.ratingValue,
128
+ bestRating: r.reviewRating.bestRating ?? 5,
129
+ worstRating: r.reviewRating.worstRating ?? 1,
130
+ },
131
+ }),
132
+ })),
133
+ }),
134
+ ...(softwareVersion && { softwareVersion }),
135
+ ...(releaseNotes && { releaseNotes }),
136
+ ...(datePublished && { datePublished }),
137
+ ...(downloadUrl && { downloadUrl }),
138
+ ...(installUrl && { installUrl }),
139
+ ...(featureList && { featureList: Array.isArray(featureList) ? featureList.join(', ') : featureList }),
140
+ ...(requirements && { requirements }),
141
+ ...(memoryRequirements && { memoryRequirements }),
142
+ ...(storageRequirements && { storageRequirements }),
143
+ ...(permissions && { permissions }),
144
+ ...(author && {
145
+ author: {
146
+ '@type': author.type || 'Person',
147
+ name: author.name,
148
+ ...(author.url && { url: author.url }),
149
+ },
150
+ }),
151
+ ...(publisher && {
152
+ publisher: {
153
+ '@type': publisher.type || 'Organization',
154
+ name: publisher.name,
155
+ ...(publisher.url && { url: publisher.url }),
156
+ },
157
+ }),
158
+ ...(inLanguage && { inLanguage }),
159
+ ...(isAccessibleForFree !== undefined && { isAccessibleForFree }),
160
+ ...(countriesSupported && { countriesSupported }),
161
+ ...(supportingData && {
162
+ supportingData: {
163
+ '@type': 'DataFeed',
164
+ name: supportingData.name,
165
+ url: supportingData.url,
166
+ },
167
+ }),
168
+ };
169
+ ---
170
+
171
+ <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
172
+ <slot />
File without changes