sanity-plugin-seofields 1.3.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.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
5
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
7
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -29,19 +30,659 @@ var __objRest = (source, exclude) => {
29
30
  }
30
31
  return target;
31
32
  };
33
+ var __esm = (fn, res) => function __init() {
34
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
35
+ };
36
+ var __export = (target, all) => {
37
+ for (var name in all)
38
+ __defProp(target, name, { get: all[name], enumerable: true });
39
+ };
32
40
 
33
- // src/plugin.ts
34
- import React13 from "react";
35
- import { definePlugin } from "sanity";
41
+ // src/utils/seoUtils.ts
42
+ var stopWords, hasMatchingKeyword, hasKeywordOveruse, startsWithStopWord, truncate, hasExcessivePunctuation, getMetaTitleValidationMessages, getMetaDescriptionValidationMessages, getOgTitleValidation, getOgDescriptionValidation, getTwitterTitleValidation, getTwitterDescriptionValidation, isSubImageSet, isMetaImageSet, getMetaImageValidation, getOgImageValidation, getOgImageUrlValidation, getTwitterImageValidation, getTwitterImageUrlValidation;
43
+ var init_seoUtils = __esm({
44
+ "src/utils/seoUtils.ts"() {
45
+ "use strict";
46
+ stopWords = ["the", "a", "an", "and", "or", "but"];
47
+ hasMatchingKeyword = (title, keywordList) => {
48
+ if (!title || keywordList.length === 0) return false;
49
+ const lowerTitle = title.toLowerCase();
50
+ return keywordList.some((keyword) => keyword && lowerTitle.includes(keyword.toLowerCase()));
51
+ };
52
+ hasKeywordOveruse = (title, keywordList, maxOccurrences = 3) => {
53
+ if (!title || keywordList.length === 0) return false;
54
+ const lowerTitle = title.toLowerCase();
55
+ return keywordList.some((keyword) => {
56
+ if (!keyword) return false;
57
+ const matches = lowerTitle.match(new RegExp(keyword.toLowerCase(), "g"));
58
+ return matches ? matches.length > maxOccurrences : false;
59
+ });
60
+ };
61
+ startsWithStopWord = (title) => {
62
+ if (!title) return false;
63
+ const firstWord = title.trim().split(" ")[0].toLowerCase();
64
+ return stopWords.includes(firstWord);
65
+ };
66
+ truncate = (text, maxLength) => text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
67
+ hasExcessivePunctuation = (title) => /[!@#$%^&*]{2,}/.test(title);
68
+ getMetaTitleValidationMessages = (title, keywords, isParentseoField) => {
69
+ const feedback = [];
70
+ const minChar = 50;
71
+ const maxChar = 60;
72
+ const charCount = (title == null ? void 0 : title.length) || 0;
73
+ if (!(title == null ? void 0 : title.trim())) {
74
+ feedback.push({ text: "Meta Title is empty. Add content to improve SEO.", color: "red" });
75
+ return feedback;
76
+ }
77
+ if (charCount < minChar)
78
+ feedback.push({
79
+ text: `Title is ${charCount} characters \u2014 below recommended ${minChar}.`,
80
+ color: "orange"
81
+ });
82
+ else if (charCount > maxChar)
83
+ feedback.push({
84
+ text: `Title is ${charCount} characters \u2014 exceeds recommended ${maxChar}.`,
85
+ color: "red"
86
+ });
87
+ else feedback.push({ text: `Title length (${charCount}) looks good for SEO.`, color: "green" });
88
+ if (isParentseoField) {
89
+ if (keywords.length > 0) {
90
+ const hasKeyword = hasMatchingKeyword(title, keywords);
91
+ feedback.push({
92
+ text: hasKeyword ? "Keyword found in title \u2014 good job!" : "Keywords defined but missing in title.",
93
+ color: hasKeyword ? "green" : "red"
94
+ });
95
+ if (hasKeywordOveruse(title, keywords)) {
96
+ feedback.push({
97
+ text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
98
+ color: "orange"
99
+ });
100
+ }
101
+ } else {
102
+ feedback.push({
103
+ text: "No keywords defined. Consider adding relevant keywords.",
104
+ color: "orange"
105
+ });
106
+ }
107
+ }
108
+ if (startsWithStopWord(title))
109
+ feedback.push({ text: "Title starts with a stop word \u2014 consider rephrasing.", color: "orange" });
110
+ if (hasExcessivePunctuation(title))
111
+ feedback.push({ text: "Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
112
+ return feedback;
113
+ };
114
+ getMetaDescriptionValidationMessages = (description, keywords, isParentseoField) => {
115
+ const feedback = [];
116
+ const minChar = 120;
117
+ const maxChar = 160;
118
+ const charCount = (description == null ? void 0 : description.length) || 0;
119
+ if (!(description == null ? void 0 : description.trim())) {
120
+ feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" });
121
+ return feedback;
122
+ }
123
+ if (charCount < minChar)
124
+ feedback.push({
125
+ text: `Description is ${charCount} chars \u2014 below recommended ${minChar}.`,
126
+ color: "orange"
127
+ });
128
+ else if (charCount > maxChar)
129
+ feedback.push({
130
+ text: `Description is ${charCount} chars \u2014 exceeds recommended ${maxChar}.`,
131
+ color: "red"
132
+ });
133
+ else
134
+ feedback.push({ text: `Description length (${charCount}) looks good for SEO.`, color: "green" });
135
+ if (isParentseoField) {
136
+ if (keywords.length > 0) {
137
+ const hasKeyword = hasMatchingKeyword(description, keywords);
138
+ feedback.push({
139
+ text: hasKeyword ? "Keyword found in description \u2014 good job!" : "Keywords defined but missing in description.",
140
+ color: hasKeyword ? "green" : "red"
141
+ });
142
+ if (hasKeywordOveruse(description, keywords)) {
143
+ feedback.push({
144
+ text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
145
+ color: "orange"
146
+ });
147
+ }
148
+ } else {
149
+ feedback.push({
150
+ text: "No keywords defined. Consider adding relevant keywords.",
151
+ color: "orange"
152
+ });
153
+ }
154
+ }
155
+ if (startsWithStopWord(description))
156
+ feedback.push({
157
+ text: "Description starts with a stop word \u2014 consider rephrasing.",
158
+ color: "orange"
159
+ });
160
+ if (hasExcessivePunctuation(description))
161
+ feedback.push({
162
+ text: "Description contains excessive punctuation \u2014 simplify it.",
163
+ color: "orange"
164
+ });
165
+ return feedback;
166
+ };
167
+ getOgTitleValidation = (title, keywords = [], isParentseoField) => {
168
+ const feedback = [];
169
+ const min = 40;
170
+ const max = 60;
171
+ const count = (title == null ? void 0 : title.length) || 0;
172
+ if (!(title == null ? void 0 : title.trim())) {
173
+ feedback.push({ text: "OG Title is empty. Add content for better social preview.", color: "red" });
174
+ return feedback;
175
+ }
176
+ if (count < min)
177
+ feedback.push({
178
+ text: `OG Title is ${count} chars \u2014 shorter than recommended ${min}.`,
179
+ color: "orange"
180
+ });
181
+ else if (count > max)
182
+ feedback.push({ text: `OG Title is ${count} chars \u2014 exceeds recommended ${max}.`, color: "red" });
183
+ else feedback.push({ text: `OG Title length (${count}) looks good.`, color: "green" });
184
+ if (isParentseoField) {
185
+ if (keywords.length > 0) {
186
+ const hasKeyword = hasMatchingKeyword(title, keywords);
187
+ feedback.push({
188
+ text: hasKeyword ? "Keyword found in OG title \u2014 good job!" : "Keywords defined but missing in OG title.",
189
+ color: hasKeyword ? "green" : "red"
190
+ });
191
+ if (hasKeywordOveruse(title, keywords)) {
192
+ feedback.push({
193
+ text: "Keyword appears too many times in OG title \u2014 avoid keyword stuffing.",
194
+ color: "orange"
195
+ });
196
+ }
197
+ } else {
198
+ feedback.push({
199
+ text: "No keywords defined. Consider adding relevant keywords.",
200
+ color: "orange"
201
+ });
202
+ }
203
+ }
204
+ if (startsWithStopWord(title))
205
+ feedback.push({
206
+ text: "OG Title starts with a stop word \u2014 consider rephrasing.",
207
+ color: "orange"
208
+ });
209
+ if (hasExcessivePunctuation(title))
210
+ feedback.push({ text: "OG Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
211
+ return feedback;
212
+ };
213
+ getOgDescriptionValidation = (desc, keywords = [], isParentseoField) => {
214
+ const feedback = [];
215
+ const min = 90;
216
+ const max = 120;
217
+ const count = (desc == null ? void 0 : desc.length) || 0;
218
+ if (!(desc == null ? void 0 : desc.trim())) {
219
+ feedback.push({
220
+ text: "OG Description is empty. Add content for better social preview.",
221
+ color: "red"
222
+ });
223
+ return feedback;
224
+ }
225
+ if (count < min)
226
+ feedback.push({
227
+ text: `OG Description is ${count} chars \u2014 shorter than recommended ${min}.`,
228
+ color: "orange"
229
+ });
230
+ else if (count > max)
231
+ feedback.push({
232
+ text: `OG Description is ${count} chars \u2014 exceeds recommended ${max}.`,
233
+ color: "red"
234
+ });
235
+ else feedback.push({ text: `OG Description length (${count}) looks good.`, color: "green" });
236
+ if (isParentseoField) {
237
+ if (keywords.length > 0) {
238
+ const hasKeyword = hasMatchingKeyword(desc, keywords);
239
+ feedback.push({
240
+ text: hasKeyword ? "Keyword found in OG description \u2014 good job!" : "Keywords defined but missing in OG description.",
241
+ color: hasKeyword ? "green" : "red"
242
+ });
243
+ if (hasKeywordOveruse(desc, keywords)) {
244
+ feedback.push({
245
+ text: "Keyword appears too many times in OG description \u2014 avoid keyword stuffing.",
246
+ color: "orange"
247
+ });
248
+ }
249
+ } else {
250
+ feedback.push({
251
+ text: "No keywords defined. Consider adding relevant keywords.",
252
+ color: "orange"
253
+ });
254
+ }
255
+ }
256
+ if (startsWithStopWord(desc))
257
+ feedback.push({
258
+ text: "OG Description starts with a stop word \u2014 consider rephrasing.",
259
+ color: "orange"
260
+ });
261
+ if (hasExcessivePunctuation(desc))
262
+ feedback.push({
263
+ text: "OG Description contains excessive punctuation \u2014 simplify it.",
264
+ color: "orange"
265
+ });
266
+ return feedback;
267
+ };
268
+ getTwitterTitleValidation = (title, keywords = [], isParentseoField) => {
269
+ const feedback = [];
270
+ const min = 30;
271
+ const max = 70;
272
+ const count = (title == null ? void 0 : title.length) || 0;
273
+ if (!(title == null ? void 0 : title.trim())) {
274
+ feedback.push({ text: "X Title is empty. Add content for better SEO.", color: "red" });
275
+ return feedback;
276
+ }
277
+ if (count < min)
278
+ feedback.push({
279
+ text: `X Title is ${count} chars \u2014 shorter than recommended ${min}.`,
280
+ color: "orange"
281
+ });
282
+ else if (count > max)
283
+ feedback.push({
284
+ text: `X Title is ${count} chars \u2014 exceeds recommended ${max}.`,
285
+ color: "red"
286
+ });
287
+ else feedback.push({ text: `X Title length (${count}) looks good.`, color: "green" });
288
+ if (isParentseoField) {
289
+ if (keywords.length > 0) {
290
+ const hasKeyword = hasMatchingKeyword(title, keywords);
291
+ feedback.push({
292
+ text: hasKeyword ? "Keyword found in X title \u2014 good job!" : "Keywords defined but missing in X title.",
293
+ color: hasKeyword ? "green" : "red"
294
+ });
295
+ } else {
296
+ feedback.push({
297
+ text: "No keywords defined. Consider adding relevant keywords.",
298
+ color: "orange"
299
+ });
300
+ }
301
+ }
302
+ if (/[!@#$%^&*]{2,}/.test(title))
303
+ feedback.push({ text: "X Title has excessive punctuation \u2014 simplify it.", color: "orange" });
304
+ return feedback;
305
+ };
306
+ getTwitterDescriptionValidation = (desc, keywords = [], isParentseoField) => {
307
+ const feedback = [];
308
+ const min = 50;
309
+ const max = 200;
310
+ const count = (desc == null ? void 0 : desc.length) || 0;
311
+ if (!(desc == null ? void 0 : desc.trim())) {
312
+ feedback.push({ text: "X Description is empty. Add content for better SEO.", color: "red" });
313
+ return feedback;
314
+ }
315
+ if (count < min)
316
+ feedback.push({
317
+ text: `X Description is ${count} chars \u2014 shorter than recommended ${min}.`,
318
+ color: "orange"
319
+ });
320
+ else if (count > max)
321
+ feedback.push({
322
+ text: `X Description is ${count} chars \u2014 exceeds recommended ${max}.`,
323
+ color: "red"
324
+ });
325
+ else feedback.push({ text: `X Description length (${count}) looks good.`, color: "green" });
326
+ if (isParentseoField) {
327
+ if (keywords.length > 0) {
328
+ const hasKeyword = hasMatchingKeyword(desc, keywords);
329
+ feedback.push({
330
+ text: hasKeyword ? "Keyword found in X description \u2014 good job!" : "Keywords defined but missing in X description.",
331
+ color: hasKeyword ? "green" : "red"
332
+ });
333
+ } else {
334
+ feedback.push({
335
+ text: "No keywords defined. Consider adding relevant keywords.",
336
+ color: "orange"
337
+ });
338
+ }
339
+ }
340
+ if (/[!@#$%^&*]{2,}/.test(desc))
341
+ feedback.push({
342
+ text: "X Description has excessive punctuation \u2014 simplify it.",
343
+ color: "orange"
344
+ });
345
+ return feedback;
346
+ };
347
+ isSubImageSet = (subObj) => {
348
+ var _a;
349
+ if (!subObj) return false;
350
+ if (subObj.imageType === "url") return !!((_a = subObj.imageUrl) == null ? void 0 : _a.trim());
351
+ const img = subObj.image;
352
+ return !!(img == null ? void 0 : img.asset);
353
+ };
354
+ isMetaImageSet = (seoParent) => {
355
+ if (!seoParent) return false;
356
+ const metaImage = seoParent.metaImage;
357
+ return !!(metaImage == null ? void 0 : metaImage.asset);
358
+ };
359
+ getMetaImageValidation = (hasImage, seoParent) => {
360
+ const feedback = [];
361
+ if (!hasImage) {
362
+ feedback.push({
363
+ text: "No meta image provided. Adding an image improves click-through rates.",
364
+ color: "red"
365
+ });
366
+ return feedback;
367
+ }
368
+ feedback.push({ text: "Meta image is set \u2014 great for SEO and social sharing.", color: "green" });
369
+ const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
370
+ const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
371
+ if (!ogSet && !twSet) {
372
+ feedback.push({
373
+ text: "OG and Twitter images are missing \u2014 add them for full social coverage.",
374
+ color: "orange"
375
+ });
376
+ } else if (!ogSet) {
377
+ feedback.push({
378
+ text: "OG image is missing \u2014 add it for better Facebook/LinkedIn previews.",
379
+ color: "orange"
380
+ });
381
+ } else if (twSet) {
382
+ feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
383
+ } else {
384
+ feedback.push({
385
+ text: "Twitter image is missing \u2014 add it for better X (Twitter) cards.",
386
+ color: "orange"
387
+ });
388
+ }
389
+ return feedback;
390
+ };
391
+ getOgImageValidation = (hasImage, altText, seoParent) => {
392
+ const feedback = [];
393
+ if (!hasImage) {
394
+ feedback.push({
395
+ text: "No OG image provided. Social shares will lack a visual preview.",
396
+ color: "red"
397
+ });
398
+ return feedback;
399
+ }
400
+ feedback.push({ text: "OG image is set \u2014 good for social sharing.", color: "green" });
401
+ if (altText == null ? void 0 : altText.trim()) {
402
+ feedback.push({ text: "Alt text is set \u2014 good for accessibility.", color: "green" });
403
+ } else {
404
+ feedback.push({ text: "Consider adding alt text for better accessibility.", color: "orange" });
405
+ }
406
+ const metaSet = isMetaImageSet(seoParent);
407
+ const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
408
+ if (metaSet && twSet) {
409
+ feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
410
+ } else {
411
+ if (!metaSet)
412
+ feedback.push({
413
+ text: "Meta image is missing \u2014 add it for search engine results.",
414
+ color: "orange"
415
+ });
416
+ if (!twSet)
417
+ feedback.push({
418
+ text: "Twitter image is missing \u2014 add it for X (Twitter) cards.",
419
+ color: "orange"
420
+ });
421
+ }
422
+ return feedback;
423
+ };
424
+ getOgImageUrlValidation = (imageUrl, seoParent) => {
425
+ const feedback = [];
426
+ if (!(imageUrl == null ? void 0 : imageUrl.trim())) {
427
+ feedback.push({
428
+ text: "No OG image URL provided. Social shares will lack a visual preview.",
429
+ color: "red"
430
+ });
431
+ return feedback;
432
+ }
433
+ feedback.push({ text: "OG image URL is set \u2014 good for social sharing.", color: "green" });
434
+ const metaSet = isMetaImageSet(seoParent);
435
+ const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
436
+ if (metaSet && twSet) {
437
+ feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
438
+ } else {
439
+ if (!metaSet)
440
+ feedback.push({
441
+ text: "Meta image is missing \u2014 add it for search engine results.",
442
+ color: "orange"
443
+ });
444
+ if (!twSet)
445
+ feedback.push({
446
+ text: "Twitter image is missing \u2014 add it for X (Twitter) cards.",
447
+ color: "orange"
448
+ });
449
+ }
450
+ return feedback;
451
+ };
452
+ getTwitterImageValidation = (hasImage, altText, seoParent) => {
453
+ const feedback = [];
454
+ if (!hasImage) {
455
+ feedback.push({
456
+ text: "No Twitter image provided. Posts on X will lack a visual.",
457
+ color: "red"
458
+ });
459
+ return feedback;
460
+ }
461
+ feedback.push({ text: "Twitter image is set \u2014 good for X sharing.", color: "green" });
462
+ if (altText == null ? void 0 : altText.trim()) {
463
+ feedback.push({ text: "Alt text is set \u2014 good for accessibility.", color: "green" });
464
+ } else {
465
+ feedback.push({ text: "Consider adding alt text for better accessibility.", color: "orange" });
466
+ }
467
+ const metaSet = isMetaImageSet(seoParent);
468
+ const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
469
+ if (metaSet && ogSet) {
470
+ feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
471
+ } else {
472
+ if (!metaSet)
473
+ feedback.push({
474
+ text: "Meta image is missing \u2014 add it for search engine results.",
475
+ color: "orange"
476
+ });
477
+ if (!ogSet)
478
+ feedback.push({
479
+ text: "OG image is missing \u2014 add it for Facebook/LinkedIn sharing.",
480
+ color: "orange"
481
+ });
482
+ }
483
+ return feedback;
484
+ };
485
+ getTwitterImageUrlValidation = (imageUrl, seoParent) => {
486
+ const feedback = [];
487
+ if (!(imageUrl == null ? void 0 : imageUrl.trim())) {
488
+ feedback.push({
489
+ text: "No Twitter image URL provided. Posts on X will lack a visual.",
490
+ color: "red"
491
+ });
492
+ return feedback;
493
+ }
494
+ feedback.push({ text: "Twitter image URL is set \u2014 good for X sharing.", color: "green" });
495
+ const metaSet = isMetaImageSet(seoParent);
496
+ const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
497
+ if (metaSet && ogSet) {
498
+ feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
499
+ } else {
500
+ if (!metaSet)
501
+ feedback.push({
502
+ text: "Meta image is missing \u2014 add it for search engine results.",
503
+ color: "orange"
504
+ });
505
+ if (!ogSet)
506
+ feedback.push({
507
+ text: "OG image is missing \u2014 add it for Facebook/LinkedIn sharing.",
508
+ color: "orange"
509
+ });
510
+ }
511
+ return feedback;
512
+ };
513
+ }
514
+ });
515
+
516
+ // src/components/SeoPreview.tsx
517
+ var SeoPreview_exports = {};
518
+ __export(SeoPreview_exports, {
519
+ default: () => SeoPreview_default
520
+ });
521
+ import { Box } from "@sanity/ui";
522
+ import { useFormValue as useFormValue12 } from "sanity";
523
+ import styled from "styled-components";
524
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
525
+ var PreviewContainer, PreviewHeader, PreviewBody, SerpUrl, SerpTitle, SerpDescription, LiveIndicator, SeoPreview, SeoPreview_default;
526
+ var init_SeoPreview = __esm({
527
+ "src/components/SeoPreview.tsx"() {
528
+ "use strict";
529
+ init_seoUtils();
530
+ PreviewContainer = styled.div`
531
+ max-width: 600px;
532
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
533
+ background: #ffffff;
534
+ border: 1px solid #dadce0;
535
+ border-radius: 8px;
536
+ overflow: hidden;
537
+ `;
538
+ PreviewHeader = styled.div`
539
+ background: #f8f9fa;
540
+ padding: 12px 16px;
541
+ border-bottom: 1px solid #dadce0;
542
+ display: flex;
543
+ align-items: center;
544
+ justify-content: space-between;
545
+ gap: 8px;
546
+ `;
547
+ PreviewBody = styled.div`
548
+ padding: 16px;
549
+ `;
550
+ SerpUrl = styled.p`
551
+ margin: 0 0 4px;
552
+ color: #006621;
553
+ font-size: 13px;
554
+ line-height: 1.4;
555
+ word-break: break-word;
556
+ `;
557
+ SerpTitle = styled.h3`
558
+ margin: 0 0 8px;
559
+ color: #1a0dab;
560
+ font-size: 18px;
561
+ font-weight: 500;
562
+ line-height: 1.4;
563
+ word-break: break-word;
564
+
565
+ &:hover {
566
+ text-decoration: underline;
567
+ }
568
+ `;
569
+ SerpDescription = styled.p`
570
+ margin: 0;
571
+ color: #545454;
572
+ font-size: 14px;
573
+ line-height: 1.6;
574
+ word-break: break-word;
575
+ display: -webkit-box;
576
+ -webkit-line-clamp: 2;
577
+ -webkit-box-orient: vertical;
578
+ overflow: hidden;
579
+ `;
580
+ LiveIndicator = styled.span`
581
+ display: inline-flex;
582
+ align-items: center;
583
+ gap: 4px;
584
+ font-size: 11px;
585
+ font-weight: 600;
586
+ text-transform: uppercase;
587
+ letter-spacing: 0.05em;
588
+ color: #4f46e5;
589
+ background: #f0f4ff;
590
+ padding: 4px 8px;
591
+ border-radius: 4px;
592
+ `;
593
+ SeoPreview = (props) => {
594
+ var _a, _b;
595
+ const { path, schemaType } = props;
596
+ const { options } = schemaType;
597
+ const baseUrl = (options == null ? void 0 : options.baseUrl) || "https://www.example.com";
598
+ const prefixFunction = options == null ? void 0 : options.prefix;
599
+ const parent = useFormValue12([path[0]]) || {
600
+ title: "",
601
+ description: "",
602
+ canonicalUrl: ""
603
+ };
604
+ const rootDoc = useFormValue12([]) || {
605
+ slug: { current: "" }
606
+ };
607
+ const slug = ((_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current) || "";
608
+ const {
609
+ title,
610
+ description,
611
+ canonicalUrl: url
612
+ } = parent;
613
+ const base = (_b = url || baseUrl) == null ? void 0 : _b.replace(/\/+$/, "");
614
+ const slugStr = String(slug || "").replace(/^\/+/, "");
615
+ const pref = String(
616
+ prefixFunction ? prefixFunction(rootDoc) : ""
617
+ ).replace(/^\/+|\/+$/g, "");
618
+ const urlPath = [pref, slugStr].filter(Boolean).join("/");
619
+ const finalUrl = urlPath ? `${base}/${urlPath}` : base;
620
+ const domain = (() => {
621
+ try {
622
+ const u = new URL(finalUrl || base);
623
+ return u.hostname;
624
+ } catch (e) {
625
+ return "example.com";
626
+ }
627
+ })();
628
+ const urlDisplay = `${domain}${urlPath ? ` \u203A ${urlPath.split("/").slice(-1)[0]}` : ""}`;
629
+ return /* @__PURE__ */ jsx12(Box, { padding: 3, children: /* @__PURE__ */ jsxs12(PreviewContainer, { children: [
630
+ /* @__PURE__ */ jsxs12(PreviewHeader, { children: [
631
+ /* @__PURE__ */ jsx12(
632
+ "span",
633
+ {
634
+ style: {
635
+ fontSize: "11px",
636
+ color: "#5f6368",
637
+ textTransform: "uppercase",
638
+ letterSpacing: "0.05em"
639
+ },
640
+ children: "Search Preview"
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsxs12(LiveIndicator, { children: [
644
+ /* @__PURE__ */ jsx12(
645
+ "span",
646
+ {
647
+ style: {
648
+ width: "4px",
649
+ height: "4px",
650
+ borderRadius: "50%",
651
+ backgroundColor: "#4f46e5",
652
+ display: "inline-block"
653
+ }
654
+ }
655
+ ),
656
+ "Live"
657
+ ] })
658
+ ] }),
659
+ /* @__PURE__ */ jsxs12(PreviewBody, { children: [
660
+ /* @__PURE__ */ jsx12(SerpUrl, { children: finalUrl ? urlDisplay : "example.com \u203A page-url" }),
661
+ /* @__PURE__ */ jsx12(SerpTitle, { children: title && title.length > 0 ? truncate(title, 60) : "Your SEO Title will appear here" }),
662
+ /* @__PURE__ */ jsx12(SerpDescription, { children: description && description.length > 0 ? truncate(description, 160) : "Your meta description will show up here. Make it compelling!" })
663
+ ] })
664
+ ] }) });
665
+ };
666
+ SeoPreview_default = SeoPreview;
667
+ }
668
+ });
36
669
 
37
670
  // src/components/SeoHealthDashboard.tsx
38
- import { useCallback, useEffect, useMemo, useState } from "react";
671
+ var SeoHealthDashboard_exports = {};
672
+ __export(SeoHealthDashboard_exports, {
673
+ default: () => SeoHealthDashboard_default
674
+ });
675
+ import { useCallback, useEffect, useMemo as useMemo12, useState } from "react";
39
676
  import { useClient, useWorkspace } from "sanity";
40
677
  import { useIntentLink } from "sanity/router";
41
678
  import { usePaneRouter } from "sanity/structure";
42
- import styled, { keyframes } from "styled-components";
43
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
44
- var DashboardContainer = styled.div`
679
+ import styled2, { css, keyframes } from "styled-components";
680
+ import { Fragment, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
681
+ var DashboardContainer, PageHeader, PageTitle, PreviewBadge, PageSubtitle, StatsGrid, StatCard, StatLabel, StatValue, ControlsBar, SearchWrapper, SearchIconSvg, SearchInput, StyledSelect, TableCard, TableHeader, TableRow, ColTitle, TitleWrapper, TitleCell, ColType, ColScore, ColIssues, DocTitleLink, DocId, TypeBadge, TypeText, CustomBadge, ScoreBadge, IssueTag, NonStringTitleWarning, MoreIssues, MoreIssuesWrapper, IssuesPopover, PopoverIssueItem, UpgradeContainer, UpgradeBox, UpgradeLock, UpgradeTitle, UpgradeText, UpgradeCode, UpgradeButton, ReloadButton, spin, DashboardRefreshButton, DocTitleAnchor, PaneLinkWrapper, DocTitleAnchorPane, DocBadgeRenderer, Spinner, LoadingState, EmptyState, DeprecationBanner, DeprecationBannerLink, TYPE_COLOR_PALETTE, getTypeColor, getStatusCategory, scoreMetaTitle, scoreMetaDescription, scoreOpenGraph, scoreTwitterCard, calculateHealthScore, resolveTypeLabel, buildTitleProjection, generateDummyData, SeoHealthDashboard, SeoHealthDashboard_default;
682
+ var init_SeoHealthDashboard = __esm({
683
+ "src/components/SeoHealthDashboard.tsx"() {
684
+ "use strict";
685
+ DashboardContainer = styled2.div`
45
686
  width: 100%;
46
687
  min-height: 100%;
47
688
  background: #f0f2f5;
@@ -49,10 +690,14 @@ var DashboardContainer = styled.div`
49
690
  box-sizing: border-box;
50
691
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
51
692
  `;
52
- var PageHeader = styled.div`
693
+ PageHeader = styled2.div`
694
+ display: flex;
695
+ align-items: flex-start;
696
+ justify-content: space-between;
697
+ gap: 12px;
53
698
  margin-bottom: 28px;
54
699
  `;
55
- var PageTitle = styled.h1`
700
+ PageTitle = styled2.h1`
56
701
  margin: 0 0 6px 0;
57
702
  font-size: 22px;
58
703
  font-weight: 700;
@@ -62,7 +707,7 @@ var PageTitle = styled.h1`
62
707
  align-items: center;
63
708
  gap: 10px;
64
709
  `;
65
- var PreviewBadge = styled.span`
710
+ PreviewBadge = styled2.span`
66
711
  display: inline-block;
67
712
  background: #fef3c7;
68
713
  color: #92400e;
@@ -74,18 +719,18 @@ var PreviewBadge = styled.span`
74
719
  letter-spacing: 0.5px;
75
720
  margin-left: 8px;
76
721
  `;
77
- var PageSubtitle = styled.p`
722
+ PageSubtitle = styled2.p`
78
723
  margin: 0;
79
724
  font-size: 13px;
80
725
  color: #6b7280;
81
726
  `;
82
- var StatsGrid = styled.div`
727
+ StatsGrid = styled2.div`
83
728
  display: grid;
84
729
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
85
730
  gap: 14px;
86
731
  margin-bottom: 20px;
87
732
  `;
88
- var StatCard = styled.div`
733
+ StatCard = styled2.div`
89
734
  background: #ffffff;
90
735
  border-radius: 10px;
91
736
  padding: 16px 18px;
@@ -99,7 +744,7 @@ var StatCard = styled.div`
99
744
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
100
745
  }
101
746
  `;
102
- var StatLabel = styled.div`
747
+ StatLabel = styled2.div`
103
748
  font-size: 11px;
104
749
  font-weight: 500;
105
750
  color: #9ca3af;
@@ -107,13 +752,13 @@ var StatLabel = styled.div`
107
752
  letter-spacing: 0.5px;
108
753
  margin-bottom: 8px;
109
754
  `;
110
- var StatValue = styled.div`
755
+ StatValue = styled2.div`
111
756
  font-size: 26px;
112
757
  font-weight: 700;
113
758
  color: #111827;
114
759
  line-height: 1;
115
760
  `;
116
- var ControlsBar = styled.div`
761
+ ControlsBar = styled2.div`
117
762
  background: #ffffff;
118
763
  border-radius: 10px;
119
764
  padding: 14px 18px;
@@ -124,12 +769,12 @@ var ControlsBar = styled.div`
124
769
  margin-bottom: 20px;
125
770
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
126
771
  `;
127
- var SearchWrapper = styled.div`
772
+ SearchWrapper = styled2.div`
128
773
  position: relative;
129
774
  flex: 1;
130
775
  min-width: 220px;
131
776
  `;
132
- var SearchIconSvg = styled.span`
777
+ SearchIconSvg = styled2.span`
133
778
  position: absolute;
134
779
  left: 11px;
135
780
  top: 50%;
@@ -139,7 +784,7 @@ var SearchIconSvg = styled.span`
139
784
  align-items: center;
140
785
  pointer-events: none;
141
786
  `;
142
- var SearchInput = styled.input`
787
+ SearchInput = styled2.input`
143
788
  width: 100%;
144
789
  height: 36px;
145
790
  padding: 0 12px 0 34px;
@@ -164,7 +809,7 @@ var SearchInput = styled.input`
164
809
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
165
810
  }
166
811
  `;
167
- var StyledSelect = styled.select`
812
+ StyledSelect = styled2.select`
168
813
  height: 36px;
169
814
  padding: 0 32px 0 12px;
170
815
  border: 1px solid #e5e7eb;
@@ -185,13 +830,13 @@ var StyledSelect = styled.select`
185
830
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
186
831
  }
187
832
  `;
188
- var TableCard = styled.div`
833
+ TableCard = styled2.div`
189
834
  background: #ffffff;
190
835
  border-radius: 10px;
191
836
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
192
837
  overflow: hidden;
193
838
  `;
194
- var TableHeader = styled.div`
839
+ TableHeader = styled2.div`
195
840
  display: flex;
196
841
  align-items: center;
197
842
  padding: 11px 20px;
@@ -204,7 +849,7 @@ var TableHeader = styled.div`
204
849
  letter-spacing: 0.5px;
205
850
  gap: 12px;
206
851
  `;
207
- var TableRow = styled.div`
852
+ TableRow = styled2.div`
208
853
  display: flex;
209
854
  align-items: center;
210
855
  padding: 13px 20px;
@@ -220,35 +865,35 @@ var TableRow = styled.div`
220
865
  background: #fafafa;
221
866
  }
222
867
  `;
223
- var ColTitle = styled.div`
868
+ ColTitle = styled2.div`
224
869
  flex: 2;
225
870
  min-width: 0;
226
871
  `;
227
- var TitleWrapper = styled.div`
872
+ TitleWrapper = styled2.div`
228
873
  display: flex;
229
874
  align-items: center;
230
875
  gap: 4px;
231
876
  flex-wrap: wrap;
232
877
  min-width: 0;
233
878
  `;
234
- var TitleCell = styled.div`
879
+ TitleCell = styled2.div`
235
880
  min-width: 0;
236
881
  overflow: hidden;
237
882
  flex: 1;
238
883
  `;
239
- var ColType = styled.div`
884
+ ColType = styled2.div`
240
885
  flex: 0.8;
241
886
  min-width: 80px;
242
887
  `;
243
- var ColScore = styled.div`
888
+ ColScore = styled2.div`
244
889
  flex: 0.6;
245
890
  min-width: 70px;
246
891
  `;
247
- var ColIssues = styled.div`
892
+ ColIssues = styled2.div`
248
893
  flex: 2;
249
894
  min-width: 0;
250
895
  `;
251
- var DocTitleLink = styled.a`
896
+ DocTitleLink = styled2.a`
252
897
  font-size: 13px;
253
898
  font-weight: 600;
254
899
  color: #4f46e5;
@@ -264,7 +909,7 @@ var DocTitleLink = styled.a`
264
909
  text-decoration: underline;
265
910
  }
266
911
  `;
267
- var DocId = styled.div`
912
+ DocId = styled2.div`
268
913
  font-size: 11px;
269
914
  color: #9ca3af;
270
915
  margin-top: 2px;
@@ -272,7 +917,7 @@ var DocId = styled.div`
272
917
  overflow: hidden;
273
918
  text-overflow: ellipsis;
274
919
  `;
275
- var TypeBadge = styled.span`
920
+ TypeBadge = styled2.span`
276
921
  display: inline-block;
277
922
  padding: 3px 8px;
278
923
  border-radius: 5px;
@@ -281,12 +926,12 @@ var TypeBadge = styled.span`
281
926
  background: ${(p) => p.$bgColor || "#ede9fe"};
282
927
  color: ${(p) => p.$textColor || "#5b21b6"};
283
928
  `;
284
- var TypeText = styled.span`
929
+ TypeText = styled2.span`
285
930
  font-size: 12px;
286
931
  font-weight: 500;
287
932
  color: #374151;
288
933
  `;
289
- var CustomBadge = styled.span`
934
+ CustomBadge = styled2.span`
290
935
  display: inline-block;
291
936
  padding: 2px 6px;
292
937
  border-radius: 4px;
@@ -296,26 +941,26 @@ var CustomBadge = styled.span`
296
941
  color: ${(p) => p.$textColor || "#3730a3"};
297
942
  white-space: nowrap;
298
943
  `;
299
- var ScoreBadge = styled.span`
944
+ ScoreBadge = styled2.span`
300
945
  display: inline-block;
301
946
  padding: 4px 10px;
302
947
  border-radius: 6px;
303
948
  font-size: 12px;
304
949
  font-weight: 700;
305
950
  background: ${(p) => {
306
- if (p.$score >= 80) return "#d1fae5";
307
- if (p.$score >= 60) return "#fef3c7";
308
- if (p.$score >= 40) return "#ffedd5";
309
- return "#fee2e2";
310
- }};
951
+ if (p.$score >= 80) return "#d1fae5";
952
+ if (p.$score >= 60) return "#fef3c7";
953
+ if (p.$score >= 40) return "#ffedd5";
954
+ return "#fee2e2";
955
+ }};
311
956
  color: ${(p) => {
312
- if (p.$score >= 80) return "#065f46";
313
- if (p.$score >= 60) return "#92400e";
314
- if (p.$score >= 40) return "#9a3412";
315
- return "#991b1b";
316
- }};
957
+ if (p.$score >= 80) return "#065f46";
958
+ if (p.$score >= 60) return "#92400e";
959
+ if (p.$score >= 40) return "#9a3412";
960
+ return "#991b1b";
961
+ }};
317
962
  `;
318
- var IssueTag = styled.div`
963
+ IssueTag = styled2.div`
319
964
  font-size: 11px;
320
965
  color: #ef4444;
321
966
  line-height: 1.5;
@@ -323,7 +968,23 @@ var IssueTag = styled.div`
323
968
  overflow: hidden;
324
969
  text-overflow: ellipsis;
325
970
  `;
326
- var MoreIssues = styled.div`
971
+ NonStringTitleWarning = styled2.div`
972
+ display: inline-flex;
973
+ align-items: center;
974
+ gap: 4px;
975
+ margin-top: 4px;
976
+ padding: 2px 7px;
977
+ border-radius: 4px;
978
+ background: #fef3c7;
979
+ border: 1px solid #fcd34d;
980
+ font-size: 10px;
981
+ font-weight: 600;
982
+ color: #92400e;
983
+ line-height: 1.4;
984
+ cursor: default;
985
+ white-space: normal;
986
+ `;
987
+ MoreIssues = styled2.div`
327
988
  font-size: 11px;
328
989
  color: #6b7280;
329
990
  cursor: pointer;
@@ -333,11 +994,11 @@ var MoreIssues = styled.div`
333
994
  color: #374151;
334
995
  }
335
996
  `;
336
- var MoreIssuesWrapper = styled.div`
997
+ MoreIssuesWrapper = styled2.div`
337
998
  position: relative;
338
999
  display: inline-block;
339
1000
  `;
340
- var IssuesPopover = styled.div`
1001
+ IssuesPopover = styled2.div`
341
1002
  position: absolute;
342
1003
  bottom: auto;
343
1004
  left: 0;
@@ -365,7 +1026,7 @@ var IssuesPopover = styled.div`
365
1026
  border-top: 6px solid #1f2937;
366
1027
  }
367
1028
  `;
368
- var PopoverIssueItem = styled.div`
1029
+ PopoverIssueItem = styled2.div`
369
1030
  display: flex;
370
1031
  gap: 6px;
371
1032
  margin-bottom: 6px;
@@ -374,14 +1035,14 @@ var PopoverIssueItem = styled.div`
374
1035
  margin-bottom: 0;
375
1036
  }
376
1037
  `;
377
- var UpgradeContainer = styled.div`
1038
+ UpgradeContainer = styled2.div`
378
1039
  display: flex;
379
1040
  align-items: center;
380
1041
  justify-content: center;
381
1042
  min-height: 100%;
382
1043
  padding: 60px 24px;
383
1044
  `;
384
- var UpgradeBox = styled.div`
1045
+ UpgradeBox = styled2.div`
385
1046
  background: #ffffff;
386
1047
  border-radius: 16px;
387
1048
  padding: 48px 40px;
@@ -393,23 +1054,23 @@ var UpgradeBox = styled.div`
393
1054
  0 1px 4px rgba(0, 0, 0, 0.05);
394
1055
  border: 1px solid #e5e7eb;
395
1056
  `;
396
- var UpgradeLock = styled.div`
1057
+ UpgradeLock = styled2.div`
397
1058
  font-size: 40px;
398
1059
  margin-bottom: 16px;
399
1060
  `;
400
- var UpgradeTitle = styled.h2`
1061
+ UpgradeTitle = styled2.h2`
401
1062
  margin: 0 0 10px;
402
1063
  font-size: 20px;
403
1064
  font-weight: 700;
404
1065
  color: #111827;
405
1066
  `;
406
- var UpgradeText = styled.p`
1067
+ UpgradeText = styled2.p`
407
1068
  margin: 0 0 20px;
408
1069
  font-size: 14px;
409
1070
  color: #6b7280;
410
1071
  line-height: 1.6;
411
1072
  `;
412
- var UpgradeCode = styled.pre`
1073
+ UpgradeCode = styled2.pre`
413
1074
  background: #f3f4f6;
414
1075
  border-radius: 8px;
415
1076
  padding: 14px 16px;
@@ -421,7 +1082,7 @@ var UpgradeCode = styled.pre`
421
1082
  line-height: 1.6;
422
1083
  border: 1px solid #e5e7eb;
423
1084
  `;
424
- var UpgradeButton = styled.a`
1085
+ UpgradeButton = styled2.a`
425
1086
  display: inline-block;
426
1087
  background: #4f46e5;
427
1088
  color: #ffffff;
@@ -436,7 +1097,7 @@ var UpgradeButton = styled.a`
436
1097
  background: #4338ca;
437
1098
  }
438
1099
  `;
439
- var ReloadButton = styled.button`
1100
+ ReloadButton = styled2.button`
440
1101
  display: inline-block;
441
1102
  background: transparent;
442
1103
  color: #6b7280;
@@ -458,17 +1119,57 @@ var ReloadButton = styled.button`
458
1119
  border-color: #9ca3af;
459
1120
  }
460
1121
  `;
461
- var DocTitleAnchor = ({ id, type, structureTool, children }) => {
462
- const { basePath } = useWorkspace();
463
- const { onClick: intentOnClick, href: intentHref } = useIntentLink({
464
- intent: "edit",
465
- params: { id, type }
466
- });
467
- const href = structureTool ? `${basePath}/${structureTool}/intent/edit/id=${id};type=${type}/` : intentHref;
468
- const onClick = structureTool ? void 0 : intentOnClick;
469
- return /* @__PURE__ */ jsx(DocTitleLink, { href, onClick, title: "Open document", children });
470
- };
471
- var PaneLinkWrapper = styled.span`
1122
+ spin = keyframes`
1123
+ to { transform: rotate(360deg); }
1124
+ `;
1125
+ DashboardRefreshButton = styled2.button`
1126
+ display: inline-flex;
1127
+ align-items: center;
1128
+ gap: 6px;
1129
+ background: #ffffff;
1130
+ color: #374151;
1131
+ font-size: 13px;
1132
+ font-weight: 500;
1133
+ padding: 8px 14px;
1134
+ border-radius: 8px;
1135
+ border: 1px solid #d1d5db;
1136
+ cursor: pointer;
1137
+ flex-shrink: 0;
1138
+ transition:
1139
+ background 0.15s,
1140
+ color 0.15s,
1141
+ border-color 0.15s,
1142
+ box-shadow 0.15s;
1143
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1144
+
1145
+ svg {
1146
+ animation: ${(p) => p.$spinning ? css`
1147
+ ${spin} 0.7s linear infinite
1148
+ ` : "none"};
1149
+ }
1150
+
1151
+ &:hover {
1152
+ background: #f3f4f6;
1153
+ border-color: #9ca3af;
1154
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
1155
+ }
1156
+
1157
+ &:disabled {
1158
+ opacity: 0.6;
1159
+ cursor: not-allowed;
1160
+ }
1161
+ `;
1162
+ DocTitleAnchor = ({ id, type, structureTool, children }) => {
1163
+ const { basePath } = useWorkspace();
1164
+ const { onClick: intentOnClick, href: intentHref } = useIntentLink({
1165
+ intent: "edit",
1166
+ params: { id, type }
1167
+ });
1168
+ const href = structureTool ? `${basePath}/${structureTool}/intent/edit/id=${id};type=${type}/` : intentHref;
1169
+ const onClick = structureTool ? void 0 : intentOnClick;
1170
+ return /* @__PURE__ */ jsx13(DocTitleLink, { href, onClick, title: "Open document", children });
1171
+ };
1172
+ PaneLinkWrapper = styled2.span`
472
1173
  display: block;
473
1174
  min-width: 0;
474
1175
  overflow: hidden;
@@ -490,23 +1191,20 @@ var PaneLinkWrapper = styled.span`
490
1191
  }
491
1192
  }
492
1193
  `;
493
- var DocTitleAnchorPane = ({
494
- id,
495
- type,
496
- children
497
- }) => {
498
- const { ChildLink } = usePaneRouter();
499
- return /* @__PURE__ */ jsx(PaneLinkWrapper, { children: /* @__PURE__ */ jsx(ChildLink, { childId: id, childParameters: { type }, children }) });
500
- };
501
- var DocBadgeRenderer = ({ doc, docBadge }) => {
502
- const badge = docBadge(doc);
503
- if (!badge) return null;
504
- return /* @__PURE__ */ jsx(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label });
505
- };
506
- var spin = keyframes`
507
- to { transform: rotate(360deg); }
508
- `;
509
- var Spinner = styled.div`
1194
+ DocTitleAnchorPane = ({
1195
+ id,
1196
+ type,
1197
+ children
1198
+ }) => {
1199
+ const { ChildLink } = usePaneRouter();
1200
+ return /* @__PURE__ */ jsx13(PaneLinkWrapper, { children: /* @__PURE__ */ jsx13(ChildLink, { childId: id, childParameters: { type }, children }) });
1201
+ };
1202
+ DocBadgeRenderer = ({ doc, docBadge }) => {
1203
+ const badge = docBadge(doc);
1204
+ if (!badge) return null;
1205
+ return /* @__PURE__ */ jsx13(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label });
1206
+ };
1207
+ Spinner = styled2.div`
510
1208
  width: 28px;
511
1209
  height: 28px;
512
1210
  border: 3px solid #e5e7eb;
@@ -515,424 +1213,463 @@ var Spinner = styled.div`
515
1213
  animation: ${spin} 0.7s linear infinite;
516
1214
  margin: 0 auto 12px;
517
1215
  `;
518
- var LoadingState = styled.div`
1216
+ LoadingState = styled2.div`
519
1217
  padding: 48px 24px;
520
1218
  text-align: center;
521
1219
  color: #6b7280;
522
1220
  font-size: 13px;
523
1221
  `;
524
- var EmptyState = styled.div`
1222
+ EmptyState = styled2.div`
525
1223
  padding: 48px 24px;
526
1224
  text-align: center;
527
1225
  color: #9ca3af;
528
1226
  font-size: 13px;
529
1227
  `;
530
- var TYPE_COLOR_PALETTE = [
531
- { bg: "#dbeafe", text: "#0c4a6e" },
532
- // Blue
533
- { bg: "#dcfce7", text: "#14532d" },
534
- // Green
535
- { bg: "#fce7f3", text: "#500724" },
536
- // Pink
537
- { bg: "#fed7aa", text: "#7c2d12" },
538
- // Orange
539
- { bg: "#e9d5ff", text: "#581c87" },
540
- // Purple
541
- { bg: "#f3e8ff", text: "#3f0f5c" },
542
- // Deep Purple
543
- { bg: "#ccfbf1", text: "#134e4a" },
544
- // Teal
545
- { bg: "#ddd6fe", text: "#3730a3" },
546
- // Indigo
547
- { bg: "#fca5a5", text: "#7f1d1d" },
548
- // Red
549
- { bg: "#a7f3d0", text: "#065f46" },
550
- // Emerald
551
- { bg: "#fbbf24", text: "#78350f" },
552
- // Amber
553
- { bg: "#c4b5fd", text: "#3b0764" },
554
- // Violet
555
- { bg: "#f0fdf4", text: "#15803d" },
556
- // Light Green
557
- { bg: "#fef2f2", text: "#991b1b" },
558
- // Light Red
559
- { bg: "#f5f3ff", text: "#5b21b6" },
560
- // Light Purple
561
- { bg: "#fffbeb", text: "#92400e" }
562
- // Light Amber
563
- ];
564
- var getTypeColor = (type) => {
565
- let hash = 0;
566
- for (let i = 0; i < type.length; i += 1) {
567
- const char = type.charCodeAt(i);
568
- hash = Math.abs(hash * 31 + char);
569
- }
570
- const colorIndex = hash % TYPE_COLOR_PALETTE.length;
571
- return TYPE_COLOR_PALETTE[colorIndex];
572
- };
573
- var getStatusCategory = (score) => {
574
- if (score >= 80) return "excellent";
575
- if (score >= 60) return "good";
576
- if (score >= 40) return "fair";
577
- if (score > 0) return "poor";
578
- return "missing";
579
- };
580
- var scoreMetaTitle = (title) => {
581
- const issues = [];
582
- let score = 0;
583
- if (title && title.length >= 50 && title.length <= 60) {
584
- score = 15;
585
- } else if (title && title.length > 0) {
586
- score = 10;
587
- if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
588
- if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
589
- } else {
590
- issues.push("Missing meta title");
591
- }
592
- return { score, issues };
593
- };
594
- var scoreMetaDescription = (description) => {
595
- const issues = [];
596
- let score = 0;
597
- if (description && description.length >= 120 && description.length <= 160) {
598
- score = 15;
599
- } else if (description && description.length > 0) {
600
- score = 10;
601
- if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
602
- if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
603
- } else {
604
- issues.push("Missing meta description");
605
- }
606
- return { score, issues };
607
- };
608
- var scoreOpenGraph = (openGraph2) => {
609
- const issues = [];
610
- let score = 0;
611
- if (openGraph2) {
612
- if (openGraph2.title) score += 6;
613
- else issues.push("Missing OG title");
614
- if (openGraph2.description) score += 6;
615
- else issues.push("Missing OG description");
616
- if (openGraph2.image) score += 6;
617
- else issues.push("Missing OG image");
618
- if (openGraph2.type) score += 7;
619
- else issues.push("Missing OG type");
620
- } else {
621
- issues.push("Open Graph not configured");
622
- }
623
- return { score, issues };
624
- };
625
- var scoreTwitterCard = (twitter2) => {
626
- const issues = [];
627
- let score = 0;
628
- if (twitter2) {
629
- if (twitter2.title) score += 5;
630
- else issues.push("Missing Twitter title");
631
- if (twitter2.description) score += 5;
632
- else issues.push("Missing Twitter description");
633
- if (twitter2.image) score += 5;
634
- else issues.push("Missing Twitter image");
635
- } else {
636
- issues.push("Twitter Card not configured");
637
- }
638
- return { score, issues };
639
- };
640
- var calculateHealthScore = (doc) => {
641
- if (!doc.seo) {
642
- return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
643
- }
644
- const { title, description, keywords, robots, canonicalUrl, openGraph: openGraph2, twitter: twitter2 } = doc.seo;
645
- let score = 0;
646
- const issues = [];
647
- const titleResult = scoreMetaTitle(title);
648
- score += titleResult.score;
649
- issues.push(...titleResult.issues);
650
- const descResult = scoreMetaDescription(description);
651
- score += descResult.score;
652
- issues.push(...descResult.issues);
653
- if (doc.seo.metaImage) score += 10;
654
- else issues.push("Missing meta image");
655
- if (keywords && keywords.length > 0) score += 10;
656
- else issues.push("No keywords defined");
657
- if (robots && !robots.noIndex) score += 5;
658
- else if (!robots) score += 5;
659
- if (canonicalUrl) score += 0;
660
- const ogResult = scoreOpenGraph(openGraph2);
661
- score += ogResult.score;
662
- issues.push(...ogResult.issues);
663
- const twResult = scoreTwitterCard(twitter2);
664
- score += twResult.score;
665
- issues.push(...twResult.issues);
666
- const hasMetaImage = !!doc.seo.metaImage;
667
- const hasOgImage = !!(openGraph2 && openGraph2.image);
668
- const hasTwitterImage = !!(twitter2 && twitter2.image);
669
- if (hasMetaImage && hasOgImage && hasTwitterImage) {
670
- score += 5;
671
- } else {
672
- const missingImages = [];
673
- if (!hasMetaImage) missingImages.push("meta image");
674
- if (!hasOgImage) missingImages.push("OG image");
675
- if (!hasTwitterImage) missingImages.push("Twitter image");
676
- issues.push(`Missing images for full score: ${missingImages.join(", ")}`);
1228
+ DeprecationBanner = styled2.div`
1229
+ background: #fffbeb;
1230
+ border: 1px solid #fcd34d;
1231
+ border-radius: 8px;
1232
+ padding: 10px 14px;
1233
+ font-size: 12px;
1234
+ color: #78350f;
1235
+ margin-bottom: 16px;
1236
+ line-height: 1.6;
1237
+ `;
1238
+ DeprecationBannerLink = styled2.a`
1239
+ color: #92400e;
1240
+ font-weight: 600;
1241
+ text-decoration: underline;
1242
+ &:hover {
1243
+ color: #78350f;
677
1244
  }
678
- const status = getStatusCategory(score);
679
- return { score, status, issues };
680
- };
681
- var resolveTypeLabel = (type, typeLabels) => {
682
- var _a;
683
- return (_a = typeLabels == null ? void 0 : typeLabels[type]) != null ? _a : type;
684
- };
685
- var buildTitleProjection = (titleField) => {
686
- if (!titleField || titleField === "title") return "title";
687
- if (typeof titleField === "string") return `"title": ${titleField}`;
688
- const cases = Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ");
689
- return `"title": select(${cases}, title)`;
690
- };
691
- var generateDummyData = () => {
692
- const dummyDocs = [
693
- {
694
- _id: "preview-post-1",
695
- _type: "post",
696
- title: "Getting Started with SEO Best Practices",
697
- slug: { current: "getting-started-seo" },
698
- _updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString(),
699
- seo: {
700
- title: "Getting Started with SEO Best Practices | My Blog",
701
- description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
702
- keywords: ["seo", "best practices", "optimization"],
703
- metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
704
- openGraph: {
705
- title: "SEO Best Practices Guide",
706
- description: "Master SEO optimization",
707
- image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
708
- type: "article"
709
- },
710
- twitter: {
711
- title: "SEO Best Practices",
712
- description: "Learn SEO optimization",
713
- image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
714
- card: "summary_large_image"
715
- }
1245
+ `;
1246
+ TYPE_COLOR_PALETTE = [
1247
+ { bg: "#dbeafe", text: "#0c4a6e" },
1248
+ // Blue
1249
+ { bg: "#dcfce7", text: "#14532d" },
1250
+ // Green
1251
+ { bg: "#fce7f3", text: "#500724" },
1252
+ // Pink
1253
+ { bg: "#fed7aa", text: "#7c2d12" },
1254
+ // Orange
1255
+ { bg: "#e9d5ff", text: "#581c87" },
1256
+ // Purple
1257
+ { bg: "#f3e8ff", text: "#3f0f5c" },
1258
+ // Deep Purple
1259
+ { bg: "#ccfbf1", text: "#134e4a" },
1260
+ // Teal
1261
+ { bg: "#ddd6fe", text: "#3730a3" },
1262
+ // Indigo
1263
+ { bg: "#fca5a5", text: "#7f1d1d" },
1264
+ // Red
1265
+ { bg: "#a7f3d0", text: "#065f46" },
1266
+ // Emerald
1267
+ { bg: "#fbbf24", text: "#78350f" },
1268
+ // Amber
1269
+ { bg: "#c4b5fd", text: "#3b0764" },
1270
+ // Violet
1271
+ { bg: "#f0fdf4", text: "#15803d" },
1272
+ // Light Green
1273
+ { bg: "#fef2f2", text: "#991b1b" },
1274
+ // Light Red
1275
+ { bg: "#f5f3ff", text: "#5b21b6" },
1276
+ // Light Purple
1277
+ { bg: "#fffbeb", text: "#92400e" }
1278
+ // Light Amber
1279
+ ];
1280
+ getTypeColor = (type) => {
1281
+ let hash = 0;
1282
+ for (let i = 0; i < type.length; i += 1) {
1283
+ const char = type.charCodeAt(i);
1284
+ hash = Math.abs(hash * 31 + char);
716
1285
  }
717
- },
718
- {
719
- _id: "preview-post-2",
720
- _type: "post",
721
- title: "Advanced Analytics Strategy",
722
- slug: { current: "advanced-analytics" },
723
- _updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1e3).toISOString(),
724
- seo: {
725
- title: "Advanced Analytics",
726
- description: "Strategy tips",
727
- keywords: ["analytics", "data"],
728
- openGraph: {
729
- title: "Analytics Guide"
730
- }
1286
+ const colorIndex = hash % TYPE_COLOR_PALETTE.length;
1287
+ return TYPE_COLOR_PALETTE[colorIndex];
1288
+ };
1289
+ getStatusCategory = (score) => {
1290
+ if (score >= 80) return "excellent";
1291
+ if (score >= 60) return "good";
1292
+ if (score >= 40) return "fair";
1293
+ if (score > 0) return "poor";
1294
+ return "missing";
1295
+ };
1296
+ scoreMetaTitle = (title) => {
1297
+ const issues = [];
1298
+ let score = 0;
1299
+ if (title && title.length >= 50 && title.length <= 60) {
1300
+ score = 15;
1301
+ } else if (title && title.length > 0) {
1302
+ score = 10;
1303
+ if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
1304
+ if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
1305
+ } else {
1306
+ issues.push("Missing meta title");
731
1307
  }
732
- },
733
- {
734
- _id: "preview-page-1",
735
- _type: "page",
736
- title: "About Us",
737
- slug: { current: "about" },
738
- _updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1e3).toISOString(),
739
- seo: {
740
- title: "About",
741
- keywords: ["company", "team"],
742
- metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
1308
+ return { score, issues };
1309
+ };
1310
+ scoreMetaDescription = (description) => {
1311
+ const issues = [];
1312
+ let score = 0;
1313
+ if (description && description.length >= 120 && description.length <= 160) {
1314
+ score = 15;
1315
+ } else if (description && description.length > 0) {
1316
+ score = 10;
1317
+ if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
1318
+ if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
1319
+ } else {
1320
+ issues.push("Missing meta description");
743
1321
  }
744
- },
745
- {
746
- _id: "preview-post-3",
747
- _type: "post",
748
- title: "Content Marketing Trends for 2024",
749
- slug: { current: "content-marketing-trends" },
750
- _updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1e3).toISOString(),
751
- seo: {
752
- title: "Content Marketing Trends 2024",
753
- description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
754
- keywords: ["content marketing", "trends", "strategy", "engagement"],
755
- metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
756
- openGraph: {
757
- title: "Content Marketing Trends 2024",
758
- description: "Latest trends in content marketing",
759
- image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
760
- type: "article"
761
- },
762
- twitter: {
763
- title: "Content Marketing Trends",
764
- description: "Discover the latest trends",
765
- card: "summary"
766
- }
1322
+ return { score, issues };
1323
+ };
1324
+ scoreOpenGraph = (openGraph2) => {
1325
+ const issues = [];
1326
+ let score = 0;
1327
+ if (openGraph2) {
1328
+ if (openGraph2.title) score += 6;
1329
+ else issues.push("Missing OG title");
1330
+ if (openGraph2.description) score += 6;
1331
+ else issues.push("Missing OG description");
1332
+ if (openGraph2.image) score += 6;
1333
+ else issues.push("Missing OG image");
1334
+ if (openGraph2.type) score += 7;
1335
+ else issues.push("Missing OG type");
1336
+ } else {
1337
+ issues.push("Open Graph not configured");
767
1338
  }
768
- },
769
- {
770
- _id: "preview-post-4",
771
- _type: "product",
772
- title: "Pro Plan",
773
- slug: { current: "pro-plan" },
774
- _updatedAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1e3).toISOString(),
775
- seo: {
776
- title: "Pro",
777
- keywords: ["pricing"]
1339
+ return { score, issues };
1340
+ };
1341
+ scoreTwitterCard = (twitter2) => {
1342
+ const issues = [];
1343
+ let score = 0;
1344
+ if (twitter2) {
1345
+ if (twitter2.title) score += 5;
1346
+ else issues.push("Missing Twitter title");
1347
+ if (twitter2.description) score += 5;
1348
+ else issues.push("Missing Twitter description");
1349
+ if (twitter2.image) score += 5;
1350
+ else issues.push("Missing Twitter image");
1351
+ } else {
1352
+ issues.push("Twitter Card not configured");
778
1353
  }
779
- },
780
- {
781
- _id: "preview-page-2",
782
- _type: "page",
783
- title: "Contact",
784
- slug: { current: "contact" },
785
- _updatedAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1e3).toISOString(),
786
- seo: {
787
- openGraph: {
788
- title: "Get in Touch"
789
- }
1354
+ return { score, issues };
1355
+ };
1356
+ calculateHealthScore = (doc) => {
1357
+ if (!doc.seo) {
1358
+ return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
790
1359
  }
791
- },
792
- {
793
- _id: "preview-post-5",
794
- _type: "post",
795
- title: "Mobile Optimization Guide",
796
- slug: { current: "mobile-optimization" },
797
- _updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1e3).toISOString(),
798
- seo: {
799
- title: "Mobile Optimization Guide: Best Practices for Responsive Design",
800
- description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
801
- keywords: ["mobile", "optimization", "responsive", "performance"],
802
- metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
803
- openGraph: {
804
- title: "Mobile Optimization Best Practices",
805
- description: "Master mobile web optimization",
806
- image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
807
- type: "article"
808
- },
809
- twitter: {
810
- title: "Mobile Optimization Tips",
811
- description: "Responsive design best practices",
812
- image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
813
- card: "summary_large_image"
814
- }
1360
+ const { title, description, keywords, robots, canonicalUrl, openGraph: openGraph2, twitter: twitter2 } = doc.seo;
1361
+ let score = 0;
1362
+ const issues = [];
1363
+ const titleResult = scoreMetaTitle(title);
1364
+ score += titleResult.score;
1365
+ issues.push(...titleResult.issues);
1366
+ const descResult = scoreMetaDescription(description);
1367
+ score += descResult.score;
1368
+ issues.push(...descResult.issues);
1369
+ if (doc.seo.metaImage) score += 10;
1370
+ else issues.push("Missing meta image");
1371
+ if (keywords && keywords.length > 0) score += 10;
1372
+ else issues.push("No keywords defined");
1373
+ if (robots && !robots.noIndex) score += 5;
1374
+ else if (!robots) score += 5;
1375
+ if (canonicalUrl) score += 0;
1376
+ const ogResult = scoreOpenGraph(openGraph2);
1377
+ score += ogResult.score;
1378
+ issues.push(...ogResult.issues);
1379
+ const twResult = scoreTwitterCard(twitter2);
1380
+ score += twResult.score;
1381
+ issues.push(...twResult.issues);
1382
+ const hasMetaImage = !!doc.seo.metaImage;
1383
+ const hasOgImage = !!(openGraph2 && openGraph2.image);
1384
+ const hasTwitterImage = !!(twitter2 && twitter2.image);
1385
+ if (hasMetaImage && hasOgImage && hasTwitterImage) {
1386
+ score += 5;
1387
+ } else {
1388
+ const missingImages = [];
1389
+ if (!hasMetaImage) missingImages.push("meta image");
1390
+ if (!hasOgImage) missingImages.push("OG image");
1391
+ if (!hasTwitterImage) missingImages.push("Twitter image");
1392
+ issues.push(`Missing images for full score: ${missingImages.join(", ")}`);
815
1393
  }
816
- }
817
- ];
818
- return dummyDocs.map((doc) => __spreadProps(__spreadValues({}, doc), {
819
- health: calculateHealthScore(doc)
820
- }));
821
- };
822
- var SeoHealthDashboard = ({
823
- icon = "\u{1F4CA}",
824
- title = "SEO Health Dashboard",
825
- description = "Monitor and optimize SEO fields across all your documents",
826
- showTypeColumn = true,
827
- showDocumentId = true,
828
- queryTypes,
829
- queryRequireSeo = true,
830
- customQuery,
831
- apiVersion = "2023-01-01",
832
- licenseKey,
833
- typeLabels,
834
- typeColumnMode = "badge",
835
- titleField,
836
- docBadge,
837
- loadingLicense,
838
- loadingDocuments,
839
- noDocuments,
840
- previewMode = false,
841
- openInPane = false,
842
- structureTool
843
- }) => {
844
- const client = useClient({ apiVersion });
845
- const [licenseStatus, setLicenseStatus] = useState("loading");
846
- const [documents, setDocuments] = useState([]);
847
- const [loading, setLoading] = useState(true);
848
- const [searchQuery, setSearchQuery] = useState("");
849
- const [filterStatus, setFilterStatus] = useState("all");
850
- const [filterType, setFilterType] = useState("all");
851
- const [sortBy, setSortBy] = useState("score");
852
- const [activePopover, setActivePopover] = useState(null);
853
- const VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
854
- const CACHE_TTL_MS = 60 * 60 * 1e3;
855
- const validateLicense = useCallback(
856
- async (forceRefresh = false) => {
1394
+ const status = getStatusCategory(score);
1395
+ return { score, status, issues };
1396
+ };
1397
+ resolveTypeLabel = (type, typeLabels) => {
857
1398
  var _a;
858
- if (previewMode) {
859
- setLicenseStatus("valid");
860
- return;
861
- }
862
- if (!licenseKey) {
863
- setLicenseStatus("invalid");
864
- return;
865
- }
866
- const projectId = (_a = client.config().projectId) != null ? _a : "";
867
- const cacheKey = `seofields_license_${projectId}`;
868
- if (forceRefresh) {
869
- try {
870
- sessionStorage.removeItem(cacheKey);
871
- } catch (e) {
872
- }
873
- }
874
- if (!forceRefresh) {
875
- try {
876
- const cached = sessionStorage.getItem(cacheKey);
877
- if (cached) {
878
- const { valid, ts } = JSON.parse(cached);
879
- if (Date.now() - ts < CACHE_TTL_MS) {
880
- setLicenseStatus(valid ? "valid" : "invalid");
881
- return;
1399
+ return (_a = typeLabels == null ? void 0 : typeLabels[type]) != null ? _a : type;
1400
+ };
1401
+ buildTitleProjection = (titleField) => {
1402
+ if (!titleField || titleField === "title") return "title";
1403
+ if (typeof titleField === "string") return `"title": ${titleField}`;
1404
+ const cases = Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ");
1405
+ return `"title": select(${cases}, title)`;
1406
+ };
1407
+ generateDummyData = () => {
1408
+ const dummyDocs = [
1409
+ {
1410
+ _id: "preview-post-1",
1411
+ _type: "post",
1412
+ title: "Getting Started with SEO Best Practices",
1413
+ slug: { current: "getting-started-seo" },
1414
+ _updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString(),
1415
+ seo: {
1416
+ title: "Getting Started with SEO Best Practices | My Blog",
1417
+ description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
1418
+ keywords: ["seo", "best practices", "optimization"],
1419
+ metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
1420
+ openGraph: {
1421
+ title: "SEO Best Practices Guide",
1422
+ description: "Master SEO optimization",
1423
+ image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
1424
+ type: "article"
1425
+ },
1426
+ twitter: {
1427
+ title: "SEO Best Practices",
1428
+ description: "Learn SEO optimization",
1429
+ image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
1430
+ card: "summary_large_image"
1431
+ }
1432
+ }
1433
+ },
1434
+ {
1435
+ _id: "preview-post-2",
1436
+ _type: "post",
1437
+ title: "Advanced Analytics Strategy",
1438
+ slug: { current: "advanced-analytics" },
1439
+ _updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1e3).toISOString(),
1440
+ seo: {
1441
+ title: "Advanced Analytics",
1442
+ description: "Strategy tips",
1443
+ keywords: ["analytics", "data"],
1444
+ openGraph: {
1445
+ title: "Analytics Guide"
1446
+ }
1447
+ }
1448
+ },
1449
+ {
1450
+ _id: "preview-page-1",
1451
+ _type: "page",
1452
+ title: "About Us",
1453
+ slug: { current: "about" },
1454
+ _updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1e3).toISOString(),
1455
+ seo: {
1456
+ title: "About",
1457
+ keywords: ["company", "team"],
1458
+ metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
1459
+ }
1460
+ },
1461
+ {
1462
+ _id: "preview-post-3",
1463
+ _type: "post",
1464
+ title: "Content Marketing Trends for 2024",
1465
+ slug: { current: "content-marketing-trends" },
1466
+ _updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1e3).toISOString(),
1467
+ seo: {
1468
+ title: "Content Marketing Trends 2024",
1469
+ description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
1470
+ keywords: ["content marketing", "trends", "strategy", "engagement"],
1471
+ metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
1472
+ openGraph: {
1473
+ title: "Content Marketing Trends 2024",
1474
+ description: "Latest trends in content marketing",
1475
+ image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
1476
+ type: "article"
1477
+ },
1478
+ twitter: {
1479
+ title: "Content Marketing Trends",
1480
+ description: "Discover the latest trends",
1481
+ card: "summary"
1482
+ }
1483
+ }
1484
+ },
1485
+ {
1486
+ _id: "preview-post-4",
1487
+ _type: "product",
1488
+ title: "Pro Plan",
1489
+ slug: { current: "pro-plan" },
1490
+ _updatedAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1e3).toISOString(),
1491
+ seo: {
1492
+ title: "Pro",
1493
+ keywords: ["pricing"]
1494
+ }
1495
+ },
1496
+ {
1497
+ _id: "preview-page-2",
1498
+ _type: "page",
1499
+ title: "Contact",
1500
+ slug: { current: "contact" },
1501
+ _updatedAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1e3).toISOString(),
1502
+ seo: {
1503
+ openGraph: {
1504
+ title: "Get in Touch"
1505
+ }
1506
+ }
1507
+ },
1508
+ {
1509
+ _id: "preview-post-5",
1510
+ _type: "post",
1511
+ title: "Mobile Optimization Guide",
1512
+ slug: { current: "mobile-optimization" },
1513
+ _updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1e3).toISOString(),
1514
+ seo: {
1515
+ title: "Mobile Optimization Guide: Best Practices for Responsive Design",
1516
+ description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
1517
+ keywords: ["mobile", "optimization", "responsive", "performance"],
1518
+ metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
1519
+ openGraph: {
1520
+ title: "Mobile Optimization Best Practices",
1521
+ description: "Master mobile web optimization",
1522
+ image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
1523
+ type: "article"
1524
+ },
1525
+ twitter: {
1526
+ title: "Mobile Optimization Tips",
1527
+ description: "Responsive design best practices",
1528
+ image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
1529
+ card: "summary_large_image"
882
1530
  }
883
1531
  }
884
- } catch (e) {
885
- }
886
- }
887
- setLicenseStatus("loading");
888
- try {
889
- const res = await fetch(VALIDATION_ENDPOINT, {
890
- method: "POST",
891
- headers: { "Content-Type": "application/json" },
892
- body: JSON.stringify({ licenseKey, projectId })
893
- });
894
- const valid = res.ok;
895
- setLicenseStatus(valid ? "valid" : "invalid");
896
- try {
897
- sessionStorage.setItem(cacheKey, JSON.stringify({ valid, ts: Date.now() }));
898
- } catch (e) {
899
1532
  }
900
- } catch (e) {
901
- setLicenseStatus("valid");
902
- }
903
- },
904
- // eslint-disable-next-line react-hooks/exhaustive-deps
905
- [licenseKey, previewMode]
906
- );
907
- useEffect(() => {
908
- validateLicense();
909
- }, [licenseKey, previewMode]);
910
- const handleMouseEnterIssues = (el, issues) => {
911
- if (!el) return;
912
- const rect = el.getBoundingClientRect();
913
- const popoverWidth = 280;
914
- const viewportWidth = window.innerWidth;
915
- let left = rect.left;
916
- if (left + popoverWidth > viewportWidth - 10) left = viewportWidth - popoverWidth - 10;
917
- if (left < 10) left = 10;
918
- setActivePopover({ top: rect.top, left, issues });
919
- };
920
- useEffect(() => {
921
- const fetchDocuments = async () => {
922
- try {
923
- setLoading(true);
924
- if (previewMode) {
925
- setDocuments(generateDummyData());
926
- return;
1533
+ ];
1534
+ return dummyDocs.map((doc) => __spreadProps(__spreadValues({}, doc), {
1535
+ health: calculateHealthScore(doc)
1536
+ }));
1537
+ };
1538
+ SeoHealthDashboard = ({
1539
+ icon = "\u{1F4CA}",
1540
+ title = "SEO Health Dashboard",
1541
+ description = "Monitor and optimize SEO fields across all your documents",
1542
+ showTypeColumn = true,
1543
+ showDocumentId = true,
1544
+ queryTypes,
1545
+ queryRequireSeo = true,
1546
+ customQuery,
1547
+ apiVersion = "2023-01-01",
1548
+ licenseKey,
1549
+ typeDisplayLabels,
1550
+ typeColumnMode = "badge",
1551
+ titleField,
1552
+ getDocumentBadge,
1553
+ loadingLicense,
1554
+ loadingDocuments,
1555
+ noDocuments,
1556
+ previewMode = false,
1557
+ openInPane = false,
1558
+ structureTool,
1559
+ _deprecationWarnings
1560
+ }) => {
1561
+ const resolvedTypeLabels = typeDisplayLabels;
1562
+ const resolvedDocBadge = getDocumentBadge;
1563
+ const allDeprecationWarnings = useMemo12(() => _deprecationWarnings != null ? _deprecationWarnings : [], [_deprecationWarnings]);
1564
+ const deprecationGroups = useMemo12(() => {
1565
+ const groups = /* @__PURE__ */ new Map();
1566
+ for (const w of allDeprecationWarnings) {
1567
+ if (!groups.has(w.version)) {
1568
+ groups.set(w.version, { version: w.version, changelogUrl: w.changelogUrl, keys: [] });
1569
+ }
1570
+ groups.get(w.version).keys.push(w.key);
927
1571
  }
928
- let groqQuery;
929
- let params = {};
930
- if (customQuery) {
931
- groqQuery = customQuery;
932
- } else if (queryTypes && queryTypes.length > 0) {
933
- const seoFilter = queryRequireSeo ? " && seo != null" : "";
934
- const titleProj = buildTitleProjection(titleField);
935
- groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
1572
+ return Array.from(groups.values());
1573
+ }, [allDeprecationWarnings]);
1574
+ const client = useClient({ apiVersion });
1575
+ const [licenseStatus, setLicenseStatus] = useState("loading");
1576
+ const [documents, setDocuments] = useState([]);
1577
+ const [loading, setLoading] = useState(true);
1578
+ const [isRefreshing, setIsRefreshing] = useState(false);
1579
+ const [searchQuery, setSearchQuery] = useState("");
1580
+ const [filterStatus, setFilterStatus] = useState("all");
1581
+ const [filterType, setFilterType] = useState("all");
1582
+ const [sortBy, setSortBy] = useState("score");
1583
+ const [activePopover, setActivePopover] = useState(null);
1584
+ const VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
1585
+ const CACHE_TTL_MS = 60 * 60 * 1e3;
1586
+ const validateLicense = useCallback(
1587
+ async (forceRefresh = false) => {
1588
+ var _a;
1589
+ if (previewMode) {
1590
+ setLicenseStatus("valid");
1591
+ return;
1592
+ }
1593
+ if (!licenseKey) {
1594
+ setLicenseStatus("invalid");
1595
+ return;
1596
+ }
1597
+ const projectId = (_a = client.config().projectId) != null ? _a : "";
1598
+ const cacheKey = `seofields_license_${projectId}`;
1599
+ if (forceRefresh) {
1600
+ try {
1601
+ sessionStorage.removeItem(cacheKey);
1602
+ } catch (e) {
1603
+ }
1604
+ }
1605
+ if (!forceRefresh) {
1606
+ try {
1607
+ const cached = sessionStorage.getItem(cacheKey);
1608
+ if (cached) {
1609
+ const { valid, ts } = JSON.parse(cached);
1610
+ if (Date.now() - ts < CACHE_TTL_MS) {
1611
+ setLicenseStatus(valid ? "valid" : "invalid");
1612
+ return;
1613
+ }
1614
+ }
1615
+ } catch (e) {
1616
+ }
1617
+ }
1618
+ setLicenseStatus("loading");
1619
+ try {
1620
+ const res = await fetch(VALIDATION_ENDPOINT, {
1621
+ method: "POST",
1622
+ headers: { "Content-Type": "application/json" },
1623
+ body: JSON.stringify({ licenseKey, projectId })
1624
+ });
1625
+ const valid = res.ok;
1626
+ setLicenseStatus(valid ? "valid" : "invalid");
1627
+ try {
1628
+ sessionStorage.setItem(cacheKey, JSON.stringify({ valid, ts: Date.now() }));
1629
+ } catch (e) {
1630
+ }
1631
+ } catch (e) {
1632
+ setLicenseStatus("valid");
1633
+ }
1634
+ },
1635
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1636
+ [licenseKey, previewMode]
1637
+ );
1638
+ useEffect(() => {
1639
+ validateLicense();
1640
+ }, [licenseKey, previewMode]);
1641
+ const handleMouseEnterIssues = (el, issues) => {
1642
+ if (!el) return;
1643
+ const rect = el.getBoundingClientRect();
1644
+ const popoverWidth = 280;
1645
+ const viewportWidth = window.innerWidth;
1646
+ let left = rect.left;
1647
+ if (left + popoverWidth > viewportWidth - 10) left = viewportWidth - popoverWidth - 10;
1648
+ if (left < 10) left = 10;
1649
+ setActivePopover({ top: rect.top, left, issues });
1650
+ };
1651
+ const JSONStringifiedQueryTypes = JSON.stringify(queryTypes);
1652
+ const JSONStringifiedTitleField = JSON.stringify(titleField);
1653
+ const fetchDocuments = useCallback(
1654
+ async (isManualRefresh = false) => {
1655
+ try {
1656
+ if (isManualRefresh) {
1657
+ setIsRefreshing(true);
1658
+ } else {
1659
+ setLoading(true);
1660
+ }
1661
+ if (previewMode) {
1662
+ setDocuments(generateDummyData());
1663
+ return;
1664
+ }
1665
+ let groqQuery;
1666
+ let params = {};
1667
+ if (customQuery) {
1668
+ groqQuery = customQuery;
1669
+ } else if (queryTypes && queryTypes.length > 0) {
1670
+ const seoFilter = queryRequireSeo ? " && seo != null" : "";
1671
+ const titleProj = buildTitleProjection(titleField);
1672
+ groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
936
1673
  _id,
937
1674
  _type,
938
1675
  ${titleProj},
@@ -940,846 +1677,473 @@ var SeoHealthDashboard = ({
940
1677
  seo,
941
1678
  _updatedAt
942
1679
  }`;
943
- params = { types: queryTypes };
944
- } else {
945
- const titleProj = buildTitleProjection(titleField);
946
- groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
1680
+ params = { types: queryTypes };
1681
+ } else {
1682
+ const titleProj = buildTitleProjection(titleField);
1683
+ groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
947
1684
  _id,
948
1685
  _type,
949
1686
  ${titleProj},
950
- slug,
951
- seo,
952
- _updatedAt
953
- }`;
954
- }
955
- const result = await client.fetch(groqQuery, params, { perspective: "published" });
956
- const docsWithHealth = result.map((doc) => __spreadProps(__spreadValues({}, doc), {
957
- health: calculateHealthScore(doc)
958
- }));
959
- setDocuments(docsWithHealth);
960
- } catch (error) {
961
- console.error("Error fetching documents:", error);
962
- } finally {
963
- setLoading(false);
964
- }
965
- };
966
- fetchDocuments();
967
- }, [
968
- client,
969
- customQuery,
970
- queryRequireSeo,
971
- // eslint-disable-next-line react-hooks/exhaustive-deps
972
- JSON.stringify(queryTypes),
973
- // eslint-disable-next-line react-hooks/exhaustive-deps
974
- JSON.stringify(titleField),
975
- previewMode
976
- ]);
977
- const uniqueDocumentTypes = useMemo(() => {
978
- const types2 = new Set(documents.map((doc) => doc._type));
979
- return Array.from(types2).sort();
980
- }, [documents]);
981
- const filteredAndSortedDocs = useMemo(() => {
982
- let filtered = documents;
983
- if (searchQuery) {
984
- filtered = filtered.filter(
985
- (doc) => {
986
- var _a, _b;
987
- return ((_a = doc.title) == null ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase())) || ((_b = doc._id) == null ? void 0 : _b.toLowerCase().includes(searchQuery.toLowerCase()));
988
- }
989
- );
990
- }
991
- if (filterStatus !== "all") {
992
- filtered = filtered.filter((doc) => doc.health.status === filterStatus);
993
- }
994
- if (filterType !== "all") {
995
- filtered = filtered.filter((doc) => doc._type === filterType);
996
- }
997
- const sorted = [...filtered].sort((a, b) => {
998
- if (sortBy === "score") {
999
- return b.health.score - a.health.score;
1000
- }
1001
- return (a.title || "").localeCompare(b.title || "");
1002
- });
1003
- return sorted;
1004
- }, [documents, searchQuery, filterStatus, filterType, sortBy]);
1005
- const stats = useMemo(() => {
1006
- const total = documents.length;
1007
- const excellent = documents.filter((d) => d.health.score >= 80).length;
1008
- const good = documents.filter((d) => d.health.score >= 60 && d.health.score < 80).length;
1009
- const fair = documents.filter((d) => d.health.score >= 40 && d.health.score < 60).length;
1010
- const poor = documents.filter((d) => d.health.score > 0 && d.health.score < 40).length;
1011
- const missing = documents.filter((d) => d.health.score === 0).length;
1012
- const avgScore = total > 0 ? Math.round(documents.reduce((sum, d) => sum + d.health.score, 0) / total) : 0;
1013
- return { total, excellent, good, fair, poor, missing, avgScore };
1014
- }, [documents]);
1015
- const handleMouseLeave = useCallback(() => {
1016
- setActivePopover(null);
1017
- }, []);
1018
- return /* @__PURE__ */ jsxs(DashboardContainer, { children: [
1019
- licenseStatus === "loading" && /* @__PURE__ */ jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
1020
- /* @__PURE__ */ jsx(Spinner, {}),
1021
- loadingLicense != null ? loadingLicense : "Verifying license\u2026"
1022
- ] }),
1023
- licenseStatus === "invalid" && /* @__PURE__ */ jsx(UpgradeContainer, { children: /* @__PURE__ */ jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxs(Fragment, { children: [
1024
- /* @__PURE__ */ jsx(UpgradeLock, { children: "\u274C" }),
1025
- /* @__PURE__ */ jsx(UpgradeTitle, { children: "Invalid License Key" }),
1026
- /* @__PURE__ */ jsx(UpgradeText, { children: "The license key you provided is invalid or has been revoked. Please check your key and update it in the plugin config." }),
1027
- /* @__PURE__ */ jsx(UpgradeCode, { children: `seofields({
1028
- healthDashboard: {
1029
- licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
1030
- },
1031
- })` }),
1032
- /* @__PURE__ */ jsx(
1033
- UpgradeButton,
1034
- {
1035
- href: "https://sanity-plugin-seofields.thehardik.in",
1036
- target: "_blank",
1037
- rel: "noopener noreferrer",
1038
- children: "Get a New License Key \u2192"
1039
- }
1040
- ),
1041
- /* @__PURE__ */ jsx("br", {}),
1042
- /* @__PURE__ */ jsx(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
1043
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1044
- /* @__PURE__ */ jsx(UpgradeLock, { children: "\u{1F512}" }),
1045
- /* @__PURE__ */ jsx(UpgradeTitle, { children: "SEO Health Dashboard" }),
1046
- /* @__PURE__ */ jsx(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
1047
- /* @__PURE__ */ jsx(UpgradeCode, { children: `// sanity.config.ts
1048
- import { seofields } from 'sanity-plugin-seofields'
1049
-
1050
- export default defineConfig({
1051
- plugins: [
1052
- seofields({
1053
- healthDashboard: {
1054
- licenseKey: 'SEOF-XXXX-XXXX-XXXX',
1055
- },
1056
- }),
1057
- ],
1058
- })` }),
1059
- /* @__PURE__ */ jsx(
1060
- UpgradeButton,
1061
- {
1062
- href: "https://sanity-plugin-seofields.thehardik.in",
1063
- target: "_blank",
1064
- rel: "noopener noreferrer",
1065
- children: "Get a License Key \u2192"
1066
- }
1067
- )
1068
- ] }) }) }),
1069
- licenseStatus === "valid" && /* @__PURE__ */ jsxs(Fragment, { children: [
1070
- /* @__PURE__ */ jsxs(PageHeader, { children: [
1071
- /* @__PURE__ */ jsxs(PageTitle, { children: [
1072
- /* @__PURE__ */ jsxs("span", { children: [
1073
- icon,
1074
- " ",
1075
- title
1076
- ] }),
1077
- previewMode && /* @__PURE__ */ jsx(PreviewBadge, { children: "Preview Mode" })
1078
- ] }),
1079
- /* @__PURE__ */ jsx(PageSubtitle, { children: description })
1080
- ] }),
1081
- !loading && /* @__PURE__ */ jsxs(StatsGrid, { children: [
1082
- /* @__PURE__ */ jsxs(StatCard, { children: [
1083
- /* @__PURE__ */ jsx(StatLabel, { children: "Total Docs" }),
1084
- /* @__PURE__ */ jsx(StatValue, { children: stats.total })
1085
- ] }),
1086
- /* @__PURE__ */ jsxs(StatCard, { children: [
1087
- /* @__PURE__ */ jsx(StatLabel, { children: "Avg Score" }),
1088
- /* @__PURE__ */ jsxs(StatValue, { children: [
1089
- stats.avgScore,
1090
- "%"
1091
- ] })
1092
- ] }),
1093
- /* @__PURE__ */ jsxs(StatCard, { $accent: "#10b981", children: [
1094
- /* @__PURE__ */ jsx(StatLabel, { children: "Excellent (80+)" }),
1095
- /* @__PURE__ */ jsx(StatValue, { children: stats.excellent })
1096
- ] }),
1097
- /* @__PURE__ */ jsxs(StatCard, { $accent: "#f59e0b", children: [
1098
- /* @__PURE__ */ jsx(StatLabel, { children: "Good (60\u201379)" }),
1099
- /* @__PURE__ */ jsx(StatValue, { children: stats.good })
1100
- ] }),
1101
- /* @__PURE__ */ jsxs(StatCard, { $accent: "#f97316", children: [
1102
- /* @__PURE__ */ jsx(StatLabel, { children: "Fair (40\u201359)" }),
1103
- /* @__PURE__ */ jsx(StatValue, { children: stats.fair })
1104
- ] }),
1105
- /* @__PURE__ */ jsxs(StatCard, { $accent: "#ef4444", children: [
1106
- /* @__PURE__ */ jsx(StatLabel, { children: "Poor / Missing" }),
1107
- /* @__PURE__ */ jsx(StatValue, { children: stats.poor + stats.missing })
1108
- ] })
1109
- ] }),
1110
- /* @__PURE__ */ jsxs(ControlsBar, { children: [
1111
- /* @__PURE__ */ jsxs(SearchWrapper, { children: [
1112
- /* @__PURE__ */ jsx(SearchIconSvg, { children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx(
1113
- "path",
1114
- {
1115
- fillRule: "evenodd",
1116
- d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
1117
- clipRule: "evenodd"
1118
- }
1119
- ) }) }),
1120
- /* @__PURE__ */ jsx(
1121
- SearchInput,
1122
- {
1123
- placeholder: "Search documents...",
1124
- value: searchQuery,
1125
- onChange: (e) => setSearchQuery(e.currentTarget.value)
1126
- }
1127
- )
1128
- ] }),
1129
- /* @__PURE__ */ jsxs(
1130
- StyledSelect,
1131
- {
1132
- value: filterStatus,
1133
- onChange: (e) => setFilterStatus(e.currentTarget.value),
1134
- children: [
1135
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Status" }),
1136
- /* @__PURE__ */ jsx("option", { value: "excellent", children: "Excellent" }),
1137
- /* @__PURE__ */ jsx("option", { value: "good", children: "Good" }),
1138
- /* @__PURE__ */ jsx("option", { value: "fair", children: "Fair" }),
1139
- /* @__PURE__ */ jsx("option", { value: "poor", children: "Poor" }),
1140
- /* @__PURE__ */ jsx("option", { value: "missing", children: "Missing" })
1141
- ]
1142
- }
1143
- ),
1144
- uniqueDocumentTypes.length > 1 && /* @__PURE__ */ jsxs(
1145
- StyledSelect,
1146
- {
1147
- value: filterType,
1148
- onChange: (e) => setFilterType(e.currentTarget.value),
1149
- children: [
1150
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Types" }),
1151
- uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: resolveTypeLabel(type, typeLabels) }, type))
1152
- ]
1153
- }
1154
- ),
1155
- /* @__PURE__ */ jsxs(
1156
- StyledSelect,
1157
- {
1158
- value: sortBy,
1159
- onChange: (e) => setSortBy(e.currentTarget.value),
1160
- children: [
1161
- /* @__PURE__ */ jsx("option", { value: "score", children: "Sort by Score" }),
1162
- /* @__PURE__ */ jsx("option", { value: "title", children: "Sort by Title" })
1163
- ]
1164
- }
1165
- )
1166
- ] }),
1167
- /* @__PURE__ */ jsxs(TableCard, { children: [
1168
- loading && /* @__PURE__ */ jsxs(LoadingState, { children: [
1169
- /* @__PURE__ */ jsx(Spinner, {}),
1170
- loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
1171
- ] }),
1172
- !loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1173
- /* @__PURE__ */ jsxs(TableHeader, { children: [
1174
- /* @__PURE__ */ jsx(ColTitle, { children: "Title" }),
1175
- showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: "Type" }),
1176
- /* @__PURE__ */ jsx(ColScore, { children: "Score" }),
1177
- /* @__PURE__ */ jsx(ColIssues, { children: "Top Issues" })
1178
- ] }),
1179
- filteredAndSortedDocs.map((doc) => {
1180
- return /* @__PURE__ */ jsxs(TableRow, { children: [
1181
- /* @__PURE__ */ jsx(ColTitle, { children: /* @__PURE__ */ jsx(TitleWrapper, { children: /* @__PURE__ */ jsxs(TitleCell, { children: [
1182
- openInPane ? /* @__PURE__ */ jsx(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }) : /* @__PURE__ */ jsx(
1183
- DocTitleAnchor,
1184
- {
1185
- id: doc._id,
1186
- type: doc._type,
1187
- structureTool,
1188
- children: doc.title || "Untitled"
1189
- }
1190
- ),
1191
- showDocumentId && /* @__PURE__ */ jsx(DocId, { children: doc._id }),
1192
- docBadge && /* @__PURE__ */ jsx(
1193
- DocBadgeRenderer,
1194
- {
1195
- doc,
1196
- docBadge
1197
- }
1198
- )
1199
- ] }) }) }),
1200
- showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : (() => {
1201
- const typeColor = getTypeColor(doc._type);
1202
- return /* @__PURE__ */ jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, typeLabels) });
1203
- })() }),
1204
- /* @__PURE__ */ jsx(ColScore, { children: /* @__PURE__ */ jsxs(ScoreBadge, { $score: doc.health.score, children: [
1205
- doc.health.score,
1206
- "%"
1207
- ] }) }),
1208
- /* @__PURE__ */ jsxs(ColIssues, { children: [
1209
- doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ jsxs(IssueTag, { children: [
1210
- "\u2022 ",
1211
- issue
1212
- ] }, `issue-${doc._id}-${issue}`)),
1213
- doc.health.issues.length > 2 && /* @__PURE__ */ jsx(
1214
- MoreIssuesWrapper,
1215
- {
1216
- onMouseEnter: function(e) {
1217
- handleMouseEnterIssues(
1218
- e.currentTarget,
1219
- doc.health.issues
1220
- );
1221
- },
1222
- onMouseLeave: handleMouseLeave,
1223
- children: /* @__PURE__ */ jsxs(MoreIssues, { children: [
1224
- "+",
1225
- doc.health.issues.length - 2,
1226
- " more issues"
1227
- ] })
1228
- }
1229
- )
1230
- ] })
1231
- ] }, doc._id);
1232
- })
1233
- ] }))
1234
- ] }),
1235
- activePopover && /* @__PURE__ */ jsx(
1236
- IssuesPopover,
1237
- {
1238
- style: {
1239
- top: activePopover.top,
1240
- left: activePopover.left,
1241
- transform: "translateY(calc(-100% - 10px))"
1242
- },
1243
- children: activePopover.issues.map((issue) => /* @__PURE__ */ jsxs(PopoverIssueItem, { children: [
1244
- "\u26A0\uFE0F ",
1245
- issue
1246
- ] }, issue))
1247
- }
1248
- ),
1249
- " "
1250
- ] }),
1251
- " "
1252
- ] });
1253
- };
1254
- var SeoHealthDashboard_default = SeoHealthDashboard;
1255
-
1256
- // src/components/SeoHealthTool.tsx
1257
- import { jsx as jsx2 } from "react/jsx-runtime";
1258
- var SeoHealthTool = (props) => {
1259
- return /* @__PURE__ */ jsx2(SeoHealthDashboard_default, __spreadValues({}, props));
1260
- };
1261
- var SeoHealthTool_default = SeoHealthTool;
1262
-
1263
- // src/schemas/index.ts
1264
- import { defineField as defineField3, defineType as defineType3 } from "sanity";
1265
-
1266
- // src/components/meta/MetaDescription.tsx
1267
- import { Stack, Text } from "@sanity/ui";
1268
- import { useMemo as useMemo2 } from "react";
1269
- import { useFormValue } from "sanity";
1270
-
1271
- // src/utils/seoUtils.ts
1272
- var stopWords = ["the", "a", "an", "and", "or", "but"];
1273
- var hasMatchingKeyword = (title, keywordList) => {
1274
- if (!title || keywordList.length === 0) return false;
1275
- const lowerTitle = title.toLowerCase();
1276
- return keywordList.some((keyword) => keyword && lowerTitle.includes(keyword.toLowerCase()));
1277
- };
1278
- var hasKeywordOveruse = (title, keywordList, maxOccurrences = 3) => {
1279
- if (!title || keywordList.length === 0) return false;
1280
- const lowerTitle = title.toLowerCase();
1281
- return keywordList.some((keyword) => {
1282
- if (!keyword) return false;
1283
- const matches = lowerTitle.match(new RegExp(keyword.toLowerCase(), "g"));
1284
- return matches ? matches.length > maxOccurrences : false;
1285
- });
1286
- };
1287
- var startsWithStopWord = (title) => {
1288
- if (!title) return false;
1289
- const firstWord = title.trim().split(" ")[0].toLowerCase();
1290
- return stopWords.includes(firstWord);
1291
- };
1292
- var truncate = (text, maxLength) => text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
1293
- var hasExcessivePunctuation = (title) => /[!@#$%^&*]{2,}/.test(title);
1294
- var getMetaTitleValidationMessages = (title, keywords, isParentseoField) => {
1295
- const feedback = [];
1296
- const minChar = 50;
1297
- const maxChar = 60;
1298
- const charCount = (title == null ? void 0 : title.length) || 0;
1299
- if (!(title == null ? void 0 : title.trim())) {
1300
- feedback.push({ text: "Meta Title is empty. Add content to improve SEO.", color: "red" });
1301
- return feedback;
1302
- }
1303
- if (charCount < minChar)
1304
- feedback.push({
1305
- text: `Title is ${charCount} characters \u2014 below recommended ${minChar}.`,
1306
- color: "orange"
1307
- });
1308
- else if (charCount > maxChar)
1309
- feedback.push({
1310
- text: `Title is ${charCount} characters \u2014 exceeds recommended ${maxChar}.`,
1311
- color: "red"
1312
- });
1313
- else feedback.push({ text: `Title length (${charCount}) looks good for SEO.`, color: "green" });
1314
- if (isParentseoField) {
1315
- if (keywords.length > 0) {
1316
- const hasKeyword = hasMatchingKeyword(title, keywords);
1317
- feedback.push({
1318
- text: hasKeyword ? "Keyword found in title \u2014 good job!" : "Keywords defined but missing in title.",
1319
- color: hasKeyword ? "green" : "red"
1320
- });
1321
- if (hasKeywordOveruse(title, keywords)) {
1322
- feedback.push({
1323
- text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
1324
- color: "orange"
1325
- });
1326
- }
1327
- } else {
1328
- feedback.push({
1329
- text: "No keywords defined. Consider adding relevant keywords.",
1330
- color: "orange"
1331
- });
1332
- }
1333
- }
1334
- if (startsWithStopWord(title))
1335
- feedback.push({ text: "Title starts with a stop word \u2014 consider rephrasing.", color: "orange" });
1336
- if (hasExcessivePunctuation(title))
1337
- feedback.push({ text: "Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
1338
- return feedback;
1339
- };
1340
- var getMetaDescriptionValidationMessages = (description, keywords, isParentseoField) => {
1341
- const feedback = [];
1342
- const minChar = 120;
1343
- const maxChar = 160;
1344
- const charCount = (description == null ? void 0 : description.length) || 0;
1345
- if (!(description == null ? void 0 : description.trim())) {
1346
- feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" });
1347
- return feedback;
1348
- }
1349
- if (charCount < minChar)
1350
- feedback.push({
1351
- text: `Description is ${charCount} chars \u2014 below recommended ${minChar}.`,
1352
- color: "orange"
1353
- });
1354
- else if (charCount > maxChar)
1355
- feedback.push({
1356
- text: `Description is ${charCount} chars \u2014 exceeds recommended ${maxChar}.`,
1357
- color: "red"
1358
- });
1359
- else
1360
- feedback.push({ text: `Description length (${charCount}) looks good for SEO.`, color: "green" });
1361
- if (isParentseoField) {
1362
- if (keywords.length > 0) {
1363
- const hasKeyword = hasMatchingKeyword(description, keywords);
1364
- feedback.push({
1365
- text: hasKeyword ? "Keyword found in description \u2014 good job!" : "Keywords defined but missing in description.",
1366
- color: hasKeyword ? "green" : "red"
1367
- });
1368
- if (hasKeywordOveruse(description, keywords)) {
1369
- feedback.push({
1370
- text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
1371
- color: "orange"
1372
- });
1373
- }
1374
- } else {
1375
- feedback.push({
1376
- text: "No keywords defined. Consider adding relevant keywords.",
1377
- color: "orange"
1378
- });
1379
- }
1380
- }
1381
- if (startsWithStopWord(description))
1382
- feedback.push({
1383
- text: "Description starts with a stop word \u2014 consider rephrasing.",
1384
- color: "orange"
1385
- });
1386
- if (hasExcessivePunctuation(description))
1387
- feedback.push({
1388
- text: "Description contains excessive punctuation \u2014 simplify it.",
1389
- color: "orange"
1390
- });
1391
- return feedback;
1392
- };
1393
- var getOgTitleValidation = (title, keywords = [], isParentseoField) => {
1394
- const feedback = [];
1395
- const min = 40;
1396
- const max = 60;
1397
- const count = (title == null ? void 0 : title.length) || 0;
1398
- if (!(title == null ? void 0 : title.trim())) {
1399
- feedback.push({ text: "OG Title is empty. Add content for better social preview.", color: "red" });
1400
- return feedback;
1401
- }
1402
- if (count < min)
1403
- feedback.push({
1404
- text: `OG Title is ${count} chars \u2014 shorter than recommended ${min}.`,
1405
- color: "orange"
1406
- });
1407
- else if (count > max)
1408
- feedback.push({ text: `OG Title is ${count} chars \u2014 exceeds recommended ${max}.`, color: "red" });
1409
- else feedback.push({ text: `OG Title length (${count}) looks good.`, color: "green" });
1410
- if (isParentseoField) {
1411
- if (keywords.length > 0) {
1412
- const hasKeyword = hasMatchingKeyword(title, keywords);
1413
- feedback.push({
1414
- text: hasKeyword ? "Keyword found in OG title \u2014 good job!" : "Keywords defined but missing in OG title.",
1415
- color: hasKeyword ? "green" : "red"
1416
- });
1417
- if (hasKeywordOveruse(title, keywords)) {
1418
- feedback.push({
1419
- text: "Keyword appears too many times in OG title \u2014 avoid keyword stuffing.",
1420
- color: "orange"
1421
- });
1422
- }
1423
- } else {
1424
- feedback.push({
1425
- text: "No keywords defined. Consider adding relevant keywords.",
1426
- color: "orange"
1427
- });
1428
- }
1429
- }
1430
- if (startsWithStopWord(title))
1431
- feedback.push({
1432
- text: "OG Title starts with a stop word \u2014 consider rephrasing.",
1433
- color: "orange"
1434
- });
1435
- if (hasExcessivePunctuation(title))
1436
- feedback.push({ text: "OG Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
1437
- return feedback;
1438
- };
1439
- var getOgDescriptionValidation = (desc, keywords = [], isParentseoField) => {
1440
- const feedback = [];
1441
- const min = 90;
1442
- const max = 120;
1443
- const count = (desc == null ? void 0 : desc.length) || 0;
1444
- if (!(desc == null ? void 0 : desc.trim())) {
1445
- feedback.push({
1446
- text: "OG Description is empty. Add content for better social preview.",
1447
- color: "red"
1448
- });
1449
- return feedback;
1450
- }
1451
- if (count < min)
1452
- feedback.push({
1453
- text: `OG Description is ${count} chars \u2014 shorter than recommended ${min}.`,
1454
- color: "orange"
1455
- });
1456
- else if (count > max)
1457
- feedback.push({
1458
- text: `OG Description is ${count} chars \u2014 exceeds recommended ${max}.`,
1459
- color: "red"
1460
- });
1461
- else feedback.push({ text: `OG Description length (${count}) looks good.`, color: "green" });
1462
- if (isParentseoField) {
1463
- if (keywords.length > 0) {
1464
- const hasKeyword = hasMatchingKeyword(desc, keywords);
1465
- feedback.push({
1466
- text: hasKeyword ? "Keyword found in OG description \u2014 good job!" : "Keywords defined but missing in OG description.",
1467
- color: hasKeyword ? "green" : "red"
1468
- });
1469
- if (hasKeywordOveruse(desc, keywords)) {
1470
- feedback.push({
1471
- text: "Keyword appears too many times in OG description \u2014 avoid keyword stuffing.",
1472
- color: "orange"
1473
- });
1474
- }
1475
- } else {
1476
- feedback.push({
1477
- text: "No keywords defined. Consider adding relevant keywords.",
1478
- color: "orange"
1479
- });
1480
- }
1481
- }
1482
- if (startsWithStopWord(desc))
1483
- feedback.push({
1484
- text: "OG Description starts with a stop word \u2014 consider rephrasing.",
1485
- color: "orange"
1486
- });
1487
- if (hasExcessivePunctuation(desc))
1488
- feedback.push({
1489
- text: "OG Description contains excessive punctuation \u2014 simplify it.",
1490
- color: "orange"
1491
- });
1492
- return feedback;
1493
- };
1494
- var getTwitterTitleValidation = (title, keywords = [], isParentseoField) => {
1495
- const feedback = [];
1496
- const min = 30;
1497
- const max = 70;
1498
- const count = (title == null ? void 0 : title.length) || 0;
1499
- if (!(title == null ? void 0 : title.trim())) {
1500
- feedback.push({ text: "X Title is empty. Add content for better SEO.", color: "red" });
1501
- return feedback;
1502
- }
1503
- if (count < min)
1504
- feedback.push({
1505
- text: `X Title is ${count} chars \u2014 shorter than recommended ${min}.`,
1506
- color: "orange"
1507
- });
1508
- else if (count > max)
1509
- feedback.push({
1510
- text: `X Title is ${count} chars \u2014 exceeds recommended ${max}.`,
1511
- color: "red"
1512
- });
1513
- else feedback.push({ text: `X Title length (${count}) looks good.`, color: "green" });
1514
- if (isParentseoField) {
1515
- if (keywords.length > 0) {
1516
- const hasKeyword = hasMatchingKeyword(title, keywords);
1517
- feedback.push({
1518
- text: hasKeyword ? "Keyword found in X title \u2014 good job!" : "Keywords defined but missing in X title.",
1519
- color: hasKeyword ? "green" : "red"
1520
- });
1521
- } else {
1522
- feedback.push({
1523
- text: "No keywords defined. Consider adding relevant keywords.",
1524
- color: "orange"
1525
- });
1526
- }
1527
- }
1528
- if (/[!@#$%^&*]{2,}/.test(title))
1529
- feedback.push({ text: "X Title has excessive punctuation \u2014 simplify it.", color: "orange" });
1530
- return feedback;
1531
- };
1532
- var getTwitterDescriptionValidation = (desc, keywords = [], isParentseoField) => {
1533
- const feedback = [];
1534
- const min = 50;
1535
- const max = 200;
1536
- const count = (desc == null ? void 0 : desc.length) || 0;
1537
- if (!(desc == null ? void 0 : desc.trim())) {
1538
- feedback.push({ text: "X Description is empty. Add content for better SEO.", color: "red" });
1539
- return feedback;
1540
- }
1541
- if (count < min)
1542
- feedback.push({
1543
- text: `X Description is ${count} chars \u2014 shorter than recommended ${min}.`,
1544
- color: "orange"
1545
- });
1546
- else if (count > max)
1547
- feedback.push({
1548
- text: `X Description is ${count} chars \u2014 exceeds recommended ${max}.`,
1549
- color: "red"
1550
- });
1551
- else feedback.push({ text: `X Description length (${count}) looks good.`, color: "green" });
1552
- if (isParentseoField) {
1553
- if (keywords.length > 0) {
1554
- const hasKeyword = hasMatchingKeyword(desc, keywords);
1555
- feedback.push({
1556
- text: hasKeyword ? "Keyword found in X description \u2014 good job!" : "Keywords defined but missing in X description.",
1557
- color: hasKeyword ? "green" : "red"
1558
- });
1559
- } else {
1560
- feedback.push({
1561
- text: "No keywords defined. Consider adding relevant keywords.",
1562
- color: "orange"
1563
- });
1564
- }
1565
- }
1566
- if (/[!@#$%^&*]{2,}/.test(desc))
1567
- feedback.push({
1568
- text: "X Description has excessive punctuation \u2014 simplify it.",
1569
- color: "orange"
1570
- });
1571
- return feedback;
1572
- };
1573
- var isSubImageSet = (subObj) => {
1574
- var _a;
1575
- if (!subObj) return false;
1576
- if (subObj.imageType === "url") return !!((_a = subObj.imageUrl) == null ? void 0 : _a.trim());
1577
- const img = subObj.image;
1578
- return !!(img == null ? void 0 : img.asset);
1579
- };
1580
- var isMetaImageSet = (seoParent) => {
1581
- if (!seoParent) return false;
1582
- const metaImage = seoParent.metaImage;
1583
- return !!(metaImage == null ? void 0 : metaImage.asset);
1584
- };
1585
- var getMetaImageValidation = (hasImage, seoParent) => {
1586
- const feedback = [];
1587
- if (!hasImage) {
1588
- feedback.push({
1589
- text: "No meta image provided. Adding an image improves click-through rates.",
1590
- color: "red"
1591
- });
1592
- return feedback;
1593
- }
1594
- feedback.push({ text: "Meta image is set \u2014 great for SEO and social sharing.", color: "green" });
1595
- const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
1596
- const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
1597
- if (!ogSet && !twSet) {
1598
- feedback.push({
1599
- text: "OG and Twitter images are missing \u2014 add them for full social coverage.",
1600
- color: "orange"
1601
- });
1602
- } else if (!ogSet) {
1603
- feedback.push({
1604
- text: "OG image is missing \u2014 add it for better Facebook/LinkedIn previews.",
1605
- color: "orange"
1606
- });
1607
- } else if (twSet) {
1608
- feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
1609
- } else {
1610
- feedback.push({
1611
- text: "Twitter image is missing \u2014 add it for better X (Twitter) cards.",
1612
- color: "orange"
1613
- });
1614
- }
1615
- return feedback;
1616
- };
1617
- var getOgImageValidation = (hasImage, altText, seoParent) => {
1618
- const feedback = [];
1619
- if (!hasImage) {
1620
- feedback.push({
1621
- text: "No OG image provided. Social shares will lack a visual preview.",
1622
- color: "red"
1623
- });
1624
- return feedback;
1625
- }
1626
- feedback.push({ text: "OG image is set \u2014 good for social sharing.", color: "green" });
1627
- if (altText == null ? void 0 : altText.trim()) {
1628
- feedback.push({ text: "Alt text is set \u2014 good for accessibility.", color: "green" });
1629
- } else {
1630
- feedback.push({ text: "Consider adding alt text for better accessibility.", color: "orange" });
1631
- }
1632
- const metaSet = isMetaImageSet(seoParent);
1633
- const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
1634
- if (metaSet && twSet) {
1635
- feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
1636
- } else {
1637
- if (!metaSet)
1638
- feedback.push({
1639
- text: "Meta image is missing \u2014 add it for search engine results.",
1640
- color: "orange"
1641
- });
1642
- if (!twSet)
1643
- feedback.push({
1644
- text: "Twitter image is missing \u2014 add it for X (Twitter) cards.",
1645
- color: "orange"
1646
- });
1647
- }
1648
- return feedback;
1649
- };
1650
- var getOgImageUrlValidation = (imageUrl, seoParent) => {
1651
- const feedback = [];
1652
- if (!(imageUrl == null ? void 0 : imageUrl.trim())) {
1653
- feedback.push({
1654
- text: "No OG image URL provided. Social shares will lack a visual preview.",
1655
- color: "red"
1656
- });
1657
- return feedback;
1658
- }
1659
- feedback.push({ text: "OG image URL is set \u2014 good for social sharing.", color: "green" });
1660
- const metaSet = isMetaImageSet(seoParent);
1661
- const twSet = isSubImageSet(seoParent == null ? void 0 : seoParent.twitter);
1662
- if (metaSet && twSet) {
1663
- feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
1664
- } else {
1665
- if (!metaSet)
1666
- feedback.push({
1667
- text: "Meta image is missing \u2014 add it for search engine results.",
1668
- color: "orange"
1669
- });
1670
- if (!twSet)
1671
- feedback.push({
1672
- text: "Twitter image is missing \u2014 add it for X (Twitter) cards.",
1673
- color: "orange"
1674
- });
1675
- }
1676
- return feedback;
1677
- };
1678
- var getTwitterImageValidation = (hasImage, altText, seoParent) => {
1679
- const feedback = [];
1680
- if (!hasImage) {
1681
- feedback.push({
1682
- text: "No Twitter image provided. Posts on X will lack a visual.",
1683
- color: "red"
1684
- });
1685
- return feedback;
1686
- }
1687
- feedback.push({ text: "Twitter image is set \u2014 good for X sharing.", color: "green" });
1688
- if (altText == null ? void 0 : altText.trim()) {
1689
- feedback.push({ text: "Alt text is set \u2014 good for accessibility.", color: "green" });
1690
- } else {
1691
- feedback.push({ text: "Consider adding alt text for better accessibility.", color: "orange" });
1692
- }
1693
- const metaSet = isMetaImageSet(seoParent);
1694
- const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
1695
- if (metaSet && ogSet) {
1696
- feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
1697
- } else {
1698
- if (!metaSet)
1699
- feedback.push({
1700
- text: "Meta image is missing \u2014 add it for search engine results.",
1701
- color: "orange"
1702
- });
1703
- if (!ogSet)
1704
- feedback.push({
1705
- text: "OG image is missing \u2014 add it for Facebook/LinkedIn sharing.",
1706
- color: "orange"
1707
- });
1708
- }
1709
- return feedback;
1710
- };
1711
- var getTwitterImageUrlValidation = (imageUrl, seoParent) => {
1712
- const feedback = [];
1713
- if (!(imageUrl == null ? void 0 : imageUrl.trim())) {
1714
- feedback.push({
1715
- text: "No Twitter image URL provided. Posts on X will lack a visual.",
1716
- color: "red"
1717
- });
1718
- return feedback;
1687
+ slug,
1688
+ seo,
1689
+ _updatedAt
1690
+ }`;
1691
+ }
1692
+ const result = await client.fetch(groqQuery, params, { perspective: "published" });
1693
+ const docsWithHealth = result.map((doc) => __spreadProps(__spreadValues({}, doc), {
1694
+ health: calculateHealthScore(doc)
1695
+ }));
1696
+ setDocuments(docsWithHealth);
1697
+ } catch (error) {
1698
+ console.error("Error fetching documents:", error);
1699
+ } finally {
1700
+ setLoading(false);
1701
+ setIsRefreshing(false);
1702
+ }
1703
+ },
1704
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1705
+ [
1706
+ client,
1707
+ customQuery,
1708
+ queryRequireSeo,
1709
+ JSONStringifiedQueryTypes,
1710
+ JSONStringifiedTitleField,
1711
+ previewMode
1712
+ ]
1713
+ );
1714
+ useEffect(() => {
1715
+ fetchDocuments();
1716
+ }, [
1717
+ client,
1718
+ customQuery,
1719
+ queryRequireSeo,
1720
+ JSONStringifiedQueryTypes,
1721
+ JSONStringifiedTitleField,
1722
+ previewMode
1723
+ ]);
1724
+ const handleRefresh = useCallback(() => {
1725
+ fetchDocuments(true);
1726
+ }, [fetchDocuments]);
1727
+ const uniqueDocumentTypes = useMemo12(() => {
1728
+ const types2 = new Set(documents.map((doc) => doc._type));
1729
+ return Array.from(types2).sort();
1730
+ }, [documents]);
1731
+ const filteredAndSortedDocs = useMemo12(() => {
1732
+ let filtered = documents;
1733
+ if (searchQuery) {
1734
+ filtered = filtered.filter(
1735
+ (doc) => {
1736
+ var _a, _b;
1737
+ return ((_a = doc.title) == null ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase())) || ((_b = doc._id) == null ? void 0 : _b.toLowerCase().includes(searchQuery.toLowerCase()));
1738
+ }
1739
+ );
1740
+ }
1741
+ if (filterStatus !== "all") {
1742
+ filtered = filtered.filter((doc) => doc.health.status === filterStatus);
1743
+ }
1744
+ if (filterType !== "all") {
1745
+ filtered = filtered.filter((doc) => doc._type === filterType);
1746
+ }
1747
+ const sorted = [...filtered].sort((a, b) => {
1748
+ if (sortBy === "score") {
1749
+ return b.health.score - a.health.score;
1750
+ }
1751
+ return (a.title || "").localeCompare(b.title || "");
1752
+ });
1753
+ return sorted;
1754
+ }, [documents, searchQuery, filterStatus, filterType, sortBy]);
1755
+ const stats = useMemo12(() => {
1756
+ const total = documents.length;
1757
+ const excellent = documents.filter((d) => d.health.score >= 80).length;
1758
+ const good = documents.filter((d) => d.health.score >= 60 && d.health.score < 80).length;
1759
+ const fair = documents.filter((d) => d.health.score >= 40 && d.health.score < 60).length;
1760
+ const poor = documents.filter((d) => d.health.score > 0 && d.health.score < 40).length;
1761
+ const missing = documents.filter((d) => d.health.score === 0).length;
1762
+ const avgScore = total > 0 ? Math.round(documents.reduce((sum, d) => sum + d.health.score, 0) / total) : 0;
1763
+ return { total, excellent, good, fair, poor, missing, avgScore };
1764
+ }, [documents]);
1765
+ const handleMouseLeave = useCallback(() => {
1766
+ setActivePopover(null);
1767
+ }, []);
1768
+ return /* @__PURE__ */ jsxs13(DashboardContainer, { children: [
1769
+ licenseStatus === "loading" && /* @__PURE__ */ jsxs13(LoadingState, { style: { padding: "80px 24px" }, children: [
1770
+ /* @__PURE__ */ jsx13(Spinner, {}),
1771
+ loadingLicense != null ? loadingLicense : "Verifying license\u2026"
1772
+ ] }),
1773
+ licenseStatus === "invalid" && /* @__PURE__ */ jsx13(UpgradeContainer, { children: /* @__PURE__ */ jsx13(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxs13(Fragment, { children: [
1774
+ /* @__PURE__ */ jsx13(UpgradeLock, { children: "\u274C" }),
1775
+ /* @__PURE__ */ jsx13(UpgradeTitle, { children: "Invalid License Key" }),
1776
+ /* @__PURE__ */ jsx13(UpgradeText, { children: "The license key you provided is invalid or has been revoked. Please check your key and update it in the plugin config." }),
1777
+ /* @__PURE__ */ jsx13(UpgradeCode, { children: `seofields({
1778
+ healthDashboard: {
1779
+ licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
1780
+ },
1781
+ })` }),
1782
+ /* @__PURE__ */ jsx13(
1783
+ UpgradeButton,
1784
+ {
1785
+ href: "https://sanity-plugin-seofields.thehardik.in",
1786
+ target: "_blank",
1787
+ rel: "noopener noreferrer",
1788
+ children: "Get a New License Key \u2192"
1789
+ }
1790
+ ),
1791
+ /* @__PURE__ */ jsx13("br", {}),
1792
+ /* @__PURE__ */ jsx13(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
1793
+ ] }) : /* @__PURE__ */ jsxs13(Fragment, { children: [
1794
+ /* @__PURE__ */ jsx13(UpgradeLock, { children: "\u{1F512}" }),
1795
+ /* @__PURE__ */ jsx13(UpgradeTitle, { children: "SEO Health Dashboard" }),
1796
+ /* @__PURE__ */ jsx13(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
1797
+ /* @__PURE__ */ jsx13(UpgradeCode, { children: `// sanity.config.ts
1798
+ import { seofields } from 'sanity-plugin-seofields'
1799
+
1800
+ export default defineConfig({
1801
+ plugins: [
1802
+ seofields({
1803
+ healthDashboard: {
1804
+ licenseKey: 'SEOF-XXXX-XXXX-XXXX',
1805
+ },
1806
+ }),
1807
+ ],
1808
+ })` }),
1809
+ /* @__PURE__ */ jsx13(
1810
+ UpgradeButton,
1811
+ {
1812
+ href: "https://sanity-plugin-seofields.thehardik.in",
1813
+ target: "_blank",
1814
+ rel: "noopener noreferrer",
1815
+ children: "Get a License Key \u2192"
1816
+ }
1817
+ )
1818
+ ] }) }) }),
1819
+ licenseStatus === "valid" && /* @__PURE__ */ jsxs13(Fragment, { children: [
1820
+ /* @__PURE__ */ jsxs13(PageHeader, { children: [
1821
+ /* @__PURE__ */ jsxs13("div", { children: [
1822
+ /* @__PURE__ */ jsxs13(PageTitle, { children: [
1823
+ /* @__PURE__ */ jsxs13("span", { children: [
1824
+ icon,
1825
+ " ",
1826
+ title
1827
+ ] }),
1828
+ previewMode && /* @__PURE__ */ jsx13(PreviewBadge, { children: "Preview Mode" })
1829
+ ] }),
1830
+ /* @__PURE__ */ jsx13(PageSubtitle, { children: description })
1831
+ ] }),
1832
+ /* @__PURE__ */ jsxs13(
1833
+ DashboardRefreshButton,
1834
+ {
1835
+ onClick: handleRefresh,
1836
+ disabled: loading || isRefreshing,
1837
+ $spinning: isRefreshing,
1838
+ title: "Refresh documents",
1839
+ children: [
1840
+ /* @__PURE__ */ jsxs13(
1841
+ "svg",
1842
+ {
1843
+ width: "14",
1844
+ height: "14",
1845
+ viewBox: "0 0 24 24",
1846
+ fill: "none",
1847
+ stroke: "currentColor",
1848
+ strokeWidth: "2.2",
1849
+ strokeLinecap: "round",
1850
+ strokeLinejoin: "round",
1851
+ children: [
1852
+ /* @__PURE__ */ jsx13("polyline", { points: "23 4 23 10 17 10" }),
1853
+ /* @__PURE__ */ jsx13("polyline", { points: "1 20 1 14 7 14" }),
1854
+ /* @__PURE__ */ jsx13("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
1855
+ ]
1856
+ }
1857
+ ),
1858
+ "Refresh"
1859
+ ]
1860
+ }
1861
+ )
1862
+ ] }),
1863
+ deprecationGroups.length > 0 && /* @__PURE__ */ jsxs13(DeprecationBanner, { children: [
1864
+ /* @__PURE__ */ jsx13("strong", { children: "\u26A0\uFE0F Deprecated config keys detected:" }),
1865
+ " ",
1866
+ deprecationGroups.map((group, gi) => /* @__PURE__ */ jsxs13("span", { children: [
1867
+ group.keys.map((w, i) => /* @__PURE__ */ jsxs13("span", { children: [
1868
+ /* @__PURE__ */ jsx13("code", { style: { background: "#fef9c3", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[0].trim() }),
1869
+ " \u2192 ",
1870
+ /* @__PURE__ */ jsx13("code", { style: { background: "#dcfce7", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[1].trim() }),
1871
+ i < group.keys.length - 1 ? " \xB7 " : ""
1872
+ ] }, w)),
1873
+ " ",
1874
+ "(",
1875
+ /* @__PURE__ */ jsxs13(
1876
+ DeprecationBannerLink,
1877
+ {
1878
+ href: group.changelogUrl,
1879
+ target: "_blank",
1880
+ rel: "noopener noreferrer",
1881
+ children: [
1882
+ group.version,
1883
+ " changelog"
1884
+ ]
1885
+ }
1886
+ ),
1887
+ ")",
1888
+ gi < deprecationGroups.length - 1 ? " \xB7 " : ""
1889
+ ] }, group.version)),
1890
+ " ",
1891
+ "\u2014 Please update your config."
1892
+ ] }),
1893
+ !loading && /* @__PURE__ */ jsxs13(StatsGrid, { children: [
1894
+ /* @__PURE__ */ jsxs13(StatCard, { children: [
1895
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Total Docs" }),
1896
+ /* @__PURE__ */ jsx13(StatValue, { children: stats.total })
1897
+ ] }),
1898
+ /* @__PURE__ */ jsxs13(StatCard, { children: [
1899
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Avg Score" }),
1900
+ /* @__PURE__ */ jsxs13(StatValue, { children: [
1901
+ stats.avgScore,
1902
+ "%"
1903
+ ] })
1904
+ ] }),
1905
+ /* @__PURE__ */ jsxs13(StatCard, { $accent: "#10b981", children: [
1906
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Excellent (80+)" }),
1907
+ /* @__PURE__ */ jsx13(StatValue, { children: stats.excellent })
1908
+ ] }),
1909
+ /* @__PURE__ */ jsxs13(StatCard, { $accent: "#f59e0b", children: [
1910
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Good (60\u201379)" }),
1911
+ /* @__PURE__ */ jsx13(StatValue, { children: stats.good })
1912
+ ] }),
1913
+ /* @__PURE__ */ jsxs13(StatCard, { $accent: "#f97316", children: [
1914
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Fair (40\u201359)" }),
1915
+ /* @__PURE__ */ jsx13(StatValue, { children: stats.fair })
1916
+ ] }),
1917
+ /* @__PURE__ */ jsxs13(StatCard, { $accent: "#ef4444", children: [
1918
+ /* @__PURE__ */ jsx13(StatLabel, { children: "Poor / Missing" }),
1919
+ /* @__PURE__ */ jsx13(StatValue, { children: stats.poor + stats.missing })
1920
+ ] })
1921
+ ] }),
1922
+ /* @__PURE__ */ jsxs13(ControlsBar, { children: [
1923
+ /* @__PURE__ */ jsxs13(SearchWrapper, { children: [
1924
+ /* @__PURE__ */ jsx13(SearchIconSvg, { children: /* @__PURE__ */ jsx13("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx13(
1925
+ "path",
1926
+ {
1927
+ fillRule: "evenodd",
1928
+ d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
1929
+ clipRule: "evenodd"
1930
+ }
1931
+ ) }) }),
1932
+ /* @__PURE__ */ jsx13(
1933
+ SearchInput,
1934
+ {
1935
+ placeholder: "Search documents...",
1936
+ value: searchQuery,
1937
+ onChange: (e) => setSearchQuery(e.currentTarget.value)
1938
+ }
1939
+ )
1940
+ ] }),
1941
+ /* @__PURE__ */ jsxs13(
1942
+ StyledSelect,
1943
+ {
1944
+ value: filterStatus,
1945
+ onChange: (e) => setFilterStatus(e.currentTarget.value),
1946
+ children: [
1947
+ /* @__PURE__ */ jsx13("option", { value: "all", children: "All Status" }),
1948
+ /* @__PURE__ */ jsx13("option", { value: "excellent", children: "Excellent" }),
1949
+ /* @__PURE__ */ jsx13("option", { value: "good", children: "Good" }),
1950
+ /* @__PURE__ */ jsx13("option", { value: "fair", children: "Fair" }),
1951
+ /* @__PURE__ */ jsx13("option", { value: "poor", children: "Poor" }),
1952
+ /* @__PURE__ */ jsx13("option", { value: "missing", children: "Missing" })
1953
+ ]
1954
+ }
1955
+ ),
1956
+ uniqueDocumentTypes.length > 1 && /* @__PURE__ */ jsxs13(
1957
+ StyledSelect,
1958
+ {
1959
+ value: filterType,
1960
+ onChange: (e) => setFilterType(e.currentTarget.value),
1961
+ children: [
1962
+ /* @__PURE__ */ jsx13("option", { value: "all", children: "All Types" }),
1963
+ uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsx13("option", { value: type, children: resolveTypeLabel(type, resolvedTypeLabels) }, type))
1964
+ ]
1965
+ }
1966
+ ),
1967
+ /* @__PURE__ */ jsxs13(
1968
+ StyledSelect,
1969
+ {
1970
+ value: sortBy,
1971
+ onChange: (e) => setSortBy(e.currentTarget.value),
1972
+ children: [
1973
+ /* @__PURE__ */ jsx13("option", { value: "score", children: "Sort by Score" }),
1974
+ /* @__PURE__ */ jsx13("option", { value: "title", children: "Sort by Title" })
1975
+ ]
1976
+ }
1977
+ )
1978
+ ] }),
1979
+ /* @__PURE__ */ jsxs13(TableCard, { children: [
1980
+ loading && /* @__PURE__ */ jsxs13(LoadingState, { children: [
1981
+ /* @__PURE__ */ jsx13(Spinner, {}),
1982
+ loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
1983
+ ] }),
1984
+ !loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsx13(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ jsxs13(Fragment, { children: [
1985
+ /* @__PURE__ */ jsxs13(TableHeader, { children: [
1986
+ /* @__PURE__ */ jsx13(ColTitle, { children: "Title" }),
1987
+ showTypeColumn && /* @__PURE__ */ jsx13(ColType, { children: "Type" }),
1988
+ /* @__PURE__ */ jsx13(ColScore, { children: "Score" }),
1989
+ /* @__PURE__ */ jsx13(ColIssues, { children: "Top Issues" })
1990
+ ] }),
1991
+ filteredAndSortedDocs.map((doc) => {
1992
+ return /* @__PURE__ */ jsxs13(TableRow, { children: [
1993
+ /* @__PURE__ */ jsx13(ColTitle, { children: /* @__PURE__ */ jsx13(TitleWrapper, { children: /* @__PURE__ */ jsxs13(TitleCell, { children: [
1994
+ doc.title !== null && typeof doc.title !== "string" ? /* @__PURE__ */ jsx13(NonStringTitleWarning, { title: "title is not a string \u2014 use pt::text(title) in your query.groq projection to convert Portable Text to a plain string", children: "\u26A0 title is not a string \u2014 use pt::text(title) in query.groq" }) : /* @__PURE__ */ jsx13(Fragment, { children: openInPane ? /* @__PURE__ */ jsx13(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled" }) : /* @__PURE__ */ jsx13(
1995
+ DocTitleAnchor,
1996
+ {
1997
+ id: doc._id,
1998
+ type: doc._type,
1999
+ structureTool,
2000
+ children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled"
2001
+ }
2002
+ ) }),
2003
+ showDocumentId && /* @__PURE__ */ jsx13(DocId, { children: doc._id }),
2004
+ resolvedDocBadge && /* @__PURE__ */ jsx13(
2005
+ DocBadgeRenderer,
2006
+ {
2007
+ doc,
2008
+ docBadge: resolvedDocBadge
2009
+ }
2010
+ )
2011
+ ] }) }) }),
2012
+ showTypeColumn && /* @__PURE__ */ jsx13(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsx13(TypeText, { children: resolveTypeLabel(doc._type, resolvedTypeLabels) }) : (() => {
2013
+ const typeColor = getTypeColor(doc._type);
2014
+ return /* @__PURE__ */ jsx13(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, resolvedTypeLabels) });
2015
+ })() }),
2016
+ /* @__PURE__ */ jsx13(ColScore, { children: /* @__PURE__ */ jsxs13(ScoreBadge, { $score: doc.health.score, children: [
2017
+ doc.health.score,
2018
+ "%"
2019
+ ] }) }),
2020
+ /* @__PURE__ */ jsxs13(ColIssues, { children: [
2021
+ doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ jsxs13(IssueTag, { children: [
2022
+ "\u2022 ",
2023
+ issue
2024
+ ] }, `issue-${doc._id}-${issue}`)),
2025
+ doc.health.issues.length > 2 && /* @__PURE__ */ jsx13(
2026
+ MoreIssuesWrapper,
2027
+ {
2028
+ onMouseEnter: function(e) {
2029
+ handleMouseEnterIssues(
2030
+ e.currentTarget,
2031
+ doc.health.issues.slice(2)
2032
+ );
2033
+ },
2034
+ onMouseLeave: handleMouseLeave,
2035
+ children: /* @__PURE__ */ jsxs13(MoreIssues, { children: [
2036
+ "+",
2037
+ doc.health.issues.length - 2,
2038
+ " more issues"
2039
+ ] })
2040
+ }
2041
+ )
2042
+ ] })
2043
+ ] }, doc._id);
2044
+ })
2045
+ ] }))
2046
+ ] }),
2047
+ activePopover && /* @__PURE__ */ jsx13(
2048
+ IssuesPopover,
2049
+ {
2050
+ style: {
2051
+ top: activePopover.top,
2052
+ left: activePopover.left,
2053
+ transform: "translateY(calc(-100% - 10px))"
2054
+ },
2055
+ children: activePopover.issues.map((issue) => /* @__PURE__ */ jsxs13(PopoverIssueItem, { children: [
2056
+ "\u26A0\uFE0F ",
2057
+ issue
2058
+ ] }, issue))
2059
+ }
2060
+ ),
2061
+ " "
2062
+ ] }),
2063
+ " "
2064
+ ] });
2065
+ };
2066
+ SeoHealthDashboard_default = SeoHealthDashboard;
1719
2067
  }
