sanity-plugin-seofields 1.5.0 → 1.5.2

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.
@@ -1,1400 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkS367Y35J_cjs = require('./chunk-S367Y35J.cjs');
4
- var react = require('react');
5
- var sanity = require('sanity');
6
- var router = require('sanity/router');
7
- var structure = require('sanity/structure');
8
- var styled = require('styled-components');
9
- var jsxRuntime = require('react/jsx-runtime');
10
-
11
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
-
13
- var styled__default = /*#__PURE__*/_interopDefault(styled);
14
-
15
- var DashboardContainer = styled__default.default.div`
16
- width: 100%;
17
- min-height: 100%;
18
- background: #f0f2f5;
19
- padding: 28px 32px;
20
- box-sizing: border-box;
21
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
22
- `;
23
- var PageHeader = styled__default.default.div`
24
- display: flex;
25
- align-items: flex-start;
26
- justify-content: space-between;
27
- gap: 12px;
28
- margin-bottom: 28px;
29
- `;
30
- var PageTitle = styled__default.default.h1`
31
- margin: 0 0 6px 0;
32
- font-size: 22px;
33
- font-weight: 700;
34
- color: #111827;
35
- letter-spacing: -0.3px;
36
- display: flex;
37
- align-items: center;
38
- gap: 10px;
39
- `;
40
- var PreviewBadge = styled__default.default.span`
41
- display: inline-block;
42
- background: #fef3c7;
43
- color: #92400e;
44
- font-size: 11px;
45
- font-weight: 600;
46
- padding: 4px 8px;
47
- border-radius: 4px;
48
- text-transform: uppercase;
49
- letter-spacing: 0.5px;
50
- margin-left: 8px;
51
- `;
52
- var PageSubtitle = styled__default.default.p`
53
- margin: 0;
54
- font-size: 13px;
55
- color: #6b7280;
56
- `;
57
- var StatsGrid = styled__default.default.div`
58
- display: grid;
59
- grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
60
- gap: 14px;
61
- margin-bottom: 20px;
62
- `;
63
- var StatCard = styled__default.default.div`
64
- background: #ffffff;
65
- border-radius: 10px;
66
- padding: 16px 18px;
67
- box-shadow:
68
- 0 1px 3px rgba(0, 0, 0, 0.07),
69
- 0 1px 2px rgba(0, 0, 0, 0.05);
70
- border-left: ${(p) => p.$accent ? `4px solid ${p.$accent}` : "4px solid transparent"};
71
- transition: box-shadow 0.15s ease;
72
-
73
- &:hover {
74
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
75
- }
76
- `;
77
- var StatLabel = styled__default.default.div`
78
- font-size: 11px;
79
- font-weight: 500;
80
- color: #9ca3af;
81
- text-transform: uppercase;
82
- letter-spacing: 0.5px;
83
- margin-bottom: 8px;
84
- `;
85
- var StatValue = styled__default.default.div`
86
- font-size: 26px;
87
- font-weight: 700;
88
- color: #111827;
89
- line-height: 1;
90
- `;
91
- var ControlsBar = styled__default.default.div`
92
- background: #ffffff;
93
- border-radius: 10px;
94
- padding: 14px 18px;
95
- display: flex;
96
- align-items: center;
97
- gap: 12px;
98
- flex-wrap: wrap;
99
- margin-bottom: 20px;
100
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
101
- `;
102
- var SearchWrapper = styled__default.default.div`
103
- position: relative;
104
- flex: 1;
105
- min-width: 220px;
106
- `;
107
- var SearchIconSvg = styled__default.default.span`
108
- position: absolute;
109
- left: 11px;
110
- top: 50%;
111
- transform: translateY(-50%);
112
- color: #9ca3af;
113
- display: flex;
114
- align-items: center;
115
- pointer-events: none;
116
- `;
117
- var SearchInput = styled__default.default.input`
118
- width: 100%;
119
- height: 36px;
120
- padding: 0 12px 0 34px;
121
- border: 1px solid #e5e7eb;
122
- border-radius: 7px;
123
- font-size: 13px;
124
- color: #111827;
125
- background: #f9fafb;
126
- box-sizing: border-box;
127
- outline: none;
128
- transition:
129
- border-color 0.15s,
130
- background 0.15s;
131
-
132
- &::placeholder {
133
- color: #9ca3af;
134
- }
135
-
136
- &:focus {
137
- border-color: #6366f1;
138
- background: #fff;
139
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
140
- }
141
- `;
142
- var StyledSelect = styled__default.default.select`
143
- height: 36px;
144
- padding: 0 32px 0 12px;
145
- border: 1px solid #e5e7eb;
146
- border-radius: 7px;
147
- font-size: 13px;
148
- color: #374151;
149
- background: #f9fafb
150
- url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
151
- no-repeat right 10px center;
152
- appearance: none;
153
- outline: none;
154
- cursor: pointer;
155
- transition: border-color 0.15s;
156
-
157
- &:focus {
158
- border-color: #6366f1;
159
- background-color: #fff;
160
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
161
- }
162
- `;
163
- var TableCard = styled__default.default.div`
164
- background: #ffffff;
165
- border-radius: 10px;
166
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
167
- overflow: hidden;
168
- `;
169
- var TableHeader = styled__default.default.div`
170
- display: flex;
171
- align-items: center;
172
- padding: 11px 20px;
173
- background: #f9fafb;
174
- border-bottom: 1px solid #e5e7eb;
175
- font-size: 11px;
176
- font-weight: 600;
177
- color: #6b7280;
178
- text-transform: uppercase;
179
- letter-spacing: 0.5px;
180
- gap: 12px;
181
- `;
182
- var TableRow = styled__default.default.div`
183
- display: flex;
184
- align-items: center;
185
- padding: 13px 20px;
186
- border-bottom: 1px solid #f3f4f6;
187
- gap: 12px;
188
- transition: background 0.1s;
189
-
190
- &:last-child {
191
- border-bottom: none;
192
- }
193
-
194
- &:hover {
195
- background: #fafafa;
196
- }
197
- `;
198
- var ColTitle = styled__default.default.div`
199
- flex: 2;
200
- min-width: 0;
201
- `;
202
- var TitleWrapper = styled__default.default.div`
203
- display: flex;
204
- align-items: center;
205
- gap: 4px;
206
- flex-wrap: wrap;
207
- min-width: 0;
208
- `;
209
- var TitleCell = styled__default.default.div`
210
- min-width: 0;
211
- overflow: hidden;
212
- flex: 1;
213
- `;
214
- var ColType = styled__default.default.div`
215
- flex: 0.8;
216
- min-width: 80px;
217
- `;
218
- var ColScore = styled__default.default.div`
219
- flex: 0.6;
220
- min-width: 70px;
221
- `;
222
- var ColIssues = styled__default.default.div`
223
- flex: 2;
224
- min-width: 0;
225
- `;
226
- var DocTitleLink = styled__default.default.a`
227
- font-size: 13px;
228
- font-weight: 600;
229
- color: #4f46e5;
230
- white-space: nowrap;
231
- overflow: hidden;
232
- text-overflow: ellipsis;
233
- text-decoration: none;
234
- display: block;
235
- transition: color 0.15s;
236
-
237
- &:hover {
238
- color: #4338ca;
239
- text-decoration: underline;
240
- }
241
- `;
242
- var DocId = styled__default.default.div`
243
- font-size: 11px;
244
- color: #9ca3af;
245
- margin-top: 2px;
246
- white-space: nowrap;
247
- overflow: hidden;
248
- text-overflow: ellipsis;
249
- `;
250
- var TypeBadge = styled__default.default.span`
251
- display: inline-block;
252
- padding: 3px 8px;
253
- border-radius: 5px;
254
- font-size: 11px;
255
- font-weight: 500;
256
- background: ${(p) => p.$bgColor || "#ede9fe"};
257
- color: ${(p) => p.$textColor || "#5b21b6"};
258
- `;
259
- var TypeText = styled__default.default.span`
260
- font-size: 12px;
261
- font-weight: 500;
262
- color: #374151;
263
- `;
264
- var CustomBadge = styled__default.default.span`
265
- display: inline-block;
266
- padding: 2px 6px;
267
- border-radius: 4px;
268
- font-size: ${(p) => p.$fontSize || "10px"};
269
- font-weight: 600;
270
- background: ${(p) => p.$bgColor || "#e0e7ff"};
271
- color: ${(p) => p.$textColor || "#3730a3"};
272
- white-space: nowrap;
273
- `;
274
- var ScoreBadge = styled__default.default.span`
275
- display: inline-block;
276
- padding: 4px 10px;
277
- border-radius: 6px;
278
- font-size: 12px;
279
- font-weight: 700;
280
- background: ${(p) => {
281
- if (p.$score >= 80) return "#d1fae5";
282
- if (p.$score >= 60) return "#fef3c7";
283
- if (p.$score >= 40) return "#ffedd5";
284
- return "#fee2e2";
285
- }};
286
- color: ${(p) => {
287
- if (p.$score >= 80) return "#065f46";
288
- if (p.$score >= 60) return "#92400e";
289
- if (p.$score >= 40) return "#9a3412";
290
- return "#991b1b";
291
- }};
292
- `;
293
- var IssueTag = styled__default.default.div`
294
- font-size: 11px;
295
- color: #ef4444;
296
- line-height: 1.5;
297
- white-space: nowrap;
298
- overflow: hidden;
299
- text-overflow: ellipsis;
300
- `;
301
- var NonStringTitleWarning = styled__default.default.div`
302
- display: inline-flex;
303
- align-items: center;
304
- gap: 4px;
305
- margin-top: 4px;
306
- padding: 2px 7px;
307
- border-radius: 4px;
308
- background: #fef3c7;
309
- border: 1px solid #fcd34d;
310
- font-size: 10px;
311
- font-weight: 600;
312
- color: #92400e;
313
- line-height: 1.4;
314
- cursor: default;
315
- white-space: normal;
316
- `;
317
- var MoreIssues = styled__default.default.div`
318
- font-size: 11px;
319
- color: #6b7280;
320
- cursor: pointer;
321
- transition: color 0.15s;
322
-
323
- &:hover {
324
- color: #374151;
325
- }
326
- `;
327
- var MoreIssuesWrapper = styled__default.default.div`
328
- position: relative;
329
- display: inline-block;
330
- `;
331
- var IssuesPopover = styled__default.default.div`
332
- position: fixed;
333
- bottom: auto;
334
- left: 0;
335
- transform: translateY(calc(-100% - 14px));
336
- background: #1f2937;
337
- color: #ffffff;
338
- padding: 12px;
339
- border-radius: 8px;
340
- font-size: 12px;
341
- z-index: 50;
342
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
343
- width: 280px;
344
- word-break: break-word;
345
- line-height: 1.5;
346
-
347
- &::after {
348
- content: '';
349
- position: absolute;
350
- bottom: -6px;
351
- left: 12px;
352
- width: 0;
353
- height: 0;
354
- border-left: 6px solid transparent;
355
- border-right: 6px solid transparent;
356
- border-top: 6px solid #1f2937;
357
- }
358
- `;
359
- var PopoverIssueItem = styled__default.default.div`
360
- display: flex;
361
- gap: 6px;
362
- margin-bottom: 6px;
363
-
364
- &:last-child {
365
- margin-bottom: 0;
366
- }
367
- `;
368
- var UpgradeContainer = styled__default.default.div`
369
- display: flex;
370
- align-items: center;
371
- justify-content: center;
372
- min-height: 100%;
373
- padding: 60px 24px;
374
- `;
375
- var UpgradeBox = styled__default.default.div`
376
- background: #ffffff;
377
- border-radius: 16px;
378
- padding: 48px 40px;
379
- max-width: 480px;
380
- width: 100%;
381
- text-align: center;
382
- box-shadow:
383
- 0 4px 24px rgba(0, 0, 0, 0.08),
384
- 0 1px 4px rgba(0, 0, 0, 0.05);
385
- border: 1px solid #e5e7eb;
386
- `;
387
- var UpgradeLock = styled__default.default.div`
388
- font-size: 40px;
389
- margin-bottom: 16px;
390
- `;
391
- var UpgradeTitle = styled__default.default.h2`
392
- margin: 0 0 10px;
393
- font-size: 20px;
394
- font-weight: 700;
395
- color: #111827;
396
- `;
397
- var UpgradeText = styled__default.default.p`
398
- margin: 0 0 20px;
399
- font-size: 14px;
400
- color: #6b7280;
401
- line-height: 1.6;
402
- `;
403
- var UpgradeCode = styled__default.default.pre`
404
- background: #f3f4f6;
405
- border-radius: 8px;
406
- padding: 14px 16px;
407
- font-size: 12px;
408
- color: #374151;
409
- text-align: left;
410
- margin: 0 0 24px;
411
- overflow-x: auto;
412
- line-height: 1.6;
413
- border: 1px solid #e5e7eb;
414
- `;
415
- var UpgradeButton = styled__default.default.a`
416
- display: inline-block;
417
- background: #4f46e5;
418
- color: #ffffff;
419
- font-size: 14px;
420
- font-weight: 600;
421
- padding: 10px 24px;
422
- border-radius: 8px;
423
- text-decoration: none;
424
- transition: background 0.15s;
425
-
426
- &:hover {
427
- background: #4338ca;
428
- }
429
- `;
430
- var ReloadButton = styled__default.default.button`
431
- display: inline-block;
432
- background: transparent;
433
- color: #6b7280;
434
- font-size: 13px;
435
- font-weight: 500;
436
- padding: 8px 20px;
437
- border-radius: 8px;
438
- border: 1px solid #d1d5db;
439
- cursor: pointer;
440
- margin-top: 10px;
441
- transition:
442
- background 0.15s,
443
- color 0.15s,
444
- border-color 0.15s;
445
-
446
- &:hover {
447
- background: #f3f4f6;
448
- color: #374151;
449
- border-color: #9ca3af;
450
- }
451
- `;
452
- var spin = styled.keyframes`
453
- to { transform: rotate(360deg); }
454
- `;
455
- var DashboardRefreshButton = styled__default.default.button`
456
- display: inline-flex;
457
- align-items: center;
458
- gap: 6px;
459
- background: #ffffff;
460
- color: #374151;
461
- font-size: 13px;
462
- font-weight: 500;
463
- padding: 8px 14px;
464
- border-radius: 8px;
465
- border: 1px solid #d1d5db;
466
- cursor: pointer;
467
- flex-shrink: 0;
468
- transition:
469
- background 0.15s,
470
- color 0.15s,
471
- border-color 0.15s,
472
- box-shadow 0.15s;
473
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
474
-
475
- svg {
476
- animation: ${(p) => p.$spinning ? styled.css`
477
- ${spin} 0.7s linear infinite
478
- ` : "none"};
479
- }
480
-
481
- &:hover {
482
- background: #f3f4f6;
483
- border-color: #9ca3af;
484
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
485
- }
486
-
487
- &:disabled {
488
- opacity: 0.6;
489
- cursor: not-allowed;
490
- }
491
- `;
492
- var DocTitleAnchor = ({ id, type, structureTool, children }) => {
493
- const { basePath } = sanity.useWorkspace();
494
- const { onClick: intentOnClick, href: intentHref } = router.useIntentLink({
495
- intent: "edit",
496
- params: { id, type }
497
- });
498
- const href = structureTool ? `${basePath}/${structureTool}/intent/edit/id=${id};type=${type}/` : intentHref;
499
- const onClick = structureTool ? void 0 : intentOnClick;
500
- return /* @__PURE__ */ jsxRuntime.jsx(DocTitleLink, { href, onClick, title: "Open document", children });
501
- };
502
- var PaneLinkWrapper = styled__default.default.span`
503
- display: block;
504
- min-width: 0;
505
- overflow: hidden;
506
-
507
- a {
508
- font-size: 13px;
509
- font-weight: 600;
510
- color: #4f46e5;
511
- white-space: nowrap;
512
- overflow: hidden;
513
- text-overflow: ellipsis;
514
- text-decoration: none;
515
- display: block;
516
- transition: color 0.15s;
517
-
518
- &:hover {
519
- color: #4338ca;
520
- text-decoration: underline;
521
- }
522
- }
523
- `;
524
- var DocTitleAnchorPane = ({
525
- id,
526
- type,
527
- children
528
- }) => {
529
- const { ChildLink } = structure.usePaneRouter();
530
- return /* @__PURE__ */ jsxRuntime.jsx(PaneLinkWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(ChildLink, { childId: id, childParameters: { type }, children }) });
531
- };
532
- var DocBadgeRenderer = ({ doc, docBadge }) => {
533
- const badge = docBadge(doc);
534
- if (!badge) return null;
535
- return /* @__PURE__ */ jsxRuntime.jsx(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label });
536
- };
537
- var Spinner = styled__default.default.div`
538
- width: 28px;
539
- height: 28px;
540
- border: 3px solid #e5e7eb;
541
- border-top-color: #6366f1;
542
- border-radius: 50%;
543
- animation: ${spin} 0.7s linear infinite;
544
- margin: 0 auto 12px;
545
- `;
546
- var LoadingState = styled__default.default.div`
547
- padding: 48px 24px;
548
- text-align: center;
549
- color: #6b7280;
550
- font-size: 13px;
551
- `;
552
- var EmptyState = styled__default.default.div`
553
- padding: 48px 24px;
554
- text-align: center;
555
- color: #9ca3af;
556
- font-size: 13px;
557
- `;
558
- var DeprecationBanner = styled__default.default.div`
559
- background: #fffbeb;
560
- border: 1px solid #fcd34d;
561
- border-radius: 8px;
562
- padding: 10px 14px;
563
- font-size: 12px;
564
- color: #78350f;
565
- margin-bottom: 16px;
566
- line-height: 1.6;
567
- `;
568
- var DeprecationBannerLink = styled__default.default.a`
569
- color: #92400e;
570
- font-weight: 600;
571
- text-decoration: underline;
572
- &:hover {
573
- color: #78350f;
574
- }
575
- `;
576
- var TYPE_COLOR_PALETTE = [
577
- { bg: "#dbeafe", text: "#0c4a6e" },
578
- // Blue
579
- { bg: "#dcfce7", text: "#14532d" },
580
- // Green
581
- { bg: "#fce7f3", text: "#500724" },
582
- // Pink
583
- { bg: "#fed7aa", text: "#7c2d12" },
584
- // Orange
585
- { bg: "#e9d5ff", text: "#581c87" },
586
- // Purple
587
- { bg: "#f3e8ff", text: "#3f0f5c" },
588
- // Deep Purple
589
- { bg: "#ccfbf1", text: "#134e4a" },
590
- // Teal
591
- { bg: "#ddd6fe", text: "#3730a3" },
592
- // Indigo
593
- { bg: "#fca5a5", text: "#7f1d1d" },
594
- // Red
595
- { bg: "#a7f3d0", text: "#065f46" },
596
- // Emerald
597
- { bg: "#fbbf24", text: "#78350f" },
598
- // Amber
599
- { bg: "#c4b5fd", text: "#3b0764" },
600
- // Violet
601
- { bg: "#f0fdf4", text: "#15803d" },
602
- // Light Green
603
- { bg: "#fef2f2", text: "#991b1b" },
604
- // Light Red
605
- { bg: "#f5f3ff", text: "#5b21b6" },
606
- // Light Purple
607
- { bg: "#fffbeb", text: "#92400e" }
608
- // Light Amber
609
- ];
610
- var getTypeColor = (type) => {
611
- let hash = 0;
612
- for (let i = 0; i < type.length; i += 1) {
613
- const char = type.charCodeAt(i);
614
- hash = Math.abs(hash * 31 + char);
615
- }
616
- const colorIndex = hash % TYPE_COLOR_PALETTE.length;
617
- return TYPE_COLOR_PALETTE[colorIndex];
618
- };
619
- var getStatusCategory = (score) => {
620
- if (score >= 80) return "excellent";
621
- if (score >= 60) return "good";
622
- if (score >= 40) return "fair";
623
- if (score > 0) return "poor";
624
- return "missing";
625
- };
626
- var scoreMetaTitle = (title) => {
627
- const issues = [];
628
- let score = 0;
629
- if (title && title.length >= 50 && title.length <= 60) {
630
- score = 15;
631
- } else if (title && title.length > 0) {
632
- score = 10;
633
- if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
634
- if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
635
- } else {
636
- issues.push("Missing meta title");
637
- }
638
- return { score, issues };
639
- };
640
- var scoreMetaDescription = (description) => {
641
- const issues = [];
642
- let score = 0;
643
- if (description && description.length >= 120 && description.length <= 160) {
644
- score = 15;
645
- } else if (description && description.length > 0) {
646
- score = 10;
647
- if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
648
- if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
649
- } else {
650
- issues.push("Missing meta description");
651
- }
652
- return { score, issues };
653
- };
654
- var scoreOpenGraph = (openGraph) => {
655
- const issues = [];
656
- let score = 0;
657
- if (openGraph) {
658
- if (openGraph.title) score += 6;
659
- else issues.push("Missing OG title");
660
- if (openGraph.description) score += 6;
661
- else issues.push("Missing OG description");
662
- if (openGraph.image) score += 6;
663
- else issues.push("Missing OG image");
664
- if (openGraph.type) score += 7;
665
- else issues.push("Missing OG type");
666
- } else {
667
- issues.push("Open Graph not configured");
668
- }
669
- return { score, issues };
670
- };
671
- var scoreTwitterCard = (twitter) => {
672
- const issues = [];
673
- let score = 0;
674
- if (twitter) {
675
- if (twitter.title) score += 5;
676
- else issues.push("Missing Twitter title");
677
- if (twitter.description) score += 5;
678
- else issues.push("Missing Twitter description");
679
- if (twitter.image) score += 5;
680
- else issues.push("Missing Twitter image");
681
- } else {
682
- issues.push("Twitter Card not configured");
683
- }
684
- return { score, issues };
685
- };
686
- var calculateHealthScore = (doc) => {
687
- if (!doc.seo) {
688
- return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
689
- }
690
- const { title, description, keywords, robots, canonicalUrl, openGraph, twitter } = doc.seo;
691
- let score = 0;
692
- const issues = [];
693
- const titleResult = scoreMetaTitle(title);
694
- score += titleResult.score;
695
- issues.push(...titleResult.issues);
696
- const descResult = scoreMetaDescription(description);
697
- score += descResult.score;
698
- issues.push(...descResult.issues);
699
- if (doc.seo.metaImage) score += 10;
700
- else issues.push("Missing meta image");
701
- if (keywords && keywords.length > 0) score += 10;
702
- else issues.push("No keywords defined");
703
- if (robots && !robots.noIndex) score += 5;
704
- else if (!robots) score += 5;
705
- if (canonicalUrl) score += 0;
706
- const ogResult = scoreOpenGraph(openGraph);
707
- score += ogResult.score;
708
- issues.push(...ogResult.issues);
709
- const twResult = scoreTwitterCard(twitter);
710
- score += twResult.score;
711
- issues.push(...twResult.issues);
712
- const hasMetaImage = !!doc.seo.metaImage;
713
- const hasOgImage = !!(openGraph && openGraph.image);
714
- const hasTwitterImage = !!(twitter && twitter.image);
715
- if (hasMetaImage && hasOgImage && hasTwitterImage) {
716
- score += 5;
717
- } else {
718
- const missingImages = [];
719
- if (!hasMetaImage) missingImages.push("meta image");
720
- if (!hasOgImage) missingImages.push("OG image");
721
- if (!hasTwitterImage) missingImages.push("Twitter image");
722
- issues.push(`Missing images for full score: ${missingImages.join(", ")}`);
723
- }
724
- const status = getStatusCategory(score);
725
- return { score, status, issues };
726
- };
727
- var resolveTypeLabel = (type, typeLabels) => {
728
- var _a;
729
- return (_a = typeLabels == null ? void 0 : typeLabels[type]) != null ? _a : type;
730
- };
731
- var buildTitleProjection = (titleField) => {
732
- if (!titleField || titleField === "title") return "title";
733
- if (typeof titleField === "string") return `"title": ${titleField}`;
734
- const cases = Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ");
735
- return `"title": select(${cases}, title)`;
736
- };
737
- var generateDummyData = () => {
738
- const dummyDocs = [
739
- {
740
- _id: "preview-post-1",
741
- _type: "post",
742
- title: "Getting Started with SEO Best Practices",
743
- slug: { current: "getting-started-seo" },
744
- _updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString(),
745
- seo: {
746
- title: "Getting Started with SEO Best Practices | My Blog",
747
- description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
748
- keywords: ["seo", "best practices", "optimization"],
749
- metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
750
- openGraph: {
751
- title: "SEO Best Practices Guide",
752
- description: "Master SEO optimization",
753
- image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
754
- type: "article"
755
- },
756
- twitter: {
757
- title: "SEO Best Practices",
758
- description: "Learn SEO optimization",
759
- image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
760
- card: "summary_large_image"
761
- }
762
- }
763
- },
764
- {
765
- _id: "preview-post-2",
766
- _type: "post",
767
- title: "Advanced Analytics Strategy",
768
- slug: { current: "advanced-analytics" },
769
- _updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1e3).toISOString(),
770
- seo: {
771
- title: "Advanced Analytics",
772
- description: "Strategy tips",
773
- keywords: ["analytics", "data"],
774
- openGraph: {
775
- title: "Analytics Guide"
776
- }
777
- }
778
- },
779
- {
780
- _id: "preview-page-1",
781
- _type: "page",
782
- title: "About Us",
783
- slug: { current: "about" },
784
- _updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1e3).toISOString(),
785
- seo: {
786
- title: "About",
787
- keywords: ["company", "team"],
788
- metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
789
- }
790
- },
791
- {
792
- _id: "preview-post-3",
793
- _type: "post",
794
- title: "Content Marketing Trends for 2024",
795
- slug: { current: "content-marketing-trends" },
796
- _updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1e3).toISOString(),
797
- seo: {
798
- title: "Content Marketing Trends 2024",
799
- description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
800
- keywords: ["content marketing", "trends", "strategy", "engagement"],
801
- metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
802
- openGraph: {
803
- title: "Content Marketing Trends 2024",
804
- description: "Latest trends in content marketing",
805
- image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
806
- type: "article"
807
- },
808
- twitter: {
809
- title: "Content Marketing Trends",
810
- description: "Discover the latest trends",
811
- card: "summary"
812
- }
813
- }
814
- },
815
- {
816
- _id: "preview-post-4",
817
- _type: "product",
818
- title: "Pro Plan",
819
- slug: { current: "pro-plan" },
820
- _updatedAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1e3).toISOString(),
821
- seo: {
822
- title: "Pro",
823
- keywords: ["pricing"]
824
- }
825
- },
826
- {
827
- _id: "preview-page-2",
828
- _type: "page",
829
- title: "Contact",
830
- slug: { current: "contact" },
831
- _updatedAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1e3).toISOString(),
832
- seo: {
833
- openGraph: {
834
- title: "Get in Touch"
835
- }
836
- }
837
- },
838
- {
839
- _id: "preview-post-5",
840
- _type: "post",
841
- title: "Mobile Optimization Guide",
842
- slug: { current: "mobile-optimization" },
843
- _updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1e3).toISOString(),
844
- seo: {
845
- title: "Mobile Optimization Guide: Best Practices for Responsive Design",
846
- description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
847
- keywords: ["mobile", "optimization", "responsive", "performance"],
848
- metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
849
- openGraph: {
850
- title: "Mobile Optimization Best Practices",
851
- description: "Master mobile web optimization",
852
- image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
853
- type: "article"
854
- },
855
- twitter: {
856
- title: "Mobile Optimization Tips",
857
- description: "Responsive design best practices",
858
- image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
859
- card: "summary_large_image"
860
- }
861
- }
862
- }
863
- ];
864
- return dummyDocs.map((doc) => chunkS367Y35J_cjs.__spreadProps(chunkS367Y35J_cjs.__spreadValues({}, doc), {
865
- health: calculateHealthScore(doc)
866
- }));
867
- };
868
- var SeoHealthDashboard = ({
869
- icon = "\u{1F4CA}",
870
- title = "SEO Health Dashboard",
871
- description = "Monitor and optimize SEO fields across all your documents",
872
- showTypeColumn = true,
873
- showDocumentId = true,
874
- queryTypes,
875
- queryRequireSeo = true,
876
- customQuery,
877
- apiVersion = "2023-01-01",
878
- licenseKey,
879
- typeDisplayLabels,
880
- typeColumnMode = "badge",
881
- titleField,
882
- getDocumentBadge,
883
- loadingLicense,
884
- loadingDocuments,
885
- noDocuments,
886
- previewMode = false,
887
- openInPane = false,
888
- structureTool,
889
- _deprecationWarnings
890
- }) => {
891
- const resolvedTypeLabels = typeDisplayLabels;
892
- const resolvedDocBadge = getDocumentBadge;
893
- const allDeprecationWarnings = react.useMemo(() => _deprecationWarnings != null ? _deprecationWarnings : [], [_deprecationWarnings]);
894
- const deprecationGroups = react.useMemo(() => {
895
- const groups = /* @__PURE__ */ new Map();
896
- for (const w of allDeprecationWarnings) {
897
- if (!groups.has(w.version)) {
898
- groups.set(w.version, { version: w.version, changelogUrl: w.changelogUrl, keys: [] });
899
- }
900
- groups.get(w.version).keys.push(w.key);
901
- }
902
- return Array.from(groups.values());
903
- }, [allDeprecationWarnings]);
904
- const client = sanity.useClient({ apiVersion });
905
- const [licenseStatus, setLicenseStatus] = react.useState("loading");
906
- const [documents, setDocuments] = react.useState([]);
907
- const [loading, setLoading] = react.useState(true);
908
- const [isRefreshing, setIsRefreshing] = react.useState(false);
909
- const [searchQuery, setSearchQuery] = react.useState("");
910
- const [filterStatus, setFilterStatus] = react.useState("all");
911
- const [filterType, setFilterType] = react.useState("all");
912
- const [sortBy, setSortBy] = react.useState("score");
913
- const [activePopover, setActivePopover] = react.useState(null);
914
- const VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
915
- const CACHE_TTL_MS = 60 * 60 * 1e3;
916
- const validateLicense = react.useCallback(
917
- async (forceRefresh = false) => {
918
- var _a;
919
- if (previewMode) {
920
- setLicenseStatus("valid");
921
- return;
922
- }
923
- if (!licenseKey) {
924
- setLicenseStatus("invalid");
925
- return;
926
- }
927
- const projectId = (_a = client.config().projectId) != null ? _a : "";
928
- const cacheKey = `seofields_license_${projectId}`;
929
- if (forceRefresh) {
930
- try {
931
- sessionStorage.removeItem(cacheKey);
932
- } catch (e) {
933
- }
934
- }
935
- if (!forceRefresh) {
936
- try {
937
- const cached = sessionStorage.getItem(cacheKey);
938
- if (cached) {
939
- const { valid, ts } = JSON.parse(cached);
940
- if (Date.now() - ts < CACHE_TTL_MS) {
941
- setLicenseStatus(valid ? "valid" : "invalid");
942
- return;
943
- }
944
- }
945
- } catch (e) {
946
- }
947
- }
948
- setLicenseStatus("loading");
949
- try {
950
- const res = await fetch(VALIDATION_ENDPOINT, {
951
- method: "POST",
952
- headers: { "Content-Type": "application/json" },
953
- body: JSON.stringify({ licenseKey, projectId })
954
- });
955
- const valid = res.ok;
956
- setLicenseStatus(valid ? "valid" : "invalid");
957
- try {
958
- sessionStorage.setItem(cacheKey, JSON.stringify({ valid, ts: Date.now() }));
959
- } catch (e) {
960
- }
961
- } catch (e) {
962
- setLicenseStatus("valid");
963
- }
964
- },
965
- // eslint-disable-next-line react-hooks/exhaustive-deps
966
- [licenseKey, previewMode]
967
- );
968
- react.useEffect(() => {
969
- validateLicense();
970
- }, [licenseKey, previewMode]);
971
- const handleMouseEnterIssues = (el, issues) => {
972
- if (!el) return;
973
- const rect = el.getBoundingClientRect();
974
- const popoverWidth = 280;
975
- const viewportWidth = window.innerWidth;
976
- let left = rect.left;
977
- if (left + popoverWidth > viewportWidth - 10) left = viewportWidth - popoverWidth - 10;
978
- if (left < 10) left = 10;
979
- setActivePopover({ top: rect.top, left, issues });
980
- };
981
- const JSONStringifiedQueryTypes = JSON.stringify(queryTypes);
982
- const JSONStringifiedTitleField = JSON.stringify(titleField);
983
- const fetchDocuments = react.useCallback(
984
- async (isManualRefresh = false) => {
985
- try {
986
- if (isManualRefresh) {
987
- setIsRefreshing(true);
988
- } else {
989
- setLoading(true);
990
- }
991
- if (previewMode) {
992
- setDocuments(generateDummyData());
993
- return;
994
- }
995
- let groqQuery;
996
- let params = {};
997
- if (customQuery) {
998
- groqQuery = customQuery;
999
- } else if (queryTypes && queryTypes.length > 0) {
1000
- const seoFilter = queryRequireSeo ? " && seo != null" : "";
1001
- const titleProj = buildTitleProjection(titleField);
1002
- groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
1003
- _id,
1004
- _type,
1005
- ${titleProj},
1006
- slug,
1007
- seo,
1008
- _updatedAt
1009
- }`;
1010
- params = { types: queryTypes };
1011
- } else {
1012
- const titleProj = buildTitleProjection(titleField);
1013
- groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
1014
- _id,
1015
- _type,
1016
- ${titleProj},
1017
- slug,
1018
- seo,
1019
- _updatedAt
1020
- }`;
1021
- }
1022
- const result = await client.fetch(groqQuery, params, { perspective: "published" });
1023
- const docsWithHealth = result.map((doc) => chunkS367Y35J_cjs.__spreadProps(chunkS367Y35J_cjs.__spreadValues({}, doc), {
1024
- health: calculateHealthScore(doc)
1025
- }));
1026
- setDocuments(docsWithHealth);
1027
- } catch (error) {
1028
- console.error("Error fetching documents:", error);
1029
- } finally {
1030
- setLoading(false);
1031
- setIsRefreshing(false);
1032
- }
1033
- },
1034
- // eslint-disable-next-line react-hooks/exhaustive-deps
1035
- [
1036
- client,
1037
- customQuery,
1038
- queryRequireSeo,
1039
- JSONStringifiedQueryTypes,
1040
- JSONStringifiedTitleField,
1041
- previewMode
1042
- ]
1043
- );
1044
- react.useEffect(() => {
1045
- fetchDocuments();
1046
- }, [
1047
- client,
1048
- customQuery,
1049
- queryRequireSeo,
1050
- JSONStringifiedQueryTypes,
1051
- JSONStringifiedTitleField,
1052
- previewMode
1053
- ]);
1054
- const handleRefresh = react.useCallback(() => {
1055
- fetchDocuments(true);
1056
- }, [fetchDocuments]);
1057
- const uniqueDocumentTypes = react.useMemo(() => {
1058
- const types = new Set(documents.map((doc) => doc._type));
1059
- return Array.from(types).sort();
1060
- }, [documents]);
1061
- const filteredAndSortedDocs = react.useMemo(() => {
1062
- let filtered = documents;
1063
- if (searchQuery) {
1064
- filtered = filtered.filter(
1065
- (doc) => {
1066
- var _a, _b;
1067
- return ((_a = doc.title) == null ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase())) || ((_b = doc._id) == null ? void 0 : _b.toLowerCase().includes(searchQuery.toLowerCase()));
1068
- }
1069
- );
1070
- }
1071
- if (filterStatus !== "all") {
1072
- filtered = filtered.filter((doc) => doc.health.status === filterStatus);
1073
- }
1074
- if (filterType !== "all") {
1075
- filtered = filtered.filter((doc) => doc._type === filterType);
1076
- }
1077
- const sorted = [...filtered].sort((a, b) => {
1078
- if (sortBy === "score") {
1079
- return b.health.score - a.health.score;
1080
- }
1081
- return (a.title || "").localeCompare(b.title || "");
1082
- });
1083
- return sorted;
1084
- }, [documents, searchQuery, filterStatus, filterType, sortBy]);
1085
- const stats = react.useMemo(() => {
1086
- const total = documents.length;
1087
- const excellent = documents.filter((d) => d.health.score >= 80).length;
1088
- const good = documents.filter((d) => d.health.score >= 60 && d.health.score < 80).length;
1089
- const fair = documents.filter((d) => d.health.score >= 40 && d.health.score < 60).length;
1090
- const poor = documents.filter((d) => d.health.score > 0 && d.health.score < 40).length;
1091
- const missing = documents.filter((d) => d.health.score === 0).length;
1092
- const avgScore = total > 0 ? Math.round(documents.reduce((sum, d) => sum + d.health.score, 0) / total) : 0;
1093
- return { total, excellent, good, fair, poor, missing, avgScore };
1094
- }, [documents]);
1095
- const handleMouseLeave = react.useCallback(() => {
1096
- setActivePopover(null);
1097
- }, []);
1098
- return /* @__PURE__ */ jsxRuntime.jsxs(DashboardContainer, { children: [
1099
- licenseStatus === "loading" && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
1100
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
1101
- loadingLicense != null ? loadingLicense : "Verifying license\u2026"
1102
- ] }),
1103
- licenseStatus === "invalid" && /* @__PURE__ */ jsxRuntime.jsx(UpgradeContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1104
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u274C" }),
1105
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "Invalid License Key" }),
1106
- /* @__PURE__ */ jsxRuntime.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." }),
1107
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `seofields({
1108
- healthDashboard: {
1109
- licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
1110
- },
1111
- })` }),
1112
- /* @__PURE__ */ jsxRuntime.jsx(
1113
- UpgradeButton,
1114
- {
1115
- href: "https://sanity-plugin-seofields.thehardik.in",
1116
- target: "_blank",
1117
- rel: "noopener noreferrer",
1118
- children: "Get a New License Key \u2192"
1119
- }
1120
- ),
1121
- /* @__PURE__ */ jsxRuntime.jsx("br", {}),
1122
- /* @__PURE__ */ jsxRuntime.jsx(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
1123
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1124
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u{1F512}" }),
1125
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeTitle, { children: "SEO Health Dashboard" }),
1126
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
1127
- /* @__PURE__ */ jsxRuntime.jsx(UpgradeCode, { children: `// sanity.config.ts
1128
- import { seofields } from 'sanity-plugin-seofields'
1129
-
1130
- export default defineConfig({
1131
- plugins: [
1132
- seofields({
1133
- healthDashboard: {
1134
- licenseKey: 'SEOF-XXXX-XXXX-XXXX',
1135
- },
1136
- }),
1137
- ],
1138
- })` }),
1139
- /* @__PURE__ */ jsxRuntime.jsx(
1140
- UpgradeButton,
1141
- {
1142
- href: "https://sanity-plugin-seofields.thehardik.in",
1143
- target: "_blank",
1144
- rel: "noopener noreferrer",
1145
- children: "Get a License Key \u2192"
1146
- }
1147
- )
1148
- ] }) }) }),
1149
- licenseStatus === "valid" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1150
- /* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
1151
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1152
- /* @__PURE__ */ jsxRuntime.jsxs(PageTitle, { children: [
1153
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1154
- icon,
1155
- " ",
1156
- title
1157
- ] }),
1158
- previewMode && /* @__PURE__ */ jsxRuntime.jsx(PreviewBadge, { children: "Preview Mode" })
1159
- ] }),
1160
- /* @__PURE__ */ jsxRuntime.jsx(PageSubtitle, { children: description })
1161
- ] }),
1162
- /* @__PURE__ */ jsxRuntime.jsxs(
1163
- DashboardRefreshButton,
1164
- {
1165
- onClick: handleRefresh,
1166
- disabled: loading || isRefreshing,
1167
- $spinning: isRefreshing,
1168
- title: "Refresh documents",
1169
- children: [
1170
- /* @__PURE__ */ jsxRuntime.jsxs(
1171
- "svg",
1172
- {
1173
- width: "14",
1174
- height: "14",
1175
- viewBox: "0 0 24 24",
1176
- fill: "none",
1177
- stroke: "currentColor",
1178
- strokeWidth: "2.2",
1179
- strokeLinecap: "round",
1180
- strokeLinejoin: "round",
1181
- children: [
1182
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "23 4 23 10 17 10" }),
1183
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "1 20 1 14 7 14" }),
1184
- /* @__PURE__ */ jsxRuntime.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" })
1185
- ]
1186
- }
1187
- ),
1188
- "Refresh"
1189
- ]
1190
- }
1191
- )
1192
- ] }),
1193
- deprecationGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(DeprecationBanner, { children: [
1194
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "\u26A0\uFE0F Deprecated config keys detected:" }),
1195
- " ",
1196
- deprecationGroups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1197
- group.keys.map((w, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1198
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#fef9c3", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[0].trim() }),
1199
- " \u2192 ",
1200
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { background: "#dcfce7", padding: "1px 4px", borderRadius: 3 }, children: w.split("\u2192")[1].trim() }),
1201
- i < group.keys.length - 1 ? " \xB7 " : ""
1202
- ] }, w)),
1203
- " ",
1204
- "(",
1205
- /* @__PURE__ */ jsxRuntime.jsxs(
1206
- DeprecationBannerLink,
1207
- {
1208
- href: group.changelogUrl,
1209
- target: "_blank",
1210
- rel: "noopener noreferrer",
1211
- children: [
1212
- group.version,
1213
- " changelog"
1214
- ]
1215
- }
1216
- ),
1217
- ")",
1218
- gi < deprecationGroups.length - 1 ? " \xB7 " : ""
1219
- ] }, group.version)),
1220
- " ",
1221
- "\u2014 Please update your config."
1222
- ] }),
1223
- !loading && /* @__PURE__ */ jsxRuntime.jsxs(StatsGrid, { children: [
1224
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1225
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Total Docs" }),
1226
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.total })
1227
- ] }),
1228
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { children: [
1229
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Avg Score" }),
1230
- /* @__PURE__ */ jsxRuntime.jsxs(StatValue, { children: [
1231
- stats.avgScore,
1232
- "%"
1233
- ] })
1234
- ] }),
1235
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#10b981", children: [
1236
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Excellent (80+)" }),
1237
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.excellent })
1238
- ] }),
1239
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f59e0b", children: [
1240
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Good (60\u201379)" }),
1241
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.good })
1242
- ] }),
1243
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#f97316", children: [
1244
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Fair (40\u201359)" }),
1245
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.fair })
1246
- ] }),
1247
- /* @__PURE__ */ jsxRuntime.jsxs(StatCard, { $accent: "#ef4444", children: [
1248
- /* @__PURE__ */ jsxRuntime.jsx(StatLabel, { children: "Poor / Missing" }),
1249
- /* @__PURE__ */ jsxRuntime.jsx(StatValue, { children: stats.poor + stats.missing })
1250
- ] })
1251
- ] }),
1252
- /* @__PURE__ */ jsxRuntime.jsxs(ControlsBar, { children: [
1253
- /* @__PURE__ */ jsxRuntime.jsxs(SearchWrapper, { children: [
1254
- /* @__PURE__ */ jsxRuntime.jsx(SearchIconSvg, { children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx(
1255
- "path",
1256
- {
1257
- fillRule: "evenodd",
1258
- 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",
1259
- clipRule: "evenodd"
1260
- }
1261
- ) }) }),
1262
- /* @__PURE__ */ jsxRuntime.jsx(
1263
- SearchInput,
1264
- {
1265
- placeholder: "Search documents...",
1266
- value: searchQuery,
1267
- onChange: (e) => setSearchQuery(e.currentTarget.value)
1268
- }
1269
- )
1270
- ] }),
1271
- /* @__PURE__ */ jsxRuntime.jsxs(
1272
- StyledSelect,
1273
- {
1274
- value: filterStatus,
1275
- onChange: (e) => setFilterStatus(e.currentTarget.value),
1276
- children: [
1277
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Status" }),
1278
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "excellent", children: "Excellent" }),
1279
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "good", children: "Good" }),
1280
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "fair", children: "Fair" }),
1281
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "poor", children: "Poor" }),
1282
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "missing", children: "Missing" })
1283
- ]
1284
- }
1285
- ),
1286
- uniqueDocumentTypes.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(
1287
- StyledSelect,
1288
- {
1289
- value: filterType,
1290
- onChange: (e) => setFilterType(e.currentTarget.value),
1291
- children: [
1292
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Types" }),
1293
- uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: resolveTypeLabel(type, resolvedTypeLabels) }, type))
1294
- ]
1295
- }
1296
- ),
1297
- /* @__PURE__ */ jsxRuntime.jsxs(
1298
- StyledSelect,
1299
- {
1300
- value: sortBy,
1301
- onChange: (e) => setSortBy(e.currentTarget.value),
1302
- children: [
1303
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "score", children: "Sort by Score" }),
1304
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "title", children: "Sort by Title" })
1305
- ]
1306
- }
1307
- )
1308
- ] }),
1309
- /* @__PURE__ */ jsxRuntime.jsxs(TableCard, { children: [
1310
- loading && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
1311
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
1312
- loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
1313
- ] }),
1314
- !loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1315
- /* @__PURE__ */ jsxRuntime.jsxs(TableHeader, { children: [
1316
- /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: "Title" }),
1317
- showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: "Type" }),
1318
- /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: "Score" }),
1319
- /* @__PURE__ */ jsxRuntime.jsx(ColIssues, { children: "Top Issues" })
1320
- ] }),
1321
- filteredAndSortedDocs.map((doc) => {
1322
- return /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
1323
- /* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: /* @__PURE__ */ jsxRuntime.jsx(TitleWrapper, { children: /* @__PURE__ */ jsxRuntime.jsxs(TitleCell, { children: [
1324
- doc.title !== null && typeof doc.title !== "string" ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: openInPane ? /* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled" }) : /* @__PURE__ */ jsxRuntime.jsx(
1325
- DocTitleAnchor,
1326
- {
1327
- id: doc._id,
1328
- type: doc._type,
1329
- structureTool,
1330
- children: typeof doc.title === "string" ? doc.title || "Untitled" : "Untitled"
1331
- }
1332
- ) }),
1333
- showDocumentId && /* @__PURE__ */ jsxRuntime.jsx(DocId, { children: doc._id }),
1334
- resolvedDocBadge && /* @__PURE__ */ jsxRuntime.jsx(
1335
- DocBadgeRenderer,
1336
- {
1337
- doc,
1338
- docBadge: resolvedDocBadge
1339
- }
1340
- )
1341
- ] }) }) }),
1342
- showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, resolvedTypeLabels) }) : (() => {
1343
- const typeColor = getTypeColor(doc._type);
1344
- return /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, resolvedTypeLabels) });
1345
- })() }),
1346
- /* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: /* @__PURE__ */ jsxRuntime.jsxs(ScoreBadge, { $score: doc.health.score, children: [
1347
- doc.health.score,
1348
- "%"
1349
- ] }) }),
1350
- /* @__PURE__ */ jsxRuntime.jsxs(ColIssues, { children: [
1351
- doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(IssueTag, { children: [
1352
- "\u2022 ",
1353
- issue
1354
- ] }, `issue-${doc._id}-${issue}`)),
1355
- doc.health.issues.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
1356
- MoreIssuesWrapper,
1357
- {
1358
- onMouseEnter: function(e) {
1359
- handleMouseEnterIssues(
1360
- e.currentTarget,
1361
- doc.health.issues.slice(2)
1362
- );
1363
- },
1364
- onMouseLeave: handleMouseLeave,
1365
- children: /* @__PURE__ */ jsxRuntime.jsxs(MoreIssues, { children: [
1366
- "+",
1367
- doc.health.issues.length - 2,
1368
- " more issues"
1369
- ] })
1370
- }
1371
- )
1372
- ] })
1373
- ] }, doc._id);
1374
- })
1375
- ] }))
1376
- ] }),
1377
- activePopover && /* @__PURE__ */ jsxRuntime.jsx(
1378
- IssuesPopover,
1379
- {
1380
- style: {
1381
- top: activePopover.top,
1382
- left: activePopover.left,
1383
- transform: "translateY(calc(-100% - 10px))"
1384
- },
1385
- children: activePopover.issues.map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(PopoverIssueItem, { children: [
1386
- "\u26A0\uFE0F ",
1387
- issue
1388
- ] }, issue))
1389
- }
1390
- ),
1391
- " "
1392
- ] }),
1393
- " "
1394
- ] });
1395
- };
1396
- var SeoHealthDashboard_default = SeoHealthDashboard;
1397
-
1398
- exports.SeoHealthDashboard_default = SeoHealthDashboard_default;
1399
- //# sourceMappingURL=chunk-DYVCCQHT.cjs.map
1400
- //# sourceMappingURL=chunk-DYVCCQHT.cjs.map