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