1720
- feedback.push({ text: "Twitter image URL is set \u2014 good for X sharing.", color: "green" });
1721
- const metaSet = isMetaImageSet(seoParent);
1722
- const ogSet = isSubImageSet(seoParent == null ? void 0 : seoParent.openGraph);
1723
- if (metaSet && ogSet) {
1724
- feedback.push({ text: "All images set (Meta, OG, Twitter) \u2014 full coverage!", color: "green" });
1725
- } else {
1726
- if (!metaSet)
1727
- feedback.push({
1728
- text: "Meta image is missing \u2014 add it for search engine results.",
1729
- color: "orange"
1730
- });
1731
- if (!ogSet)
1732
- feedback.push({
1733
- text: "OG image is missing \u2014 add it for Facebook/LinkedIn sharing.",
1734
- color: "orange"
1735
- });
2068
+ });
2069
+
2070
+ // src/components/SeoHealthTool.tsx
2071
+ var SeoHealthTool_exports = {};
2072
+ __export(SeoHealthTool_exports, {
2073
+ default: () => SeoHealthTool_default
2074
+ });
2075
+ import { jsx as jsx14 } from "react/jsx-runtime";
2076
+ var SeoHealthTool, SeoHealthTool_default;
2077
+ var init_SeoHealthTool = __esm({
2078
+ "src/components/SeoHealthTool.tsx"() {
2079
+ "use strict";
2080
+ init_SeoHealthDashboard();
2081
+ SeoHealthTool = (props) => {
2082
+ return /* @__PURE__ */ jsx14(SeoHealthDashboard_default, __spreadValues({}, props));
2083
+ };
2084
+ SeoHealthTool_default = SeoHealthTool;
1736
2085
  }
1737
- return feedback;
1738
- };
2086
+ });
2087
+
2088
+ // src/plugin.ts
2089
+ import React14 from "react";
2090
+ import { definePlugin } from "sanity";
2091
+
2092
+ // src/schemas/index.ts
2093
+ import React12 from "react";
2094
+ import {
2095
+ defineField as defineField3,
2096
+ defineType as defineType3
2097
+ } from "sanity";
1739
2098
 
