nuxt-schema-org 6.1.3 → 6.2.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.
- package/dist/devtools/components/schema-org/SchemaValidator.vue +326 -0
- package/dist/devtools/lib/schema-org/fetch.ts +13 -0
- package/dist/devtools/lib/schema-org/rpc.ts +46 -0
- package/dist/devtools/lib/schema-org/util/schema-validation.ts +470 -0
- package/dist/devtools/nuxt.config.ts +7 -0
- package/dist/devtools/pages/schema-org/debug.vue +23 -0
- package/dist/devtools/pages/schema-org/docs.vue +3 -0
- package/dist/devtools/pages/schema-org/index.vue +8 -0
- package/dist/devtools/pages/schema-org/raw.vue +25 -0
- package/dist/devtools/pages/schema-org.vue +54 -0
- package/dist/module.cjs +28 -7
- package/dist/module.d.cts +1 -1
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -7
- package/dist/schema.cjs +2147 -9
- package/dist/schema.d.cts +185 -1
- package/dist/schema.d.mts +185 -1
- package/dist/schema.d.ts +185 -1
- package/dist/schema.mjs +2035 -1
- package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.cts +2770 -0
- package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.mts +2770 -0
- package/dist/shared/nuxt-schema-org.BQ-jW5j5.d.ts +2770 -0
- package/dist/vendor/schema-org-v2/LICENSE +21 -0
- package/dist/vendor/schema-org-v2/chunks/index.mjs +23 -0
- package/dist/vendor/schema-org-v2/chunks/index10.mjs +63 -0
- package/dist/vendor/schema-org-v2/chunks/index11.mjs +35 -0
- package/dist/vendor/schema-org-v2/chunks/index12.mjs +48 -0
- package/dist/vendor/schema-org-v2/chunks/index13.mjs +36 -0
- package/dist/vendor/schema-org-v2/chunks/index14.mjs +25 -0
- package/dist/vendor/schema-org-v2/chunks/index15.mjs +37 -0
- package/dist/vendor/schema-org-v2/chunks/index16.mjs +27 -0
- package/dist/vendor/schema-org-v2/chunks/index17.mjs +34 -0
- package/dist/vendor/schema-org-v2/chunks/index18.mjs +32 -0
- package/dist/vendor/schema-org-v2/chunks/index19.mjs +31 -0
- package/dist/vendor/schema-org-v2/chunks/index2.mjs +12 -0
- package/dist/vendor/schema-org-v2/chunks/index20.mjs +31 -0
- package/dist/vendor/schema-org-v2/chunks/index21.mjs +30 -0
- package/dist/vendor/schema-org-v2/chunks/index22.mjs +30 -0
- package/dist/vendor/schema-org-v2/chunks/index23.mjs +82 -0
- package/dist/vendor/schema-org-v2/chunks/index24.mjs +14 -0
- package/dist/vendor/schema-org-v2/chunks/index25.mjs +33 -0
- package/dist/vendor/schema-org-v2/chunks/index26.mjs +31 -0
- package/dist/vendor/schema-org-v2/chunks/index27.mjs +29 -0
- package/dist/vendor/schema-org-v2/chunks/index28.mjs +12 -0
- package/dist/vendor/schema-org-v2/chunks/index29.mjs +46 -0
- package/dist/vendor/schema-org-v2/chunks/index3.mjs +317 -0
- package/dist/vendor/schema-org-v2/chunks/index30.mjs +53 -0
- package/dist/vendor/schema-org-v2/chunks/index31.mjs +41 -0
- package/dist/vendor/schema-org-v2/chunks/index32.mjs +26 -0
- package/dist/vendor/schema-org-v2/chunks/index33.mjs +47 -0
- package/dist/vendor/schema-org-v2/chunks/index34.mjs +29 -0
- package/dist/vendor/schema-org-v2/chunks/index35.mjs +34 -0
- package/dist/vendor/schema-org-v2/chunks/index36.mjs +33 -0
- package/dist/vendor/schema-org-v2/chunks/index37.mjs +34 -0
- package/dist/vendor/schema-org-v2/chunks/index38.mjs +51 -0
- package/dist/vendor/schema-org-v2/chunks/index39.mjs +17 -0
- package/dist/vendor/schema-org-v2/chunks/index4.mjs +54 -0
- package/dist/vendor/schema-org-v2/chunks/index40.mjs +29 -0
- package/dist/vendor/schema-org-v2/chunks/index5.mjs +31 -0
- package/dist/vendor/schema-org-v2/chunks/index6.mjs +29 -0
- package/dist/vendor/schema-org-v2/chunks/index7.mjs +35 -0
- package/dist/vendor/schema-org-v2/chunks/index8.mjs +18 -0
- package/dist/vendor/schema-org-v2/chunks/index9.mjs +20 -0
- package/dist/vendor/schema-org-v2/index.d.mts +32 -0
- package/dist/vendor/schema-org-v2/index.d.ts +32 -0
- package/dist/vendor/schema-org-v2/index.mjs +46 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.BR4WcSqZ.d.ts +21 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.Ba7D0Hp1.mjs +19 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.CAKsqzbX.d.ts +1022 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.CFcsqFfN.d.mts +1824 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.CFcsqFfN.d.ts +1824 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.CHbRCiep.mjs +52 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.Dryb3EoR.mjs +884 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.F44ipjVJ.mjs +27 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.UT1u1UYq.d.mts +1022 -0
- package/dist/vendor/schema-org-v2/shared/schema-org.oFHFm6my.d.mts +21 -0
- package/dist/vendor/schema-org-v2/vendor.json +4 -0
- package/dist/vendor/schema-org-v2/vue.d.mts +88 -0
- package/dist/vendor/schema-org-v2/vue.d.ts +88 -0
- package/dist/vendor/schema-org-v2/vue.mjs +262 -0
- package/dist/vendor/schema-org-v3/LICENSE +21 -0
- package/dist/vendor/schema-org-v3/index.d.mts +34 -0
- package/dist/vendor/schema-org-v3/index.d.ts +34 -0
- package/dist/vendor/schema-org-v3/index.mjs +4 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.D9o9UOh2.d.mts +11 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.DNQroCjX.d.mts +2770 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.DNQroCjX.d.ts +2770 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.DYFTMLZ0.mjs +2035 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.DjDPlqPB.d.mts +150 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.mKZYVZ3E.d.ts +150 -0
- package/dist/vendor/schema-org-v3/shared/schema-org.rS-TANmN.d.ts +11 -0
- package/dist/vendor/schema-org-v3/vendor.json +4 -0
- package/dist/vendor/schema-org-v3/vue.d.mts +100 -0
- package/dist/vendor/schema-org-v3/vue.d.ts +100 -0
- package/dist/vendor/schema-org-v3/vue.mjs +302 -0
- package/package.json +12 -12
- package/dist/devtools/200.html +0 -1
- package/dist/devtools/404.html +0 -1
- package/dist/devtools/_nuxt/B1DdtD2d.js +0 -30
- package/dist/devtools/_nuxt/BOpHBWj1.js +0 -1
- package/dist/devtools/_nuxt/BdQEHo9i.js +0 -1
- package/dist/devtools/_nuxt/BvDCh0XJ.js +0 -1
- package/dist/devtools/_nuxt/C6vgB62o.js +0 -1
- package/dist/devtools/_nuxt/CCjZBs6a.js +0 -3
- package/dist/devtools/_nuxt/CHHO6nw6.js +0 -1
- package/dist/devtools/_nuxt/CQr7N5ou.js +0 -6
- package/dist/devtools/_nuxt/CRABcd13.js +0 -1
- package/dist/devtools/_nuxt/Cy67RPmZ.js +0 -1
- package/dist/devtools/_nuxt/D-Oj-loM.js +0 -154
- package/dist/devtools/_nuxt/D6u4txBo.js +0 -1
- package/dist/devtools/_nuxt/DKZUJ--l.js +0 -1
- package/dist/devtools/_nuxt/DLXftbE1.js +0 -1
- package/dist/devtools/_nuxt/DevtoolsSnippet.CuW0O0Zu.css +0 -1
- package/dist/devtools/_nuxt/QcnxyHc5.js +0 -1
- package/dist/devtools/_nuxt/builds/latest.json +0 -1
- package/dist/devtools/_nuxt/builds/meta/b29085bd-1521-40f3-8c81-bdc818184bef.json +0 -1
- package/dist/devtools/_nuxt/entry.DprwPCib.css +0 -2
- package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
- package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
- package/dist/devtools/_nuxt/pages.BpqfEWCe.css +0 -1
- package/dist/devtools/debug/index.html +0 -1
- package/dist/devtools/docs/index.html +0 -1
- package/dist/devtools/index.html +0 -1
- package/dist/devtools/nodes/index.html +0 -1
- package/dist/devtools/raw/index.html +0 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// Rich result schema types that Google supports
|
|
2
|
+
export const richResultTypes = new Set([
|
|
3
|
+
'Article',
|
|
4
|
+
'NewsArticle',
|
|
5
|
+
'BlogPosting',
|
|
6
|
+
'ScholarlyArticle',
|
|
7
|
+
'Product',
|
|
8
|
+
'AggregateOffer',
|
|
9
|
+
'Offer',
|
|
10
|
+
'FAQPage',
|
|
11
|
+
'Question',
|
|
12
|
+
'HowTo',
|
|
13
|
+
'HowToStep',
|
|
14
|
+
'Recipe',
|
|
15
|
+
'Event',
|
|
16
|
+
'LocalBusiness',
|
|
17
|
+
'Restaurant',
|
|
18
|
+
'JobPosting',
|
|
19
|
+
'Course',
|
|
20
|
+
'Movie',
|
|
21
|
+
'Book',
|
|
22
|
+
'SoftwareApplication',
|
|
23
|
+
'VideoObject',
|
|
24
|
+
'Review',
|
|
25
|
+
'AggregateRating',
|
|
26
|
+
'BreadcrumbList',
|
|
27
|
+
'SearchAction',
|
|
28
|
+
'Dataset',
|
|
29
|
+
'SpecialAnnouncement',
|
|
30
|
+
'Person',
|
|
31
|
+
'NewsMediaOrganization',
|
|
32
|
+
'Organization',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
// Google Rich Results requirements for each schema type
|
|
36
|
+
export const googleRichResultsRequirements: Record<string, {
|
|
37
|
+
required: string[]
|
|
38
|
+
recommended: string[]
|
|
39
|
+
documentationUrl: string
|
|
40
|
+
}> = {
|
|
41
|
+
Article: {
|
|
42
|
+
required: [],
|
|
43
|
+
recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
|
|
44
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
|
|
45
|
+
},
|
|
46
|
+
NewsArticle: {
|
|
47
|
+
required: [],
|
|
48
|
+
recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
|
|
49
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
|
|
50
|
+
},
|
|
51
|
+
BlogPosting: {
|
|
52
|
+
required: [],
|
|
53
|
+
recommended: ['author', 'author.name', 'dateModified', 'datePublished', 'headline', 'image'],
|
|
54
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/article',
|
|
55
|
+
},
|
|
56
|
+
Product: {
|
|
57
|
+
required: ['name'],
|
|
58
|
+
recommended: ['description', 'offers', 'offers.price', 'offers.priceCurrency', 'offers.availability', 'aggregateRating', 'review', 'image'],
|
|
59
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/product',
|
|
60
|
+
},
|
|
61
|
+
FAQPage: {
|
|
62
|
+
required: ['mainEntity'],
|
|
63
|
+
recommended: [],
|
|
64
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/faqpage',
|
|
65
|
+
},
|
|
66
|
+
Recipe: {
|
|
67
|
+
required: ['name', 'image'],
|
|
68
|
+
recommended: ['aggregateRating', 'author', 'cookTime', 'datePublished', 'description', 'keywords', 'nutrition', 'prepTime', 'recipeCategory', 'recipeCuisine', 'recipeIngredient', 'recipeInstructions', 'recipeYield', 'totalTime', 'video'],
|
|
69
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/recipe',
|
|
70
|
+
},
|
|
71
|
+
Event: {
|
|
72
|
+
required: ['name', 'location', 'startDate'],
|
|
73
|
+
recommended: ['description', 'endDate', 'eventStatus', 'image', 'offers', 'performer', 'organizer'],
|
|
74
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/event',
|
|
75
|
+
},
|
|
76
|
+
LocalBusiness: {
|
|
77
|
+
required: ['name', 'address'],
|
|
78
|
+
recommended: ['aggregateRating', 'department', 'geo', 'openingHoursSpecification', 'priceRange', 'telephone', 'url'],
|
|
79
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/local-business',
|
|
80
|
+
},
|
|
81
|
+
Restaurant: {
|
|
82
|
+
required: ['name', 'address'],
|
|
83
|
+
recommended: ['aggregateRating', 'servesCuisine', 'hasMenu', 'geo', 'openingHoursSpecification', 'priceRange', 'telephone', 'url'],
|
|
84
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/local-business',
|
|
85
|
+
},
|
|
86
|
+
Review: {
|
|
87
|
+
required: ['author', 'itemReviewed', 'reviewRating'],
|
|
88
|
+
recommended: ['datePublished', 'reviewRating.bestRating', 'reviewRating.worstRating'],
|
|
89
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/review-snippet',
|
|
90
|
+
},
|
|
91
|
+
AggregateRating: {
|
|
92
|
+
required: ['itemReviewed', 'ratingValue'],
|
|
93
|
+
recommended: ['bestRating', 'worstRating', 'ratingCount', 'reviewCount'],
|
|
94
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/review-snippet',
|
|
95
|
+
},
|
|
96
|
+
BreadcrumbList: {
|
|
97
|
+
required: ['itemListElement'],
|
|
98
|
+
recommended: [],
|
|
99
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/breadcrumb',
|
|
100
|
+
},
|
|
101
|
+
Organization: {
|
|
102
|
+
required: [],
|
|
103
|
+
recommended: ['name', 'logo', 'url', 'email', 'telephone', 'contactPoint', 'sameAs', 'address', 'description'],
|
|
104
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/organization',
|
|
105
|
+
},
|
|
106
|
+
Person: {
|
|
107
|
+
required: [],
|
|
108
|
+
recommended: ['name', 'url', 'image', 'sameAs', 'jobTitle', 'worksFor'],
|
|
109
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/person',
|
|
110
|
+
},
|
|
111
|
+
SoftwareApplication: {
|
|
112
|
+
required: ['name', 'offers'],
|
|
113
|
+
recommended: ['applicationCategory', 'operatingSystem', 'aggregateRating', 'review'],
|
|
114
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/software-app',
|
|
115
|
+
},
|
|
116
|
+
VideoObject: {
|
|
117
|
+
required: ['name', 'thumbnailUrl', 'uploadDate'],
|
|
118
|
+
recommended: ['contentUrl', 'description', 'duration', 'embedUrl'],
|
|
119
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/video',
|
|
120
|
+
},
|
|
121
|
+
JobPosting: {
|
|
122
|
+
required: ['datePosted', 'description', 'hiringOrganization', 'jobLocation', 'title'],
|
|
123
|
+
recommended: ['applicantLocationRequirements', 'baseSalary', 'employmentType', 'jobLocationType', 'validThrough'],
|
|
124
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/job-posting',
|
|
125
|
+
},
|
|
126
|
+
Course: {
|
|
127
|
+
required: ['description', 'name'],
|
|
128
|
+
recommended: ['provider'],
|
|
129
|
+
documentationUrl: 'https://developers.google.com/search/docs/appearance/structured-data/course',
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Recursively extract all typed objects from a schema node
|
|
134
|
+
function extractNestedTypes(obj: any, nodes: any[], seen: Set<any>): void {
|
|
135
|
+
if (!obj || typeof obj !== 'object' || seen.has(obj))
|
|
136
|
+
return
|
|
137
|
+
seen.add(obj)
|
|
138
|
+
|
|
139
|
+
if (obj['@type'] && Object.keys(obj).length > 1) {
|
|
140
|
+
nodes.push(obj)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const value of Object.values(obj)) {
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
for (const item of value) {
|
|
146
|
+
extractNestedTypes(item, nodes, seen)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if (typeof value === 'object') {
|
|
150
|
+
extractNestedTypes(value, nodes, seen)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Resolve @id references in a node
|
|
156
|
+
function resolveNodeReferences(node: any, nodeMap: Map<string, any>): any {
|
|
157
|
+
const resolved = { ...node }
|
|
158
|
+
|
|
159
|
+
Object.keys(resolved).forEach((key) => {
|
|
160
|
+
const value = resolved[key]
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
value
|
|
164
|
+
&& typeof value === 'object'
|
|
165
|
+
&& !Array.isArray(value)
|
|
166
|
+
&& Object.keys(value).length === 1
|
|
167
|
+
&& value['@id']
|
|
168
|
+
&& nodeMap.has(value['@id'])
|
|
169
|
+
) {
|
|
170
|
+
resolved[key] = { ...nodeMap.get(value['@id']) }
|
|
171
|
+
}
|
|
172
|
+
else if (Array.isArray(value)) {
|
|
173
|
+
resolved[key] = value.map((item: any) => {
|
|
174
|
+
if (
|
|
175
|
+
item
|
|
176
|
+
&& typeof item === 'object'
|
|
177
|
+
&& Object.keys(item).length === 1
|
|
178
|
+
&& item['@id']
|
|
179
|
+
&& nodeMap.has(item['@id'])
|
|
180
|
+
) {
|
|
181
|
+
return { ...nodeMap.get(item['@id']) }
|
|
182
|
+
}
|
|
183
|
+
return item
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
else if (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 1) {
|
|
187
|
+
resolved[key] = resolveNodeReferences(value, nodeMap)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return resolved
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Extract all nodes from a schema graph (including nested @graph and nested types)
|
|
195
|
+
export function extractSchemaNodes(data: any): any[] {
|
|
196
|
+
const nodes: any[] = []
|
|
197
|
+
if (!data)
|
|
198
|
+
return nodes
|
|
199
|
+
|
|
200
|
+
const seen = new Set<any>()
|
|
201
|
+
|
|
202
|
+
if (data['@graph'] && Array.isArray(data['@graph'])) {
|
|
203
|
+
const nodeMap = new Map<string, any>()
|
|
204
|
+
data['@graph'].forEach((node: any) => {
|
|
205
|
+
if (node && typeof node === 'object' && node['@id']) {
|
|
206
|
+
nodeMap.set(node['@id'], node)
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const addedIds = new Set<string>()
|
|
211
|
+
|
|
212
|
+
data['@graph'].forEach((node: any) => {
|
|
213
|
+
if (node && typeof node === 'object') {
|
|
214
|
+
if (Object.keys(node).length === 1 && node['@id'])
|
|
215
|
+
return
|
|
216
|
+
if (node['@id'] && addedIds.has(node['@id']))
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
const resolvedNode = resolveNodeReferences(node, nodeMap)
|
|
220
|
+
extractNestedTypes(resolvedNode, nodes, seen)
|
|
221
|
+
|
|
222
|
+
if (node['@id'])
|
|
223
|
+
addedIds.add(node['@id'])
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
else if (data['@type']) {
|
|
228
|
+
extractNestedTypes(data, nodes, seen)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Deduplicate nodes that appear multiple times due to @id reference resolution
|
|
232
|
+
const uniqueNodes: any[] = []
|
|
233
|
+
const seenIds = new Set<string>()
|
|
234
|
+
for (const node of nodes) {
|
|
235
|
+
const id = node['@id']
|
|
236
|
+
if (id) {
|
|
237
|
+
if (seenIds.has(id))
|
|
238
|
+
continue
|
|
239
|
+
seenIds.add(id)
|
|
240
|
+
}
|
|
241
|
+
uniqueNodes.push(node)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return uniqueNodes
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getNodeType(node: any): string {
|
|
248
|
+
if (!node || !node['@type'])
|
|
249
|
+
return 'Unknown'
|
|
250
|
+
return Array.isArray(node['@type']) ? node['@type'][0] : node['@type']
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function isRichResultType(type: string): boolean {
|
|
254
|
+
return richResultTypes.has(type)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function getNodeDescription(node: any): string {
|
|
258
|
+
const type = getNodeType(node)
|
|
259
|
+
|
|
260
|
+
if (node.name)
|
|
261
|
+
return node.name
|
|
262
|
+
if (node.headline)
|
|
263
|
+
return node.headline
|
|
264
|
+
if (node.title)
|
|
265
|
+
return node.title
|
|
266
|
+
if (node.description) {
|
|
267
|
+
return typeof node.description === 'string'
|
|
268
|
+
? node.description.substring(0, 100) + (node.description.length > 100 ? '...' : '')
|
|
269
|
+
: ''
|
|
270
|
+
}
|
|
271
|
+
if (node['@id'])
|
|
272
|
+
return node['@id']
|
|
273
|
+
if (node.url)
|
|
274
|
+
return node.url
|
|
275
|
+
|
|
276
|
+
return `${type} Schema`
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function getNestedProperty(obj: any, path: string): any {
|
|
280
|
+
const parts = path.split('.')
|
|
281
|
+
let current = obj
|
|
282
|
+
|
|
283
|
+
for (const part of parts) {
|
|
284
|
+
if (current && typeof current === 'object' && part in current) {
|
|
285
|
+
current = current[part]
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return undefined
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return current
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function analyzeNodeProperties(node: any): {
|
|
296
|
+
missingRequired: string[]
|
|
297
|
+
missingRecommended: string[]
|
|
298
|
+
presentProperties: Record<string, any>
|
|
299
|
+
} {
|
|
300
|
+
const type = getNodeType(node)
|
|
301
|
+
const requirements = googleRichResultsRequirements[type]
|
|
302
|
+
|
|
303
|
+
if (!requirements) {
|
|
304
|
+
return {
|
|
305
|
+
missingRequired: [],
|
|
306
|
+
missingRecommended: [],
|
|
307
|
+
presentProperties: {},
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const missingRequired: string[] = []
|
|
312
|
+
const missingRecommended: string[] = []
|
|
313
|
+
const presentProperties: Record<string, any> = {}
|
|
314
|
+
|
|
315
|
+
requirements.required.forEach((prop) => {
|
|
316
|
+
const value = getNestedProperty(node, prop)
|
|
317
|
+
if (value === undefined || value === null || value === '') {
|
|
318
|
+
missingRequired.push(prop)
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
presentProperties[prop] = value
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
requirements.recommended.forEach((prop) => {
|
|
326
|
+
const value = getNestedProperty(node, prop)
|
|
327
|
+
if (value === undefined || value === null || value === '') {
|
|
328
|
+
missingRecommended.push(prop)
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
presentProperties[prop] = value
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
return { missingRequired, missingRecommended, presentProperties }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function formatPropertyValue(value: any): string {
|
|
339
|
+
if (value === null || value === undefined)
|
|
340
|
+
return 'null'
|
|
341
|
+
if (typeof value === 'string')
|
|
342
|
+
return value.length > 50 ? `${value.substring(0, 50)}...` : value
|
|
343
|
+
if (typeof value === 'number')
|
|
344
|
+
return value.toString()
|
|
345
|
+
if (typeof value === 'boolean')
|
|
346
|
+
return value ? 'true' : 'false'
|
|
347
|
+
if (Array.isArray(value))
|
|
348
|
+
return `[${value.length} items]`
|
|
349
|
+
if (typeof value === 'object' && value['@type'])
|
|
350
|
+
return value['@type']
|
|
351
|
+
if (typeof value === 'object')
|
|
352
|
+
return '{...}'
|
|
353
|
+
return String(value)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function getSchemaIcon(type: string): string {
|
|
357
|
+
const iconMap: Record<string, string> = {
|
|
358
|
+
Article: 'carbon:document',
|
|
359
|
+
NewsArticle: 'carbon:news',
|
|
360
|
+
BlogPosting: 'carbon:blog',
|
|
361
|
+
Product: 'carbon:shopping-cart',
|
|
362
|
+
FAQPage: 'carbon:help',
|
|
363
|
+
Organization: 'carbon:building',
|
|
364
|
+
LocalBusiness: 'carbon:location',
|
|
365
|
+
Person: 'carbon:user',
|
|
366
|
+
Event: 'carbon:calendar',
|
|
367
|
+
SoftwareApplication: 'carbon:application',
|
|
368
|
+
Recipe: 'carbon:restaurant',
|
|
369
|
+
HowTo: 'carbon:list-numbered',
|
|
370
|
+
WebSite: 'carbon:earth',
|
|
371
|
+
WebPage: 'carbon:page-first',
|
|
372
|
+
BreadcrumbList: 'carbon:flow',
|
|
373
|
+
VideoObject: 'carbon:video',
|
|
374
|
+
Review: 'carbon:star',
|
|
375
|
+
AggregateRating: 'carbon:star-filled',
|
|
376
|
+
SearchAction: 'carbon:search',
|
|
377
|
+
}
|
|
378
|
+
return iconMap[type] || 'carbon:code'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export interface ValidationSummary {
|
|
382
|
+
totalNodes: number
|
|
383
|
+
richResultNodes: number
|
|
384
|
+
totalErrors: number
|
|
385
|
+
totalWarnings: number
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Google structured data page slugs mapped to schema types
|
|
389
|
+
export const googleStructuredDataLinks: Record<string, string[]> = {
|
|
390
|
+
'article': ['Article', 'NewsArticle', 'BlogPosting'],
|
|
391
|
+
'book': ['Book'],
|
|
392
|
+
'breadcrumb': ['BreadcrumbList'],
|
|
393
|
+
'carousel': ['ItemList'],
|
|
394
|
+
'course-info': ['Course'],
|
|
395
|
+
'course': ['Course'],
|
|
396
|
+
'dataset': ['Dataset'],
|
|
397
|
+
'discussion-forum': ['DiscussionForumPosting'],
|
|
398
|
+
'education-qa': ['Question', 'Answer'],
|
|
399
|
+
'employer-rating': ['EmployerAggregateRating'],
|
|
400
|
+
'estimated-salary': ['OccupationalExperienceRequirements'],
|
|
401
|
+
'event': ['Event'],
|
|
402
|
+
'factcheck': ['ClaimReview'],
|
|
403
|
+
'faqpage': ['FAQPage'],
|
|
404
|
+
'image-license-metadata': ['ImageObject'],
|
|
405
|
+
'job-posting': ['JobPosting'],
|
|
406
|
+
'learning-video': ['LearningResource', 'VideoObject'],
|
|
407
|
+
'local-business': ['LocalBusiness'],
|
|
408
|
+
'math-solvers': ['MathSolver'],
|
|
409
|
+
'movie': ['Movie'],
|
|
410
|
+
'organization': ['Organization'],
|
|
411
|
+
'practice-problems': ['Quiz', 'Question'],
|
|
412
|
+
'product': ['Product'],
|
|
413
|
+
'product-snippet': ['Product'],
|
|
414
|
+
'merchant-listing': ['Product', 'Offer'],
|
|
415
|
+
'product-variants': ['Product'],
|
|
416
|
+
'profile-page': ['ProfilePage', 'Person'],
|
|
417
|
+
'qapage': ['QAPage'],
|
|
418
|
+
'recipe': ['Recipe'],
|
|
419
|
+
'review-snippet': ['Review'],
|
|
420
|
+
'software-app': ['SoftwareApplication'],
|
|
421
|
+
'speakable': ['SpeakableSpecification'],
|
|
422
|
+
'special-announcements': ['SpecialAnnouncement'],
|
|
423
|
+
'paywalled-content': ['CreativeWork'],
|
|
424
|
+
'vacation-rental': ['Accommodation', 'LodgingBusiness'],
|
|
425
|
+
'vehicle-listing': ['Vehicle'],
|
|
426
|
+
'video': ['VideoObject'],
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function nodeToSchemaOrgLink(type: string) {
|
|
430
|
+
const simpleType = type.replace('https://schema.org/', '')
|
|
431
|
+
const googlePage = Object.entries(googleStructuredDataLinks)
|
|
432
|
+
.find(([_, types]) => types.includes(simpleType))?.[0]
|
|
433
|
+
return {
|
|
434
|
+
type: simpleType,
|
|
435
|
+
schemaOrg: `https://schema.org/${simpleType}`,
|
|
436
|
+
googlePage: googlePage ? `https://developers.google.com/search/docs/appearance/structured-data/${googlePage}` : null,
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function asArray<T>(value: T | T[]): T[] {
|
|
441
|
+
return Array.isArray(value) ? value : [value]
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function validateGraph(data: any): { nodes: any[], summary: ValidationSummary } {
|
|
445
|
+
const nodes = extractSchemaNodes(data)
|
|
446
|
+
|
|
447
|
+
let totalErrors = 0
|
|
448
|
+
let totalWarnings = 0
|
|
449
|
+
let richResultNodes = 0
|
|
450
|
+
|
|
451
|
+
for (const node of nodes) {
|
|
452
|
+
const type = getNodeType(node)
|
|
453
|
+
if (isRichResultType(type))
|
|
454
|
+
richResultNodes++
|
|
455
|
+
|
|
456
|
+
const analysis = analyzeNodeProperties(node)
|
|
457
|
+
totalErrors += analysis.missingRequired.length
|
|
458
|
+
totalWarnings += analysis.missingRecommended.length
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
nodes,
|
|
463
|
+
summary: {
|
|
464
|
+
totalNodes: nodes.length,
|
|
465
|
+
richResultNodes,
|
|
466
|
+
totalErrors,
|
|
467
|
+
totalWarnings,
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resolve } from 'pathe'
|
|
2
|
+
|
|
3
|
+
// Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
|
|
4
|
+
// so intra-panel references resolve by name.
|
|
5
|
+
export default defineNuxtConfig({
|
|
6
|
+
components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
|
|
7
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { fetchGlobalDebug } from '../../lib/schema-org/fetch'
|
|
3
|
+
|
|
4
|
+
const { data } = fetchGlobalDebug()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="space-y-5 animate-fade-up">
|
|
9
|
+
<DevtoolsSection>
|
|
10
|
+
<template #text>
|
|
11
|
+
<h3 class="opacity-80 text-base mb-1">
|
|
12
|
+
<UIcon name="carbon:settings" class="mr-1" />
|
|
13
|
+
Runtime Config
|
|
14
|
+
</h3>
|
|
15
|
+
</template>
|
|
16
|
+
<DevtoolsSnippet
|
|
17
|
+
:code="JSON.stringify(data?.runtimeConfig || {}, null, 2)"
|
|
18
|
+
lang="json"
|
|
19
|
+
label="Runtime Config"
|
|
20
|
+
/>
|
|
21
|
+
</DevtoolsSection>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { schemaOrgGraph } from '../../lib/schema-org/rpc'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="space-y-4">
|
|
7
|
+
<div class="flex items-center gap-3">
|
|
8
|
+
<UButton
|
|
9
|
+
icon="carbon:launch"
|
|
10
|
+
to="https://validator.schema.org/"
|
|
11
|
+
target="_blank"
|
|
12
|
+
>
|
|
13
|
+
Structured Data Test
|
|
14
|
+
</UButton>
|
|
15
|
+
<UButton
|
|
16
|
+
icon="carbon:launch"
|
|
17
|
+
to="https://search.google.com/test/rich-results"
|
|
18
|
+
target="_blank"
|
|
19
|
+
>
|
|
20
|
+
Rich Results Test
|
|
21
|
+
</UButton>
|
|
22
|
+
</div>
|
|
23
|
+
<DevtoolsSnippet :code="schemaOrgGraph" lang="json" label="schema.json" />
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, watch } from 'vue'
|
|
3
|
+
import { isProductionMode, loadShiki, navigateTo, refreshSources, useRoute } from '#imports'
|
|
4
|
+
import { fetchGlobalDebug } from '../lib/schema-org/fetch'
|
|
5
|
+
import { schemaOrgGraph } from '../lib/schema-org/rpc'
|
|
6
|
+
|
|
7
|
+
await loadShiki()
|
|
8
|
+
|
|
9
|
+
const { data } = fetchGlobalDebug()
|
|
10
|
+
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const currentTab = computed(() => {
|
|
13
|
+
const path = route.path
|
|
14
|
+
if (path.startsWith('/schema-org/raw'))
|
|
15
|
+
return 'raw'
|
|
16
|
+
if (path.startsWith('/schema-org/debug'))
|
|
17
|
+
return 'debug'
|
|
18
|
+
if (path.startsWith('/schema-org/docs'))
|
|
19
|
+
return 'docs'
|
|
20
|
+
return 'validate'
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const navItems = [
|
|
24
|
+
{ value: 'validate', to: '/schema-org', icon: 'carbon:connect-source', label: 'Nodes', devOnly: false },
|
|
25
|
+
{ value: 'raw', to: '/schema-org/raw', icon: 'carbon:code', label: 'Raw', devOnly: true },
|
|
26
|
+
{ value: 'debug', to: '/schema-org/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },
|
|
27
|
+
{ value: 'docs', to: '/schema-org/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const runtimeVersion = computed(() => {
|
|
31
|
+
return data.value?.runtimeConfig?.version || 'unknown'
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
watch(isProductionMode, (isProd) => {
|
|
35
|
+
if (isProd && ['raw', 'debug'].includes(currentTab.value))
|
|
36
|
+
return navigateTo('/schema-org')
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<DevtoolsLayout
|
|
42
|
+
v-model:active-tab="currentTab"
|
|
43
|
+
module-name="nuxt-schema-org"
|
|
44
|
+
title="Schema.org"
|
|
45
|
+
icon="carbon:image-search"
|
|
46
|
+
:version="runtimeVersion"
|
|
47
|
+
:nav-items="navItems"
|
|
48
|
+
github-url="https://github.com/harlan-zw/nuxt-schema-org"
|
|
49
|
+
:loading="!schemaOrgGraph || schemaOrgGraph === 'loading'"
|
|
50
|
+
@refresh="refreshSources"
|
|
51
|
+
>
|
|
52
|
+
<NuxtPage />
|
|
53
|
+
</DevtoolsLayout>
|
|
54
|
+
</template>
|
package/dist/module.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const node_url = require('node:url');
|
|
3
4
|
const kit = require('@nuxt/kit');
|
|
4
5
|
const defu = require('defu');
|
|
5
6
|
const kit$1 = require('nuxt-site-config/kit');
|
|
6
7
|
const pkgTypes = require('pkg-types');
|
|
7
8
|
const devtools = require('nuxtseo-shared/devtools');
|
|
8
9
|
const kit$2 = require('nuxtseo-shared/kit');
|
|
10
|
+
const node_fs = require('node:fs');
|
|
9
11
|
const node_path = require('node:path');
|
|
10
|
-
const node_url = require('node:url');
|
|
11
12
|
|
|
12
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
14
|
function setupDevToolsUI(_options, resolve, nuxt = kit.useNuxt()) {
|
|
@@ -79,9 +80,12 @@ async function resolveHostUnheadMajor(rootDir) {
|
|
|
79
80
|
}
|
|
80
81
|
return 3;
|
|
81
82
|
}
|
|
82
|
-
function schemaOrgVendor(major) {
|
|
83
|
+
function schemaOrgVendor(major, resolve) {
|
|
84
|
+
const dir = resolve(`./vendor/schema-org-v${major}`);
|
|
85
|
+
if (node_fs.existsSync(dir))
|
|
86
|
+
return { vendored: true, dir, main: `${dir}/index.mjs`, vue: `${dir}/vue.mjs` };
|
|
83
87
|
const pkg = major === 2 ? "@unhead/schema-org-v2" : "@unhead/schema-org";
|
|
84
|
-
return { main: pkg, vue: `${pkg}/vue` };
|
|
88
|
+
return { vendored: false, main: pkg, vue: `${pkg}/vue` };
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
const module$1 = kit.defineNuxtModule({
|
|
@@ -132,15 +136,32 @@ const module$1 = kit.defineNuxtModule({
|
|
|
132
136
|
if (!nuxt.options.ssr && nuxt.options.dev)
|
|
133
137
|
logger.warn("You are using Schema.org with SSR disabled. This is not recommended, Google may not detect your Schema.org, and it adds extra page weight");
|
|
134
138
|
const unheadMajor = await resolveHostUnheadMajor(nuxt.options.rootDir);
|
|
135
|
-
const vendor = schemaOrgVendor(unheadMajor);
|
|
136
|
-
const { defineWebPage } = await import(vendor.main);
|
|
137
|
-
const { schemaOrgAutoImports, schemaOrgComponents } = await import(vendor.vue);
|
|
138
|
-
if (vendor.
|
|
139
|
+
const vendor = schemaOrgVendor(unheadMajor, resolve);
|
|
140
|
+
const { defineWebPage } = await (vendor.vendored ? import(node_url.pathToFileURL(vendor.main).href) : import(vendor.main));
|
|
141
|
+
const { schemaOrgAutoImports, schemaOrgComponents } = await (vendor.vendored ? import(node_url.pathToFileURL(vendor.vue).href) : import(vendor.vue));
|
|
142
|
+
if (vendor.vendored) {
|
|
143
|
+
logger.debug(`Detected unhead v${unheadMajor}, aliasing @unhead/schema-org -> ${vendor.dir}`);
|
|
144
|
+
nuxt.options.alias["@unhead/schema-org/vue"] = vendor.vue;
|
|
145
|
+
nuxt.options.alias["@unhead/schema-org"] = vendor.main;
|
|
146
|
+
nuxt.options.build.transpile.push(vendor.dir);
|
|
147
|
+
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
148
|
+
nitroConfig.alias = nitroConfig.alias || {};
|
|
149
|
+
nitroConfig.alias["@unhead/schema-org/vue"] = vendor.vue;
|
|
150
|
+
nitroConfig.alias["@unhead/schema-org"] = vendor.main;
|
|
151
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
152
|
+
nitroConfig.externals.inline = nitroConfig.externals.inline || [];
|
|
153
|
+
nitroConfig.externals.inline.push(vendor.dir);
|
|
154
|
+
});
|
|
155
|
+
} else if (vendor.main !== "@unhead/schema-org") {
|
|
139
156
|
logger.debug(`Detected unhead v${unheadMajor}, aliasing @unhead/schema-org -> ${vendor.main}`);
|
|
140
157
|
nuxt.options.alias["@unhead/schema-org"] = vendor.main;
|
|
158
|
+
nuxt.options.build.transpile.push(vendor.main);
|
|
141
159
|
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
142
160
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
143
161
|
nitroConfig.alias["@unhead/schema-org"] = vendor.main;
|
|
162
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
163
|
+
nitroConfig.externals.inline = nitroConfig.externals.inline || [];
|
|
164
|
+
nitroConfig.externals.inline.push(vendor.main);
|
|
144
165
|
});
|
|
145
166
|
}
|
|
146
167
|
await kit$1.installNuxtSiteConfig();
|
package/dist/module.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
-
import { OrganizationSimple, PersonSimple, LocalBusinessSimple } from '
|
|
2
|
+
import { O as OrganizationSimple, P as PersonSimple, L as LocalBusinessSimple } from './shared/nuxt-schema-org.BQ-jW5j5.cjs';
|
|
3
3
|
import { Script } from '@unhead/vue/types';
|
|
4
4
|
|
|
5
5
|
type SchemaOrgScriptAttributes = Partial<Script> & Record<string, unknown>;
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
-
import { OrganizationSimple, PersonSimple, LocalBusinessSimple } from '
|
|
2
|
+
import { O as OrganizationSimple, P as PersonSimple, L as LocalBusinessSimple } from './shared/nuxt-schema-org.BQ-jW5j5.mjs';
|
|
3
3
|
import { Script } from '@unhead/vue/types';
|
|
4
4
|
|
|
5
5
|
type SchemaOrgScriptAttributes = Partial<Script> & Record<string, unknown>;
|