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