1740
2099
  // src/components/meta/MetaDescription.tsx
1741
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2100
+ init_seoUtils();
2101
+ import { Stack, Text } from "@sanity/ui";
2102
+ import { useMemo } from "react";
2103
+ import { useFormValue } from "sanity";
2104
+ import { jsx, jsxs } from "react/jsx-runtime";
1742
2105
  var MetaDescription = (props) => {
1743
2106
  const { value, renderDefault, path } = props;
1744
2107
  const parent = useFormValue([path[0]]);
1745
2108
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1746
- const keywords = useMemo2(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1747
- const feedbackItems = useMemo2(
2109
+ const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2110
+ const feedbackItems = useMemo(
1748
2111
  () => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
1749
2112
  [value, keywords, isParentseoField]
1750
2113
  );
1751
- return /* @__PURE__ */ jsxs2(Stack, { space: 3, children: [
2114
+ return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1752
2115
  renderDefault(props),
1753
- /* @__PURE__ */ jsx3(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1754
- /* @__PURE__ */ jsx3(
2116
+ /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2117
+ /* @__PURE__ */ jsx(
1755
2118
  "div",
1756
2119
  {
1757
2120
  style: { width: 10, height: 10, borderRadius: "50%", backgroundColor: item.color }
1758
2121
  }
1759
2122
  ),
1760
- /* @__PURE__ */ jsx3(Text, { weight: "bold", muted: true, size: 14, children: item.text })
2123
+ /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1761
2124
  ] }, item.text)) })
1762
2125
  ] });
1763
2126
  };
1764
2127
  var MetaDescription_default = MetaDescription;
1765
2128
 
1766
2129
  // src/components/meta/MetaImage.tsx
2130
+ init_seoUtils();
1767
2131
  import { Stack as Stack2, Text as Text2 } from "@sanity/ui";
1768
- import { useMemo as useMemo3 } from "react";
2132
+ import { useMemo as useMemo2 } from "react";
1769
2133
  import { useFormValue as useFormValue2 } from "sanity";
1770
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2134
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1771
2135
  var MetaImage = (props) => {
1772
2136
  const { value, renderDefault, path } = props;
1773
2137
  const seoParent = useFormValue2([path[0]]);
1774
2138
  const hasImage = !!(value == null ? void 0 : value.asset);
1775
- const feedbackItems = useMemo3(
2139
+ const feedbackItems = useMemo2(
1776
2140
  () => getMetaImageValidation(hasImage, seoParent),
1777
2141
  [hasImage, seoParent]
1778
2142
  );
1779
- return /* @__PURE__ */ jsxs3(Stack2, { space: 3, children: [
2143
+ return /* @__PURE__ */ jsxs2(Stack2, { space: 3, children: [
1780
2144
  renderDefault(props),
1781
- /* @__PURE__ */ jsx4(Stack2, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1782
- /* @__PURE__ */ jsx4(
2145
+ /* @__PURE__ */ jsx2(Stack2, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2146
+ /* @__PURE__ */ jsx2(
1783
2147
  "div",
1784
2148
  {
1785
2149
  style: {
@@ -1790,30 +2154,31 @@ var MetaImage = (props) => {
1790
2154
  }
1791
2155
  }
1792
2156
  ),
1793
- /* @__PURE__ */ jsx4(Text2, { weight: "bold", muted: true, size: 14, children: item.text })
2157
+ /* @__PURE__ */ jsx2(Text2, { weight: "bold", muted: true, size: 14, children: item.text })
1794
2158
  ] }, item.text)) })
1795
2159
  ] });
1796
2160
  };
1797
2161
  var MetaImage_default = MetaImage;
1798
2162
 
1799
2163
  // src/components/meta/MetaTitle.tsx
2164
+ init_seoUtils();
1800
2165
  import { Stack as Stack3, Text as Text3 } from "@sanity/ui";
1801
- import { useMemo as useMemo4 } from "react";
2166
+ import { useMemo as useMemo3 } from "react";
1802
2167
  import { useFormValue as useFormValue3 } from "sanity";
1803
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2168
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1804
2169
  var MetaTitle = (props) => {
1805
2170
  const { value, renderDefault, path } = props;
1806
2171
  const parent = useFormValue3([path[0]]);
1807
2172
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1808
- const keywords = useMemo4(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1809
- const feedbackItems = useMemo4(
2173
+ const keywords = useMemo3(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2174
+ const feedbackItems = useMemo3(
1810
2175
  () => getMetaTitleValidationMessages(value || "", keywords, isParentseoField),
1811
2176
  [value, keywords, isParentseoField]
1812
2177
  );
1813
- return /* @__PURE__ */ jsxs4(Stack3, { space: 3, children: [
2178
+ return /* @__PURE__ */ jsxs3(Stack3, { space: 3, children: [
1814
2179
  renderDefault(props),
1815
- /* @__PURE__ */ jsx5(Stack3, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1816
- /* @__PURE__ */ jsx5(
2180
+ /* @__PURE__ */ jsx3(Stack3, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2181
+ /* @__PURE__ */ jsx3(
1817
2182
  "div",
1818
2183
  {
1819
2184
  style: {
@@ -1824,155 +2189,12 @@ var MetaTitle = (props) => {
1824
2189
  }
1825
2190
  }
1826
2191
  ),
1827
- /* @__PURE__ */ jsx5(Text3, { weight: "bold", muted: true, size: 14, children: item.text })
2192
+ /* @__PURE__ */ jsx3(Text3, { weight: "bold", muted: true, size: 14, children: item.text })
1828
2193
  ] }, item.text)) })
1829
2194
  ] });
1830
2195
  };
1831
2196
  var MetaTitle_default = MetaTitle;
1832
2197
 
1833
- // src/components/SeoPreview.tsx
1834
- import { Box } from "@sanity/ui";
1835
- import { useFormValue as useFormValue4 } from "sanity";
1836
- import styled2 from "styled-components";
1837
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1838
- var PreviewContainer = styled2.div`
1839
- max-width: 600px;
1840
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1841
- background: #ffffff;
1842
- border: 1px solid #dadce0;
1843
- border-radius: 8px;
1844
- overflow: hidden;
1845
- `;
1846
- var PreviewHeader = styled2.div`
1847
- background: #f8f9fa;
1848
- padding: 12px 16px;
1849
- border-bottom: 1px solid #dadce0;
1850
- display: flex;
1851
- align-items: center;
1852
- justify-content: space-between;
1853
- gap: 8px;
1854
- `;
1855
- var PreviewBody = styled2.div`
1856
- padding: 16px;
1857
- `;
1858
- var SerpUrl = styled2.p`
1859
- margin: 0 0 4px;
1860
- color: #006621;
1861
- font-size: 13px;
1862
- line-height: 1.4;
1863
- word-break: break-word;
1864
- `;
1865
- var SerpTitle = styled2.h3`
1866
- margin: 0 0 8px;
1867
- color: #1a0dab;
1868
- font-size: 18px;
1869
- font-weight: 500;
1870
- line-height: 1.4;
1871
- word-break: break-word;
1872
-
1873
- &:hover {
1874
- text-decoration: underline;
1875
- }
1876
- `;
1877
- var SerpDescription = styled2.p`
1878
- margin: 0;
1879
- color: #545454;
1880
- font-size: 14px;
1881
- line-height: 1.6;
1882
- word-break: break-word;
1883
- display: -webkit-box;
1884
- -webkit-line-clamp: 2;
1885
- -webkit-box-orient: vertical;
1886
- overflow: hidden;
1887
- `;
1888
- var LiveIndicator = styled2.span`
1889
- display: inline-flex;
1890
- align-items: center;
1891
- gap: 4px;
1892
- font-size: 11px;
1893
- font-weight: 600;
1894
- text-transform: uppercase;
1895
- letter-spacing: 0.05em;
1896
- color: #4f46e5;
1897
- background: #f0f4ff;
1898
- padding: 4px 8px;
1899
- border-radius: 4px;
1900
- `;
1901
- var SeoPreview = (props) => {
1902
- var _a, _b;
1903
- const { path, schemaType } = props;
1904
- const { options } = schemaType;
1905
- const baseUrl = (options == null ? void 0 : options.baseUrl) || "https://www.example.com";
1906
- const prefixFunction = options == null ? void 0 : options.prefix;
1907
- const parent = useFormValue4([path[0]]) || {
1908
- title: "",
1909
- description: "",
1910
- canonicalUrl: ""
1911
- };
1912
- const rootDoc = useFormValue4([]) || {
1913
- slug: { current: "" }
1914
- };
1915
- const slug = ((_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current) || "";
1916
- const {
1917
- title,
1918
- description,
1919
- canonicalUrl: url
1920
- } = parent;
1921
- const base = (_b = url || baseUrl) == null ? void 0 : _b.replace(/\/+$/, "");
1922
- const slugStr = String(slug || "").replace(/^\/+/, "");
1923
- const pref = String(
1924
- prefixFunction ? prefixFunction(rootDoc) : ""
1925
- ).replace(/^\/+|\/+$/g, "");
1926
- const urlPath = [pref, slugStr].filter(Boolean).join("/");
1927
- const finalUrl = urlPath ? `${base}/${urlPath}` : base;
1928
- const domain = (() => {
1929
- try {
1930
- const u = new URL(finalUrl || base);
1931
- return u.hostname;
1932
- } catch (e) {
1933
- return "example.com";
1934
- }
1935
- })();
1936
- const urlDisplay = `${domain}${urlPath ? ` \u203A ${urlPath.split("/").slice(-1)[0]}` : ""}`;
1937
- return /* @__PURE__ */ jsx6(Box, { padding: 3, children: /* @__PURE__ */ jsxs5(PreviewContainer, { children: [
1938
- /* @__PURE__ */ jsxs5(PreviewHeader, { children: [
1939
- /* @__PURE__ */ jsx6(
1940
- "span",
1941
- {
1942
- style: {
1943
- fontSize: "11px",
1944
- color: "#5f6368",
1945
- textTransform: "uppercase",
1946
- letterSpacing: "0.05em"
1947
- },
1948
- children: "Search Preview"
1949
- }
1950
- ),
1951
- /* @__PURE__ */ jsxs5(LiveIndicator, { children: [
1952
- /* @__PURE__ */ jsx6(
1953
- "span",
1954
- {
1955
- style: {
1956
- width: "4px",
1957
- height: "4px",
1958
- borderRadius: "50%",
1959
- backgroundColor: "#4f46e5",
1960
- display: "inline-block"
1961
- }
1962
- }
1963
- ),
1964
- "Live"
1965
- ] })
1966
- ] }),
1967
- /* @__PURE__ */ jsxs5(PreviewBody, { children: [
1968
- /* @__PURE__ */ jsx6(SerpUrl, { children: finalUrl ? urlDisplay : "example.com \u203A page-url" }),
1969
- /* @__PURE__ */ jsx6(SerpTitle, { children: title && title.length > 0 ? truncate(title, 60) : "Your SEO Title will appear here" }),
1970
- /* @__PURE__ */ jsx6(SerpDescription, { children: description && description.length > 0 ? truncate(description, 160) : "Your meta description will show up here. Make it compelling!" })
1971
- ] })
1972
- ] }) });
1973
- };
1974
- var SeoPreview_default = SeoPreview;
1975
-
1976
2198
  // src/utils/fieldsUtils.ts
1977
2199
  var DEFAULT_FIELD_INFO = {
1978
2200
  title: {
@@ -2100,23 +2322,24 @@ var isEmpty = (value) => {
2100
2322
  import { defineField, defineType } from "sanity";
2101
2323
 
2102
2324
  // src/components/openGraph/OgDescription.tsx
2103
- import { Stack as Stack5, Text as Text5 } from "@sanity/ui";
2104
- import { useMemo as useMemo5 } from "react";
2105
- import { useFormValue as useFormValue5 } from "sanity";
2106
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2325
+ init_seoUtils();
2326
+ import { Stack as Stack4, Text as Text4 } from "@sanity/ui";
2327
+ import { useMemo as useMemo4 } from "react";
2328
+ import { useFormValue as useFormValue4 } from "sanity";
2329
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2107
2330
  var OgDescription = (props) => {
2108
2331
  const { value, renderDefault, path } = props;
2109
- const parent = useFormValue5([path[0]]);
2332
+ const parent = useFormValue4([path[0]]);
2110
2333
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
2111
- const keywords = useMemo5(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2112
- const feedbackItems = useMemo5(
2334
+ const keywords = useMemo4(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2335
+ const feedbackItems = useMemo4(
2113
2336
  () => getOgDescriptionValidation(value || "", keywords, isParentseoField),
2114
2337
  [value, keywords, isParentseoField]
2115
2338
  );
2116
- return /* @__PURE__ */ jsxs6(Stack5, { space: 3, children: [
2339
+ return /* @__PURE__ */ jsxs4(Stack4, { space: 3, children: [
2117
2340
  renderDefault(props),
2118
- /* @__PURE__ */ jsx7(Stack5, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2119
- /* @__PURE__ */ jsx7(
2341
+ /* @__PURE__ */ jsx4(Stack4, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2342
+ /* @__PURE__ */ jsx4(
2120
2343
  "div",
2121
2344
  {
2122
2345
  style: {
@@ -2127,31 +2350,32 @@ var OgDescription = (props) => {
2127
2350
  }
2128
2351
  }
2129
2352
  ),
2130
- /* @__PURE__ */ jsx7(Text5, { weight: "bold", muted: true, size: 14, children: item.text })
2353
+ /* @__PURE__ */ jsx4(Text4, { weight: "bold", muted: true, size: 14, children: item.text })
2131
2354
  ] }, item.text)) })
2132
2355
  ] });
2133
2356
  };
2134
2357
  var OgDescription_default = OgDescription;
2135
2358
 
2136
2359
  // src/components/openGraph/OgImage.tsx
2137
- import { Stack as Stack6, Text as Text6 } from "@sanity/ui";
2138
- import { useMemo as useMemo6 } from "react";
2139
- import { useFormValue as useFormValue6 } from "sanity";
2140
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2360
+ init_seoUtils();
2361
+ import { Stack as Stack5, Text as Text5 } from "@sanity/ui";
2362
+ import { useMemo as useMemo5 } from "react";
2363
+ import { useFormValue as useFormValue5 } from "sanity";
2364
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2141
2365
  var OgImage = (props) => {
2142
2366
  const { value, renderDefault, path } = props;
2143
- const seoParent = useFormValue6([path[0]]);
2367
+ const seoParent = useFormValue5([path[0]]);
2144
2368
  const imgValue = value;
2145
2369
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
2146
2370
  const altText = imgValue == null ? void 0 : imgValue.alt;
2147
- const feedbackItems = useMemo6(
2371
+ const feedbackItems = useMemo5(
2148
2372
  () => getOgImageValidation(hasImage, altText, seoParent),
2149
2373
  [hasImage, altText, seoParent]
2150
2374
  );
2151
- return /* @__PURE__ */ jsxs7(Stack6, { space: 3, children: [
2375
+ return /* @__PURE__ */ jsxs5(Stack5, { space: 3, children: [
2152
2376
  renderDefault(props),
2153
- /* @__PURE__ */ jsx8(Stack6, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2154
- /* @__PURE__ */ jsx8(
2377
+ /* @__PURE__ */ jsx5(Stack5, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2378
+ /* @__PURE__ */ jsx5(
2155
2379
  "div",
2156
2380
  {
2157
2381
  style: {
@@ -2162,25 +2386,26 @@ var OgImage = (props) => {
2162
2386
  }
2163
2387
  }
2164
2388
  ),
2165
- /* @__PURE__ */ jsx8(Text6, { weight: "bold", muted: true, size: 14, children: item.text })
2389
+ /* @__PURE__ */ jsx5(Text5, { weight: "bold", muted: true, size: 14, children: item.text })
2166
2390
  ] }, item.text)) })
2167
2391
  ] });
2168
2392
  };
2169
2393
  var OgImage_default = OgImage;
2170
2394
 
2171
2395
  // src/components/openGraph/OgImageUrl.tsx
2172
- import { Stack as Stack7, Text as Text7 } from "@sanity/ui";
2173
- import { useMemo as useMemo7 } from "react";
2174
- import { useFormValue as useFormValue7 } from "sanity";
2175
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2396
+ init_seoUtils();
2397
+ import { Stack as Stack6, Text as Text6 } from "@sanity/ui";
2398
+ import { useMemo as useMemo6 } from "react";
2399
+ import { useFormValue as useFormValue6 } from "sanity";
2400
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2176
2401
  var OgImageUrl = (props) => {
2177
2402
  const { value, renderDefault, path } = props;
2178
- const seoParent = useFormValue7([path[0]]);
2179
- const feedbackItems = useMemo7(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
2180
- return /* @__PURE__ */ jsxs8(Stack7, { space: 3, children: [
2403
+ const seoParent = useFormValue6([path[0]]);
2404
+ const feedbackItems = useMemo6(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
2405
+ return /* @__PURE__ */ jsxs6(Stack6, { space: 3, children: [
2181
2406
  renderDefault(props),
2182
- /* @__PURE__ */ jsx9(Stack7, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2183
- /* @__PURE__ */ jsx9(
2407
+ /* @__PURE__ */ jsx6(Stack6, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2408
+ /* @__PURE__ */ jsx6(
2184
2409
  "div",
2185
2410
  {
2186
2411
  style: {
@@ -2191,30 +2416,31 @@ var OgImageUrl = (props) => {
2191
2416
  }
2192
2417
  }
2193
2418
  ),
2194
- /* @__PURE__ */ jsx9(Text7, { weight: "bold", muted: true, size: 14, children: item.text })
2419
+ /* @__PURE__ */ jsx6(Text6, { weight: "bold", muted: true, size: 14, children: item.text })
2195
2420
  ] }, item.text)) })
2196
2421
  ] });
2197
2422
  };
2198
2423
  var OgImageUrl_default = OgImageUrl;
2199
2424
 
2200
2425
  // src/components/openGraph/OgTitle.tsx
2201
- import { Stack as Stack8, Text as Text8 } from "@sanity/ui";
2202
- import { useMemo as useMemo8 } from "react";
2203
- import { useFormValue as useFormValue8 } from "sanity";
2204
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2426
+ init_seoUtils();
2427
+ import { Stack as Stack7, Text as Text7 } from "@sanity/ui";
2428
+ import { useMemo as useMemo7 } from "react";
2429
+ import { useFormValue as useFormValue7 } from "sanity";
2430
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2205
2431
  var OgTitle = (props) => {
2206
2432
  const { value, renderDefault, path } = props;
2207
- const parent = useFormValue8([path[0]]);
2433
+ const parent = useFormValue7([path[0]]);
2208
2434
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
2209
- const keywords = useMemo8(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2210
- const feedbackItems = useMemo8(
2435
+ const keywords = useMemo7(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2436
+ const feedbackItems = useMemo7(
2211
2437
  () => getOgTitleValidation(value || "", keywords, isParentseoField),
2212
2438
  [value, keywords, isParentseoField]
2213
2439
  );
2214
- return /* @__PURE__ */ jsxs9(Stack8, { space: 3, children: [
2440
+ return /* @__PURE__ */ jsxs7(Stack7, { space: 3, children: [
2215
2441
  renderDefault(props),
2216
- /* @__PURE__ */ jsx10(Stack8, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2217
- /* @__PURE__ */ jsx10(
2442
+ /* @__PURE__ */ jsx7(Stack7, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2443
+ /* @__PURE__ */ jsx7(
2218
2444
  "div",
2219
2445
  {
2220
2446
  style: {
@@ -2225,7 +2451,7 @@ var OgTitle = (props) => {
2225
2451
  }
2226
2452
  }
2227
2453
  ),
2228
- /* @__PURE__ */ jsx10(Text8, { weight: "bold", muted: true, size: 14, children: item.text })
2454
+ /* @__PURE__ */ jsx7(Text7, { weight: "bold", muted: true, size: 14, children: item.text })
2229
2455
  ] }, item.text)) })
2230
2456
  ] });
2231
2457
  };
@@ -2351,23 +2577,24 @@ function openGraph(config = {}) {
2351
2577
  import { defineField as defineField2, defineType as defineType2 } from "sanity";
2352
2578
 
2353
2579
  // src/components/twitter/twitterDescription.tsx
2354
- import { Stack as Stack9, Text as Text9 } from "@sanity/ui";
2355
- import { useMemo as useMemo9 } from "react";
2356
- import { useFormValue as useFormValue9 } from "sanity";
2357
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2580
+ init_seoUtils();
2581
+ import { Stack as Stack8, Text as Text8 } from "@sanity/ui";
2582
+ import { useMemo as useMemo8 } from "react";
2583
+ import { useFormValue as useFormValue8 } from "sanity";
2584
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2358
2585
  var TwitterDescription = (props) => {
2359
2586
  const { value, renderDefault, path } = props;
2360
- const parent = useFormValue9([path[0]]);
2587
+ const parent = useFormValue8([path[0]]);
2361
2588
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
2362
- const keywords = useMemo9(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2363
- const feedbackItems = useMemo9(
2589
+ const keywords = useMemo8(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2590
+ const feedbackItems = useMemo8(
2364
2591
  () => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
2365
2592
  [value, keywords, isParentseoField]
2366
2593
  );
2367
- return /* @__PURE__ */ jsxs10(Stack9, { space: 3, children: [
2594
+ return /* @__PURE__ */ jsxs8(Stack8, { space: 3, children: [
2368
2595
  renderDefault(props),
2369
- /* @__PURE__ */ jsx11(Stack9, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2370
- /* @__PURE__ */ jsx11(
2596
+ /* @__PURE__ */ jsx8(Stack8, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2597
+ /* @__PURE__ */ jsx8(
2371
2598
  "div",
2372
2599
  {
2373
2600
  style: {
@@ -2378,31 +2605,32 @@ var TwitterDescription = (props) => {
2378
2605
  }
2379
2606
  }
2380
2607
  ),
2381
- /* @__PURE__ */ jsx11(Text9, { weight: "bold", muted: true, size: 14, children: item.text })
2608
+ /* @__PURE__ */ jsx8(Text8, { weight: "bold", muted: true, size: 14, children: item.text })
2382
2609
  ] }, item.text)) })
2383
2610
  ] });
2384
2611
  };
2385
2612
  var twitterDescription_default = TwitterDescription;
2386
2613
 
2387
2614
  // src/components/twitter/TwitterImage.tsx
2388
- import { Stack as Stack10, Text as Text10 } from "@sanity/ui";
2389
- import { useMemo as useMemo10 } from "react";
2390
- import { useFormValue as useFormValue10 } from "sanity";
2391
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2615
+ init_seoUtils();
2616
+ import { Stack as Stack9, Text as Text9 } from "@sanity/ui";
2617
+ import { useMemo as useMemo9 } from "react";
2618
+ import { useFormValue as useFormValue9 } from "sanity";
2619
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2392
2620
  var TwitterImage = (props) => {
2393
2621
  const { value, renderDefault, path } = props;
2394
- const seoParent = useFormValue10([path[0]]);
2622
+ const seoParent = useFormValue9([path[0]]);
2395
2623
  const imgValue = value;
2396
2624
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
2397
2625
  const altText = imgValue == null ? void 0 : imgValue.alt;
2398
- const feedbackItems = useMemo10(
2626
+ const feedbackItems = useMemo9(
2399
2627
  () => getTwitterImageValidation(hasImage, altText, seoParent),
2400
2628
  [hasImage, altText, seoParent]
2401
2629
  );
2402
- return /* @__PURE__ */ jsxs11(Stack10, { space: 3, children: [
2630
+ return /* @__PURE__ */ jsxs9(Stack9, { space: 3, children: [
2403
2631
  renderDefault(props),
2404
- /* @__PURE__ */ jsx12(Stack10, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2405
- /* @__PURE__ */ jsx12(
2632
+ /* @__PURE__ */ jsx9(Stack9, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2633
+ /* @__PURE__ */ jsx9(
2406
2634
  "div",
2407
2635
  {
2408
2636
  style: {
@@ -2413,28 +2641,29 @@ var TwitterImage = (props) => {
2413
2641
  }
2414
2642
  }
2415
2643
  ),
2416
- /* @__PURE__ */ jsx12(Text10, { weight: "bold", muted: true, size: 14, children: item.text })
2644
+ /* @__PURE__ */ jsx9(Text9, { weight: "bold", muted: true, size: 14, children: item.text })
2417
2645
  ] }, item.text)) })
2418
2646
  ] });
2419
2647
  };
2420
2648
  var TwitterImage_default = TwitterImage;
2421
2649
 
2422
2650
  // src/components/twitter/TwitterImageUrl.tsx
2423
- import { Stack as Stack11, Text as Text11 } from "@sanity/ui";
2424
- import { useMemo as useMemo11 } from "react";
2425
- import { useFormValue as useFormValue11 } from "sanity";
2426
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2651
+ init_seoUtils();
2652
+ import { Stack as Stack10, Text as Text10 } from "@sanity/ui";
2653
+ import { useMemo as useMemo10 } from "react";
2654
+ import { useFormValue as useFormValue10 } from "sanity";
2655
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2427
2656
  var TwitterImageUrl = (props) => {
2428
2657
  const { value, renderDefault, path } = props;
2429
- const seoParent = useFormValue11([path[0]]);
2430
- const feedbackItems = useMemo11(
2658
+ const seoParent = useFormValue10([path[0]]);
2659
+ const feedbackItems = useMemo10(
2431
2660
  () => getTwitterImageUrlValidation(value, seoParent),
2432
2661
  [value, seoParent]
2433
2662
  );
2434
- return /* @__PURE__ */ jsxs12(Stack11, { space: 3, children: [
2663
+ return /* @__PURE__ */ jsxs10(Stack10, { space: 3, children: [
2435
2664
  renderDefault(props),
2436
- /* @__PURE__ */ jsx13(Stack11, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2437
- /* @__PURE__ */ jsx13(
2665
+ /* @__PURE__ */ jsx10(Stack10, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2666
+ /* @__PURE__ */ jsx10(
2438
2667
  "div",
2439
2668
  {
2440
2669
  style: {
@@ -2445,30 +2674,31 @@ var TwitterImageUrl = (props) => {
2445
2674
  }
2446
2675
  }
2447
2676
  ),
2448
- /* @__PURE__ */ jsx13(Text11, { weight: "bold", muted: true, size: 14, children: item.text })
2677
+ /* @__PURE__ */ jsx10(Text10, { weight: "bold", muted: true, size: 14, children: item.text })
2449
2678
  ] }, item.text)) })
2450
2679
  ] });
2451
2680
  };
2452
2681
  var TwitterImageUrl_default = TwitterImageUrl;
2453
2682
 
2454
2683
  // src/components/twitter/twitterTitle.tsx
2455
- import { Stack as Stack12, Text as Text12 } from "@sanity/ui";
2456
- import { useMemo as useMemo12 } from "react";
2457
- import { useFormValue as useFormValue12 } from "sanity";
2458
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2684
+ init_seoUtils();
2685
+ import { Stack as Stack11, Text as Text11 } from "@sanity/ui";
2686
+ import { useMemo as useMemo11 } from "react";
2687
+ import { useFormValue as useFormValue11 } from "sanity";
2688
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2459
2689
  var TwitterTitle = (props) => {
2460
2690
  const { value, renderDefault, path } = props;
2461
- const parent = useFormValue12([path[0]]);
2691
+ const parent = useFormValue11([path[0]]);
2462
2692
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
2463
- const keywords = useMemo12(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2464
- const feedbackItems = useMemo12(
2693
+ const keywords = useMemo11(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
2694
+ const feedbackItems = useMemo11(
2465
2695
  () => getTwitterTitleValidation(value || "", keywords, isParentseoField),
2466
2696
  [value, keywords, isParentseoField]
2467
2697
  );
2468
- return /* @__PURE__ */ jsxs13(Stack12, { space: 3, children: [
2698
+ return /* @__PURE__ */ jsxs11(Stack11, { space: 3, children: [
2469
2699
  renderDefault(props),
2470
- /* @__PURE__ */ jsx14(Stack12, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2471
- /* @__PURE__ */ jsx14(
2700
+ /* @__PURE__ */ jsx11(Stack11, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
2701
+ /* @__PURE__ */ jsx11(
2472
2702
  "div",
2473
2703
  {
2474
2704
  style: {
@@ -2479,7 +2709,7 @@ var TwitterTitle = (props) => {
2479
2709
  }
2480
2710
  }
2481
2711
  ),
2482
- /* @__PURE__ */ jsx14(Text12, { weight: "bold", muted: true, size: 14, children: item.text })
2712
+ /* @__PURE__ */ jsx11(Text11, { weight: "bold", muted: true, size: 14, children: item.text })
2483
2713
  ] }, item.text)) })
2484
2714
  ] });
2485
2715
  };
@@ -2596,6 +2826,8 @@ function twitter(config = {}) {
2596
2826
  }
2597
2827
 
2598
2828
  // src/schemas/index.ts
2829
+ var LazySeoPreview = React12.lazy(() => Promise.resolve().then(() => (init_SeoPreview(), SeoPreview_exports)));
2830
+ var SeoPreviewWrapper = (props) => React12.createElement(React12.Suspense, { fallback: null }, React12.createElement(LazySeoPreview, props));
2599
2831
  function seoFieldsSchema(config = {}) {
2600
2832
  return defineType3({
2601
2833
  name: "seoFields",
@@ -2615,7 +2847,7 @@ function seoFieldsSchema(config = {}) {
2615
2847
  name: "preview",
2616
2848
  title: "SEO Preview",
2617
2849
  type: "string",
2618
- components: { input: SeoPreview_default },
2850
+ components: { input: SeoPreviewWrapper },
2619
2851
  options: __spreadValues({
2620
2852
  baseUrl: config.baseUrl || "https://www.example.com"
2621
2853
  }, typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.prefix ? { prefix: config.seoPreview.prefix } : {}),
@@ -2819,58 +3051,135 @@ function types(config = {}) {
2819
3051
  }
2820
3052
 
2821
3053
  // src/plugin.ts
3054
+ var V132 = "v1.3.2";
3055
+ var CHANGELOG_V132 = `https://github.com/hardik-143/sanity-plugin-seofields/blob/main/CHANGELOG.md#132--2026-03-23`;
2822
3056
  var resolveDashboardConfig = (healthDashboard) => {
2823
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
3057
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
2824
3058
  const cfg = typeof healthDashboard === "object" ? healthDashboard : void 0;
3059
+ const deprecationWarnings = [];
3060
+ if (((_a = cfg == null ? void 0 : cfg.display) == null ? void 0 : _a.typeColumn) !== void 0) {
3061
+ deprecationWarnings.push({
3062
+ key: "display.typeColumn \u2192 showTypeColumn",
3063
+ version: V132,
3064
+ changelogUrl: CHANGELOG_V132
3065
+ });
3066
+ if (cfg.showTypeColumn) {
3067
+ console.warn(
3068
+ `[sanity-plugin-seofields] Both "healthDashboard.display.typeColumn" and "healthDashboard.showTypeColumn" are set. "showTypeColumn" will take precedence. Please remove "healthDashboard.display.typeColumn". See ${CHANGELOG_V132}`
3069
+ );
3070
+ } else {
3071
+ console.warn(
3072
+ `[sanity-plugin-seofields] "healthDashboard.display.typeColumn" is deprecated. Use "healthDashboard.showTypeColumn" instead. See ${CHANGELOG_V132}`
3073
+ );
3074
+ }
3075
+ }
3076
+ if (((_b = cfg == null ? void 0 : cfg.display) == null ? void 0 : _b.documentId) !== void 0) {
3077
+ deprecationWarnings.push({
3078
+ key: "display.documentId \u2192 showDocumentId",
3079
+ version: V132,
3080
+ changelogUrl: CHANGELOG_V132
3081
+ });
3082
+ if (cfg.showDocumentId) {
3083
+ console.warn(
3084
+ `[sanity-plugin-seofields] Both "healthDashboard.display.documentId" and "healthDashboard.showDocumentId" are set. "showDocumentId" will take precedence. Please remove "healthDashboard.display.documentId". See ${CHANGELOG_V132}`
3085
+ );
3086
+ } else {
3087
+ console.warn(
3088
+ `[sanity-plugin-seofields] "healthDashboard.display.documentId" is deprecated. Use "healthDashboard.showDocumentId" instead. See ${CHANGELOG_V132}`
3089
+ );
3090
+ }
3091
+ }
3092
+ if (cfg == null ? void 0 : cfg.typeLabels) {
3093
+ deprecationWarnings.push({
3094
+ key: "typeLabels \u2192 typeDisplayLabels",
3095
+ version: V132,
3096
+ changelogUrl: CHANGELOG_V132
3097
+ });
3098
+ if (cfg.typeDisplayLabels) {
3099
+ console.warn(
3100
+ `[sanity-plugin-seofields] Both "healthDashboard.typeLabels" and "healthDashboard.typeDisplayLabels" are set. "typeDisplayLabels" will take precedence. Please remove "typeLabels". See ${CHANGELOG_V132}`
3101
+ );
3102
+ } else {
3103
+ console.warn(
3104
+ `[sanity-plugin-seofields] "healthDashboard.typeLabels" is deprecated. Use "healthDashboard.typeDisplayLabels" instead. See ${CHANGELOG_V132}`
3105
+ );
3106
+ }
3107
+ }
3108
+ if (cfg == null ? void 0 : cfg.docBadge) {
3109
+ deprecationWarnings.push({
3110
+ key: "docBadge \u2192 getDocumentBadge",
3111
+ version: V132,
3112
+ changelogUrl: CHANGELOG_V132
3113
+ });
3114
+ if (cfg == null ? void 0 : cfg.getDocumentBadge) {
3115
+ console.warn(
3116
+ `[sanity-plugin-seofields] Both "healthDashboard.docBadge" and "healthDashboard.getDocumentBadge" are set. "getDocumentBadge" will take precedence. Please remove "docBadge". See ${CHANGELOG_V132}`
3117
+ );
3118
+ } else {
3119
+ console.warn(
3120
+ `[sanity-plugin-seofields] "healthDashboard.docBadge" is deprecated. Use "healthDashboard.getDocumentBadge" instead. See ${CHANGELOG_V132}`
3121
+ );
3122
+ }
3123
+ }
2825
3124
  return {
2826
3125
  enabled: healthDashboard !== false,
2827
- toolTitle: (_b = (_a = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _a.title) != null ? _b : "SEO Health",
2828
- toolName: (_d = (_c = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _c.name) != null ? _d : "seo-health-dashboard",
2829
- icon: (_e = cfg == null ? void 0 : cfg.content) == null ? void 0 : _e.icon,
2830
- title: (_f = cfg == null ? void 0 : cfg.content) == null ? void 0 : _f.title,
2831
- description: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.description,
2832
- showTypeColumn: (_h = cfg == null ? void 0 : cfg.display) == null ? void 0 : _h.typeColumn,
2833
- showDocumentId: (_i = cfg == null ? void 0 : cfg.display) == null ? void 0 : _i.documentId,
2834
- queryTypes: (_j = cfg == null ? void 0 : cfg.query) == null ? void 0 : _j.types,
2835
- queryRequireSeo: (_k = cfg == null ? void 0 : cfg.query) == null ? void 0 : _k.requireSeo,
2836
- queryGroq: (_l = cfg == null ? void 0 : cfg.query) == null ? void 0 : _l.groq,
3126
+ toolTitle: (_d = (_c = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _c.title) != null ? _d : "SEO Health",
3127
+ toolName: (_f = (_e = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _e.name) != null ? _f : "seo-health-dashboard",
3128
+ icon: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.icon,
3129
+ title: (_h = cfg == null ? void 0 : cfg.content) == null ? void 0 : _h.title,
3130
+ description: (_i = cfg == null ? void 0 : cfg.content) == null ? void 0 : _i.description,
3131
+ // New flat keys take precedence; fall back to deprecated display.* for backwards compat
3132
+ showTypeColumn: (_k = cfg == null ? void 0 : cfg.showTypeColumn) != null ? _k : (_j = cfg == null ? void 0 : cfg.display) == null ? void 0 : _j.typeColumn,
3133
+ showDocumentId: (_m = cfg == null ? void 0 : cfg.showDocumentId) != null ? _m : (_l = cfg == null ? void 0 : cfg.display) == null ? void 0 : _l.documentId,
3134
+ queryTypes: (_n = cfg == null ? void 0 : cfg.query) == null ? void 0 : _n.types,
3135
+ queryRequireSeo: (_o = cfg == null ? void 0 : cfg.query) == null ? void 0 : _o.requireSeo,
3136
+ queryGroq: (_p = cfg == null ? void 0 : cfg.query) == null ? void 0 : _p.groq,
2837
3137
  apiVersion: cfg == null ? void 0 : cfg.apiVersion,
2838
3138
  licenseKey: cfg == null ? void 0 : cfg.licenseKey,
2839
- typeLabels: cfg == null ? void 0 : cfg.typeLabels,
3139
+ // New key takes precedence; fall back to deprecated key for backwards compat
3140
+ typeDisplayLabels: (_q = cfg == null ? void 0 : cfg.typeDisplayLabels) != null ? _q : cfg == null ? void 0 : cfg.typeLabels,
2840
3141
  typeColumnMode: cfg == null ? void 0 : cfg.typeColumnMode,
2841
3142
  titleField: cfg == null ? void 0 : cfg.titleField,
2842
- docBadge: cfg == null ? void 0 : cfg.docBadge,
2843
- loadingLicense: (_m = cfg == null ? void 0 : cfg.content) == null ? void 0 : _m.loadingLicense,
2844
- loadingDocuments: (_n = cfg == null ? void 0 : cfg.content) == null ? void 0 : _n.loadingDocuments,
2845
- noDocuments: (_o = cfg == null ? void 0 : cfg.content) == null ? void 0 : _o.noDocuments,
3143
+ // New key takes precedence; fall back to deprecated key for backwards compat
3144
+ getDocumentBadge: (_r = cfg == null ? void 0 : cfg.getDocumentBadge) != null ? _r : cfg == null ? void 0 : cfg.docBadge,
3145
+ loadingLicense: (_s = cfg == null ? void 0 : cfg.content) == null ? void 0 : _s.loadingLicense,
3146
+ loadingDocuments: (_t = cfg == null ? void 0 : cfg.content) == null ? void 0 : _t.loadingDocuments,
3147
+ noDocuments: (_u = cfg == null ? void 0 : cfg.content) == null ? void 0 : _u.noDocuments,
2846
3148
  previewMode: cfg == null ? void 0 : cfg.previewMode,
2847
- structureTool: cfg == null ? void 0 : cfg.structureTool
3149
+ structureTool: cfg == null ? void 0 : cfg.structureTool,
3150
+ deprecationWarnings
2848
3151
  };
2849
3152
  };
2850
3153
  var seofields = definePlugin((config = {}) => {
2851
3154
  const { healthDashboard = true } = config;
2852
3155
  const dash = resolveDashboardConfig(healthDashboard);
2853
- const BoundSeoHealthTool = () => React13.createElement(SeoHealthTool_default, {
2854
- icon: dash.icon,
2855
- title: dash.title,
2856
- description: dash.description,
2857
- showTypeColumn: dash.showTypeColumn,
2858
- showDocumentId: dash.showDocumentId,
2859
- queryTypes: dash.queryTypes,
2860
- queryRequireSeo: dash.queryRequireSeo,
2861
- customQuery: dash.queryGroq,
2862
- apiVersion: dash.apiVersion,
2863
- licenseKey: dash.licenseKey,
2864
- typeLabels: dash.typeLabels,
2865
- typeColumnMode: dash.typeColumnMode,
2866
- titleField: dash.titleField,
2867
- docBadge: dash.docBadge,
2868
- loadingLicense: dash.loadingLicense,
2869
- loadingDocuments: dash.loadingDocuments,
2870
- noDocuments: dash.noDocuments,
2871
- previewMode: dash.previewMode,
2872
- structureTool: dash.structureTool
2873
- });
3156
+ const LazySeoHealthTool = React14.lazy(() => Promise.resolve().then(() => (init_SeoHealthTool(), SeoHealthTool_exports)));
3157
+ const BoundSeoHealthTool = () => React14.createElement(
3158
+ React14.Suspense,
3159
+ { fallback: null },
3160
+ React14.createElement(LazySeoHealthTool, {
3161
+ icon: dash.icon,
3162
+ title: dash.title,
3163
+ description: dash.description,
3164
+ showTypeColumn: dash.showTypeColumn,
3165
+ showDocumentId: dash.showDocumentId,
3166
+ queryTypes: dash.queryTypes,
3167
+ queryRequireSeo: dash.queryRequireSeo,
3168
+ customQuery: dash.queryGroq,
3169
+ apiVersion: dash.apiVersion,
3170
+ licenseKey: dash.licenseKey,
3171
+ typeDisplayLabels: dash.typeDisplayLabels,
3172
+ typeColumnMode: dash.typeColumnMode,
3173
+ titleField: dash.titleField,
3174
+ getDocumentBadge: dash.getDocumentBadge,
3175
+ loadingLicense: dash.loadingLicense,
3176
+ loadingDocuments: dash.loadingDocuments,
3177
+ noDocuments: dash.noDocuments,
3178
+ previewMode: dash.previewMode,
3179
+ structureTool: dash.structureTool,
3180
+ _deprecationWarnings: dash.deprecationWarnings
3181
+ })
3182
+ );
2874
3183
  return __spreadValues({
2875
3184
  name: "sanity-plugin-seofields",
2876
3185
  schema: {
@@ -2890,11 +3199,20 @@ var seofields = definePlugin((config = {}) => {
2890
3199
  var plugin_default = seofields;
2891
3200
 
2892
3201
  // src/components/SeoHealthPane.tsx
3202
+ import React15 from "react";
2893
3203
  import { jsx as jsx15 } from "react/jsx-runtime";
3204
+ var LazySeoHealthDashboard = React15.lazy(() => Promise.resolve().then(() => (init_SeoHealthDashboard(), SeoHealthDashboard_exports)));
2894
3205
  function createSeoHealthPane(optionsOrS, optionsWhenS) {
2895
3206
  const S = optionsOrS;
2896
3207
  const _a = optionsWhenS != null ? optionsWhenS : {}, { query, openInPane = true, title: paneTitle } = _a, rest = __objRest(_a, ["query", "openInPane", "title"]);
2897
- const SeoHealthPane = () => /* @__PURE__ */ jsx15(SeoHealthDashboard_default, __spreadValues({ customQuery: query, openInPane, title: paneTitle }, rest));
3208
+ const SeoHealthPane = () => /* @__PURE__ */ jsx15(React15.Suspense, { fallback: null, children: /* @__PURE__ */ jsx15(
3209
+ LazySeoHealthDashboard,
3210
+ __spreadValues({
3211
+ customQuery: query,
3212
+ openInPane,
3213
+ title: paneTitle
3214
+ }, rest)
3215
+ ) });
2898
3216
  SeoHealthPane.displayName = "SeoHealthPane";
2899
3217
  return S.component(SeoHealthPane).title(paneTitle != null ? paneTitle : "SEO Health").child((docId, { params }) => {
2900
3218
  const builder = S.document().documentId(docId);
@@ -2905,8 +3223,6 @@ function createSeoHealthPane(optionsOrS, optionsWhenS) {
2905
3223
  // src/index.ts
2906
3224
  var src_default = plugin_default;
2907
3225
  export {
2908
- SeoHealthDashboard_default as SeoHealthDashboard,
2909
- SeoHealthTool_default as SeoHealthTool,
2910
3226
  types as allSchemas,
2911
3227
  createSeoHealthPane,
2912
3228
  src_default as default,