sanity-plugin-seofields 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2604 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +422 -0
- package/dist/index.d.ts +339 -492
- package/dist/index.js +1284 -2013
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +182 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +241 -0
- package/dist/next.d.ts +202 -295
- package/dist/next.js +110 -70
- package/dist/next.js.map +1 -1
- package/dist/types-B91ena4g.d.cts +89 -0
- package/dist/types-B91ena4g.d.ts +89 -0
- package/package.json +37 -18
- package/dist/index.d.mts +0 -575
- package/dist/index.mjs +0 -3292
- package/dist/index.mjs.map +0 -1
- package/dist/next.d.mts +0 -334
- package/dist/next.mjs +0 -102
- package/dist/next.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/components/SeoHealthDashboard.tsx +0 -1568
- package/src/components/SeoHealthPane.tsx +0 -81
- package/src/components/SeoHealthTool.tsx +0 -11
- package/src/components/SeoPreview.tsx +0 -178
- package/src/components/meta/MetaDescription.tsx +0 -39
- package/src/components/meta/MetaTitle.tsx +0 -44
- package/src/components/openGraph/OgDescription.tsx +0 -46
- package/src/components/openGraph/OgTitle.tsx +0 -45
- package/src/components/twitter/twitterDescription.tsx +0 -45
- package/src/components/twitter/twitterTitle.tsx +0 -45
- package/src/helpers/SeoMetaTags.tsx +0 -154
- package/src/helpers/seoMeta.ts +0 -283
- package/src/index.ts +0 -26
- package/src/next.ts +0 -12
- package/src/plugin.ts +0 -344
- package/src/schemas/index.ts +0 -121
- package/src/schemas/types/index.ts +0 -20
- package/src/schemas/types/metaAttribute/index.ts +0 -60
- package/src/schemas/types/metaTag/index.ts +0 -17
- package/src/schemas/types/openGraph/index.ts +0 -114
- package/src/schemas/types/robots/index.ts +0 -26
- package/src/schemas/types/twitter/index.ts +0 -108
- package/src/types.ts +0 -108
- package/src/utils/fieldsUtils.ts +0 -160
- package/src/utils/seoUtils.ts +0 -423
- package/src/utils/utils.ts +0 -9
- package/v2-incompatible.js +0 -11
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2604 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
40
|
+
};
|
|
41
|
+
var __copyProps = (to, from, except, desc) => {
|
|
42
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
43
|
+
for (let key of __getOwnPropNames(from))
|
|
44
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
45
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
46
|
+
}
|
|
47
|
+
return to;
|
|
48
|
+
};
|
|
49
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
50
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
51
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
52
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
53
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
54
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
55
|
+
mod
|
|
56
|
+
));
|
|
57
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
58
|
+
|
|
59
|
+
// src/index.ts
|
|
60
|
+
var src_exports = {};
|
|
61
|
+
__export(src_exports, {
|
|
62
|
+
SeoHealthDashboard: () => SeoHealthDashboard_default,
|
|
63
|
+
SeoHealthTool: () => SeoHealthTool_default,
|
|
64
|
+
allSchemas: () => types,
|
|
65
|
+
createSeoHealthPane: () => createSeoHealthPane,
|
|
66
|
+
default: () => src_default,
|
|
67
|
+
metaAttributeSchema: () => metaAttribute_default,
|
|
68
|
+
metaTagSchema: () => metaTag_default,
|
|
69
|
+
openGraphSchema: () => openGraph,
|
|
70
|
+
robotsSchema: () => robots_default,
|
|
71
|
+
seoFieldsSchema: () => seoFieldsSchema,
|
|
72
|
+
twitterSchema: () => twitter
|
|
73
|
+
});
|
|
74
|
+
module.exports = __toCommonJS(src_exports);
|
|
75
|
+
|
|
76
|
+
// src/plugin.ts
|
|
77
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
78
|
+
var import_sanity15 = require("sanity");
|
|
79
|
+
|
|
80
|
+
// src/components/SeoHealthDashboard.tsx
|
|
81
|
+
var import_react = require("react");
|
|
82
|
+
var import_sanity = require("sanity");
|
|
83
|
+
var import_router = require("sanity/router");
|
|
84
|
+
var import_structure = require("sanity/structure");
|
|
85
|
+
var import_styled_components = __toESM(require("styled-components"), 1);
|
|
86
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
87
|
+
var DashboardContainer = import_styled_components.default.div`
|
|
88
|
+
width: 100%;
|
|
89
|
+
min-height: 100%;
|
|
90
|
+
background: #f0f2f5;
|
|
91
|
+
padding: 28px 32px;
|
|
92
|
+
box-sizing: border-box;
|
|
93
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
94
|
+
`;
|
|
95
|
+
var PageHeader = import_styled_components.default.div`
|
|
96
|
+
margin-bottom: 28px;
|
|
97
|
+
`;
|
|
98
|
+
var PageTitle = import_styled_components.default.h1`
|
|
99
|
+
margin: 0 0 6px 0;
|
|
100
|
+
font-size: 22px;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
color: #111827;
|
|
103
|
+
letter-spacing: -0.3px;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 10px;
|
|
107
|
+
`;
|
|
108
|
+
var PreviewBadge = import_styled_components.default.span`
|
|
109
|
+
display: inline-block;
|
|
110
|
+
background: #fef3c7;
|
|
111
|
+
color: #92400e;
|
|
112
|
+
font-size: 11px;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
padding: 4px 8px;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
letter-spacing: 0.5px;
|
|
118
|
+
margin-left: 8px;
|
|
119
|
+
`;
|
|
120
|
+
var PageSubtitle = import_styled_components.default.p`
|
|
121
|
+
margin: 0;
|
|
122
|
+
font-size: 13px;
|
|
123
|
+
color: #6b7280;
|
|
124
|
+
`;
|
|
125
|
+
var StatsGrid = import_styled_components.default.div`
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
128
|
+
gap: 14px;
|
|
129
|
+
margin-bottom: 20px;
|
|
130
|
+
`;
|
|
131
|
+
var StatCard = import_styled_components.default.div`
|
|
132
|
+
background: #ffffff;
|
|
133
|
+
border-radius: 10px;
|
|
134
|
+
padding: 16px 18px;
|
|
135
|
+
box-shadow:
|
|
136
|
+
0 1px 3px rgba(0, 0, 0, 0.07),
|
|
137
|
+
0 1px 2px rgba(0, 0, 0, 0.05);
|
|
138
|
+
border-left: ${(p) => p.$accent ? `4px solid ${p.$accent}` : "4px solid transparent"};
|
|
139
|
+
transition: box-shadow 0.15s ease;
|
|
140
|
+
|
|
141
|
+
&:hover {
|
|
142
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
var StatLabel = import_styled_components.default.div`
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
font-weight: 500;
|
|
148
|
+
color: #9ca3af;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
letter-spacing: 0.5px;
|
|
151
|
+
margin-bottom: 8px;
|
|
152
|
+
`;
|
|
153
|
+
var StatValue = import_styled_components.default.div`
|
|
154
|
+
font-size: 26px;
|
|
155
|
+
font-weight: 700;
|
|
156
|
+
color: #111827;
|
|
157
|
+
line-height: 1;
|
|
158
|
+
`;
|
|
159
|
+
var ControlsBar = import_styled_components.default.div`
|
|
160
|
+
background: #ffffff;
|
|
161
|
+
border-radius: 10px;
|
|
162
|
+
padding: 14px 18px;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 12px;
|
|
166
|
+
flex-wrap: wrap;
|
|
167
|
+
margin-bottom: 20px;
|
|
168
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
|
|
169
|
+
`;
|
|
170
|
+
var SearchWrapper = import_styled_components.default.div`
|
|
171
|
+
position: relative;
|
|
172
|
+
flex: 1;
|
|
173
|
+
min-width: 220px;
|
|
174
|
+
`;
|
|
175
|
+
var SearchIconSvg = import_styled_components.default.span`
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: 11px;
|
|
178
|
+
top: 50%;
|
|
179
|
+
transform: translateY(-50%);
|
|
180
|
+
color: #9ca3af;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
pointer-events: none;
|
|
184
|
+
`;
|
|
185
|
+
var SearchInput = import_styled_components.default.input`
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 36px;
|
|
188
|
+
padding: 0 12px 0 34px;
|
|
189
|
+
border: 1px solid #e5e7eb;
|
|
190
|
+
border-radius: 7px;
|
|
191
|
+
font-size: 13px;
|
|
192
|
+
color: #111827;
|
|
193
|
+
background: #f9fafb;
|
|
194
|
+
box-sizing: border-box;
|
|
195
|
+
outline: none;
|
|
196
|
+
transition:
|
|
197
|
+
border-color 0.15s,
|
|
198
|
+
background 0.15s;
|
|
199
|
+
|
|
200
|
+
&::placeholder {
|
|
201
|
+
color: #9ca3af;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
&:focus {
|
|
205
|
+
border-color: #6366f1;
|
|
206
|
+
background: #fff;
|
|
207
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
var StyledSelect = import_styled_components.default.select`
|
|
211
|
+
height: 36px;
|
|
212
|
+
padding: 0 32px 0 12px;
|
|
213
|
+
border: 1px solid #e5e7eb;
|
|
214
|
+
border-radius: 7px;
|
|
215
|
+
font-size: 13px;
|
|
216
|
+
color: #374151;
|
|
217
|
+
background: #f9fafb
|
|
218
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
|
|
219
|
+
no-repeat right 10px center;
|
|
220
|
+
appearance: none;
|
|
221
|
+
outline: none;
|
|
222
|
+
cursor: pointer;
|
|
223
|
+
transition: border-color 0.15s;
|
|
224
|
+
|
|
225
|
+
&:focus {
|
|
226
|
+
border-color: #6366f1;
|
|
227
|
+
background-color: #fff;
|
|
228
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
229
|
+
}
|
|
230
|
+
`;
|
|
231
|
+
var TableCard = import_styled_components.default.div`
|
|
232
|
+
background: #ffffff;
|
|
233
|
+
border-radius: 10px;
|
|
234
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
|
|
235
|
+
overflow: hidden;
|
|
236
|
+
`;
|
|
237
|
+
var TableHeader = import_styled_components.default.div`
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
padding: 11px 20px;
|
|
241
|
+
background: #f9fafb;
|
|
242
|
+
border-bottom: 1px solid #e5e7eb;
|
|
243
|
+
font-size: 11px;
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
color: #6b7280;
|
|
246
|
+
text-transform: uppercase;
|
|
247
|
+
letter-spacing: 0.5px;
|
|
248
|
+
gap: 12px;
|
|
249
|
+
`;
|
|
250
|
+
var TableRow = import_styled_components.default.div`
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
padding: 13px 20px;
|
|
254
|
+
border-bottom: 1px solid #f3f4f6;
|
|
255
|
+
gap: 12px;
|
|
256
|
+
transition: background 0.1s;
|
|
257
|
+
|
|
258
|
+
&:last-child {
|
|
259
|
+
border-bottom: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
&:hover {
|
|
263
|
+
background: #fafafa;
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
var ColTitle = import_styled_components.default.div`
|
|
267
|
+
flex: 2;
|
|
268
|
+
min-width: 0;
|
|
269
|
+
`;
|
|
270
|
+
var TitleWrapper = import_styled_components.default.div`
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
gap: 4px;
|
|
274
|
+
flex-wrap: wrap;
|
|
275
|
+
min-width: 0;
|
|
276
|
+
`;
|
|
277
|
+
var TitleCell = import_styled_components.default.div`
|
|
278
|
+
min-width: 0;
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
flex: 1;
|
|
281
|
+
`;
|
|
282
|
+
var ColType = import_styled_components.default.div`
|
|
283
|
+
flex: 0.8;
|
|
284
|
+
min-width: 80px;
|
|
285
|
+
`;
|
|
286
|
+
var ColScore = import_styled_components.default.div`
|
|
287
|
+
flex: 0.6;
|
|
288
|
+
min-width: 70px;
|
|
289
|
+
`;
|
|
290
|
+
var ColIssues = import_styled_components.default.div`
|
|
291
|
+
flex: 2;
|
|
292
|
+
min-width: 0;
|
|
293
|
+
`;
|
|
294
|
+
var DocTitleLink = import_styled_components.default.a`
|
|
295
|
+
font-size: 13px;
|
|
296
|
+
font-weight: 600;
|
|
297
|
+
color: #4f46e5;
|
|
298
|
+
white-space: nowrap;
|
|
299
|
+
overflow: hidden;
|
|
300
|
+
text-overflow: ellipsis;
|
|
301
|
+
text-decoration: none;
|
|
302
|
+
display: block;
|
|
303
|
+
transition: color 0.15s;
|
|
304
|
+
|
|
305
|
+
&:hover {
|
|
306
|
+
color: #4338ca;
|
|
307
|
+
text-decoration: underline;
|
|
308
|
+
}
|
|
309
|
+
`;
|
|
310
|
+
var DocId = import_styled_components.default.div`
|
|
311
|
+
font-size: 11px;
|
|
312
|
+
color: #9ca3af;
|
|
313
|
+
margin-top: 2px;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
text-overflow: ellipsis;
|
|
317
|
+
`;
|
|
318
|
+
var TypeBadge = import_styled_components.default.span`
|
|
319
|
+
display: inline-block;
|
|
320
|
+
padding: 3px 8px;
|
|
321
|
+
border-radius: 5px;
|
|
322
|
+
font-size: 11px;
|
|
323
|
+
font-weight: 500;
|
|
324
|
+
background: ${(p) => p.$bgColor || "#ede9fe"};
|
|
325
|
+
color: ${(p) => p.$textColor || "#5b21b6"};
|
|
326
|
+
`;
|
|
327
|
+
var TypeText = import_styled_components.default.span`
|
|
328
|
+
font-size: 12px;
|
|
329
|
+
font-weight: 500;
|
|
330
|
+
color: #374151;
|
|
331
|
+
`;
|
|
332
|
+
var CustomBadge = import_styled_components.default.span`
|
|
333
|
+
display: inline-block;
|
|
334
|
+
padding: 2px 6px;
|
|
335
|
+
border-radius: 4px;
|
|
336
|
+
font-size: ${(p) => p.$fontSize || "10px"};
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
margin-left: 6px;
|
|
339
|
+
background: ${(p) => p.$bgColor || "#e0e7ff"};
|
|
340
|
+
color: ${(p) => p.$textColor || "#3730a3"};
|
|
341
|
+
white-space: nowrap;
|
|
342
|
+
`;
|
|
343
|
+
var ScoreBadge = import_styled_components.default.span`
|
|
344
|
+
display: inline-block;
|
|
345
|
+
padding: 4px 10px;
|
|
346
|
+
border-radius: 6px;
|
|
347
|
+
font-size: 12px;
|
|
348
|
+
font-weight: 700;
|
|
349
|
+
background: ${(p) => {
|
|
350
|
+
if (p.$score >= 80) return "#d1fae5";
|
|
351
|
+
if (p.$score >= 60) return "#fef3c7";
|
|
352
|
+
if (p.$score >= 40) return "#ffedd5";
|
|
353
|
+
return "#fee2e2";
|
|
354
|
+
}};
|
|
355
|
+
color: ${(p) => {
|
|
356
|
+
if (p.$score >= 80) return "#065f46";
|
|
357
|
+
if (p.$score >= 60) return "#92400e";
|
|
358
|
+
if (p.$score >= 40) return "#9a3412";
|
|
359
|
+
return "#991b1b";
|
|
360
|
+
}};
|
|
361
|
+
`;
|
|
362
|
+
var IssueTag = import_styled_components.default.div`
|
|
363
|
+
font-size: 11px;
|
|
364
|
+
color: #ef4444;
|
|
365
|
+
line-height: 1.5;
|
|
366
|
+
white-space: nowrap;
|
|
367
|
+
overflow: hidden;
|
|
368
|
+
text-overflow: ellipsis;
|
|
369
|
+
`;
|
|
370
|
+
var MoreIssues = import_styled_components.default.div`
|
|
371
|
+
font-size: 11px;
|
|
372
|
+
color: #6b7280;
|
|
373
|
+
cursor: pointer;
|
|
374
|
+
transition: color 0.15s;
|
|
375
|
+
|
|
376
|
+
&:hover {
|
|
377
|
+
color: #374151;
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
var MoreIssuesWrapper = import_styled_components.default.div`
|
|
381
|
+
position: relative;
|
|
382
|
+
display: inline-block;
|
|
383
|
+
`;
|
|
384
|
+
var IssuesPopover = import_styled_components.default.div`
|
|
385
|
+
position: absolute;
|
|
386
|
+
bottom: auto;
|
|
387
|
+
left: 0;
|
|
388
|
+
transform: translateY(calc(-100% - 14px));
|
|
389
|
+
background: #1f2937;
|
|
390
|
+
color: #ffffff;
|
|
391
|
+
padding: 12px;
|
|
392
|
+
border-radius: 8px;
|
|
393
|
+
font-size: 12px;
|
|
394
|
+
z-index: 50;
|
|
395
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
396
|
+
width: 280px;
|
|
397
|
+
word-break: break-word;
|
|
398
|
+
line-height: 1.5;
|
|
399
|
+
|
|
400
|
+
&::after {
|
|
401
|
+
content: '';
|
|
402
|
+
position: absolute;
|
|
403
|
+
bottom: -6px;
|
|
404
|
+
left: 12px;
|
|
405
|
+
width: 0;
|
|
406
|
+
height: 0;
|
|
407
|
+
border-left: 6px solid transparent;
|
|
408
|
+
border-right: 6px solid transparent;
|
|
409
|
+
border-top: 6px solid #1f2937;
|
|
410
|
+
}
|
|
411
|
+
`;
|
|
412
|
+
var PopoverIssueItem = import_styled_components.default.div`
|
|
413
|
+
display: flex;
|
|
414
|
+
gap: 6px;
|
|
415
|
+
margin-bottom: 6px;
|
|
416
|
+
|
|
417
|
+
&:last-child {
|
|
418
|
+
margin-bottom: 0;
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
var UpgradeContainer = import_styled_components.default.div`
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: center;
|
|
424
|
+
justify-content: center;
|
|
425
|
+
min-height: 100%;
|
|
426
|
+
padding: 60px 24px;
|
|
427
|
+
`;
|
|
428
|
+
var UpgradeBox = import_styled_components.default.div`
|
|
429
|
+
background: #ffffff;
|
|
430
|
+
border-radius: 16px;
|
|
431
|
+
padding: 48px 40px;
|
|
432
|
+
max-width: 480px;
|
|
433
|
+
width: 100%;
|
|
434
|
+
text-align: center;
|
|
435
|
+
box-shadow:
|
|
436
|
+
0 4px 24px rgba(0, 0, 0, 0.08),
|
|
437
|
+
0 1px 4px rgba(0, 0, 0, 0.05);
|
|
438
|
+
border: 1px solid #e5e7eb;
|
|
439
|
+
`;
|
|
440
|
+
var UpgradeLock = import_styled_components.default.div`
|
|
441
|
+
font-size: 40px;
|
|
442
|
+
margin-bottom: 16px;
|
|
443
|
+
`;
|
|
444
|
+
var UpgradeTitle = import_styled_components.default.h2`
|
|
445
|
+
margin: 0 0 10px;
|
|
446
|
+
font-size: 20px;
|
|
447
|
+
font-weight: 700;
|
|
448
|
+
color: #111827;
|
|
449
|
+
`;
|
|
450
|
+
var UpgradeText = import_styled_components.default.p`
|
|
451
|
+
margin: 0 0 20px;
|
|
452
|
+
font-size: 14px;
|
|
453
|
+
color: #6b7280;
|
|
454
|
+
line-height: 1.6;
|
|
455
|
+
`;
|
|
456
|
+
var UpgradeCode = import_styled_components.default.pre`
|
|
457
|
+
background: #f3f4f6;
|
|
458
|
+
border-radius: 8px;
|
|
459
|
+
padding: 14px 16px;
|
|
460
|
+
font-size: 12px;
|
|
461
|
+
color: #374151;
|
|
462
|
+
text-align: left;
|
|
463
|
+
margin: 0 0 24px;
|
|
464
|
+
overflow-x: auto;
|
|
465
|
+
line-height: 1.6;
|
|
466
|
+
border: 1px solid #e5e7eb;
|
|
467
|
+
`;
|
|
468
|
+
var UpgradeButton = import_styled_components.default.a`
|
|
469
|
+
display: inline-block;
|
|
470
|
+
background: #4f46e5;
|
|
471
|
+
color: #ffffff;
|
|
472
|
+
font-size: 14px;
|
|
473
|
+
font-weight: 600;
|
|
474
|
+
padding: 10px 24px;
|
|
475
|
+
border-radius: 8px;
|
|
476
|
+
text-decoration: none;
|
|
477
|
+
transition: background 0.15s;
|
|
478
|
+
|
|
479
|
+
&:hover {
|
|
480
|
+
background: #4338ca;
|
|
481
|
+
}
|
|
482
|
+
`;
|
|
483
|
+
var ReloadButton = import_styled_components.default.button`
|
|
484
|
+
display: inline-block;
|
|
485
|
+
background: transparent;
|
|
486
|
+
color: #6b7280;
|
|
487
|
+
font-size: 13px;
|
|
488
|
+
font-weight: 500;
|
|
489
|
+
padding: 8px 20px;
|
|
490
|
+
border-radius: 8px;
|
|
491
|
+
border: 1px solid #d1d5db;
|
|
492
|
+
cursor: pointer;
|
|
493
|
+
margin-top: 10px;
|
|
494
|
+
transition:
|
|
495
|
+
background 0.15s,
|
|
496
|
+
color 0.15s,
|
|
497
|
+
border-color 0.15s;
|
|
498
|
+
|
|
499
|
+
&:hover {
|
|
500
|
+
background: #f3f4f6;
|
|
501
|
+
color: #374151;
|
|
502
|
+
border-color: #9ca3af;
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
var DocTitleAnchor = ({ id, type, structureTool, children }) => {
|
|
506
|
+
const { basePath } = (0, import_sanity.useWorkspace)();
|
|
507
|
+
const { onClick: intentOnClick, href: intentHref } = (0, import_router.useIntentLink)({ intent: "edit", params: { id, type } });
|
|
508
|
+
const href = structureTool ? `${basePath}/${structureTool}/intent/edit/id=${id};type=${type}/` : intentHref;
|
|
509
|
+
const onClick = structureTool ? void 0 : intentOnClick;
|
|
510
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocTitleLink, { href, onClick, title: "Open document", children });
|
|
511
|
+
};
|
|
512
|
+
var PaneLinkWrapper = import_styled_components.default.span`
|
|
513
|
+
display: block;
|
|
514
|
+
min-width: 0;
|
|
515
|
+
overflow: hidden;
|
|
516
|
+
|
|
517
|
+
a {
|
|
518
|
+
font-size: 13px;
|
|
519
|
+
font-weight: 600;
|
|
520
|
+
color: #4f46e5;
|
|
521
|
+
white-space: nowrap;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
text-overflow: ellipsis;
|
|
524
|
+
text-decoration: none;
|
|
525
|
+
display: block;
|
|
526
|
+
transition: color 0.15s;
|
|
527
|
+
|
|
528
|
+
&:hover {
|
|
529
|
+
color: #4338ca;
|
|
530
|
+
text-decoration: underline;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
`;
|
|
534
|
+
var DocTitleAnchorPane = ({
|
|
535
|
+
id,
|
|
536
|
+
type,
|
|
537
|
+
children
|
|
538
|
+
}) => {
|
|
539
|
+
const { ChildLink } = (0, import_structure.usePaneRouter)();
|
|
540
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PaneLinkWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChildLink, { childId: id, childParameters: { type }, children }) });
|
|
541
|
+
};
|
|
542
|
+
var DocBadgeRenderer = ({ doc, docBadge }) => {
|
|
543
|
+
const badge = docBadge(doc);
|
|
544
|
+
if (!badge) return null;
|
|
545
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label });
|
|
546
|
+
};
|
|
547
|
+
var spin = import_styled_components.keyframes`
|
|
548
|
+
to { transform: rotate(360deg); }
|
|
549
|
+
`;
|
|
550
|
+
var Spinner = import_styled_components.default.div`
|
|
551
|
+
width: 28px;
|
|
552
|
+
height: 28px;
|
|
553
|
+
border: 3px solid #e5e7eb;
|
|
554
|
+
border-top-color: #6366f1;
|
|
555
|
+
border-radius: 50%;
|
|
556
|
+
animation: ${spin} 0.7s linear infinite;
|
|
557
|
+
margin: 0 auto 12px;
|
|
558
|
+
`;
|
|
559
|
+
var LoadingState = import_styled_components.default.div`
|
|
560
|
+
padding: 48px 24px;
|
|
561
|
+
text-align: center;
|
|
562
|
+
color: #6b7280;
|
|
563
|
+
font-size: 13px;
|
|
564
|
+
`;
|
|
565
|
+
var EmptyState = import_styled_components.default.div`
|
|
566
|
+
padding: 48px 24px;
|
|
567
|
+
text-align: center;
|
|
568
|
+
color: #9ca3af;
|
|
569
|
+
font-size: 13px;
|
|
570
|
+
`;
|
|
571
|
+
var TYPE_COLOR_PALETTE = [
|
|
572
|
+
{ bg: "#dbeafe", text: "#0c4a6e" },
|
|
573
|
+
// Blue
|
|
574
|
+
{ bg: "#dcfce7", text: "#14532d" },
|
|
575
|
+
// Green
|
|
576
|
+
{ bg: "#fce7f3", text: "#500724" },
|
|
577
|
+
// Pink
|
|
578
|
+
{ bg: "#fed7aa", text: "#7c2d12" },
|
|
579
|
+
// Orange
|
|
580
|
+
{ bg: "#e9d5ff", text: "#581c87" },
|
|
581
|
+
// Purple
|
|
582
|
+
{ bg: "#f3e8ff", text: "#3f0f5c" },
|
|
583
|
+
// Deep Purple
|
|
584
|
+
{ bg: "#ccfbf1", text: "#134e4a" },
|
|
585
|
+
// Teal
|
|
586
|
+
{ bg: "#ddd6fe", text: "#3730a3" },
|
|
587
|
+
// Indigo
|
|
588
|
+
{ bg: "#fca5a5", text: "#7f1d1d" },
|
|
589
|
+
// Red
|
|
590
|
+
{ bg: "#a7f3d0", text: "#065f46" },
|
|
591
|
+
// Emerald
|
|
592
|
+
{ bg: "#fbbf24", text: "#78350f" },
|
|
593
|
+
// Amber
|
|
594
|
+
{ bg: "#c4b5fd", text: "#3b0764" },
|
|
595
|
+
// Violet
|
|
596
|
+
{ bg: "#f0fdf4", text: "#15803d" },
|
|
597
|
+
// Light Green
|
|
598
|
+
{ bg: "#fef2f2", text: "#991b1b" },
|
|
599
|
+
// Light Red
|
|
600
|
+
{ bg: "#f5f3ff", text: "#5b21b6" },
|
|
601
|
+
// Light Purple
|
|
602
|
+
{ bg: "#fffbeb", text: "#92400e" }
|
|
603
|
+
// Light Amber
|
|
604
|
+
];
|
|
605
|
+
var getTypeColor = (type) => {
|
|
606
|
+
let hash = 0;
|
|
607
|
+
for (let i = 0; i < type.length; i += 1) {
|
|
608
|
+
const char = type.charCodeAt(i);
|
|
609
|
+
hash = Math.abs(hash * 31 + char);
|
|
610
|
+
}
|
|
611
|
+
const colorIndex = hash % TYPE_COLOR_PALETTE.length;
|
|
612
|
+
return TYPE_COLOR_PALETTE[colorIndex];
|
|
613
|
+
};
|
|
614
|
+
var getStatusCategory = (score) => {
|
|
615
|
+
if (score >= 80) return "excellent";
|
|
616
|
+
if (score >= 60) return "good";
|
|
617
|
+
if (score >= 40) return "fair";
|
|
618
|
+
if (score > 0) return "poor";
|
|
619
|
+
return "missing";
|
|
620
|
+
};
|
|
621
|
+
var scoreMetaTitle = (title) => {
|
|
622
|
+
const issues = [];
|
|
623
|
+
let score = 0;
|
|
624
|
+
if (title && title.length >= 50 && title.length <= 60) {
|
|
625
|
+
score = 15;
|
|
626
|
+
} else if (title && title.length > 0) {
|
|
627
|
+
score = 10;
|
|
628
|
+
if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
|
|
629
|
+
if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
|
|
630
|
+
} else {
|
|
631
|
+
issues.push("Missing meta title");
|
|
632
|
+
}
|
|
633
|
+
return { score, issues };
|
|
634
|
+
};
|
|
635
|
+
var scoreMetaDescription = (description) => {
|
|
636
|
+
const issues = [];
|
|
637
|
+
let score = 0;
|
|
638
|
+
if (description && description.length >= 120 && description.length <= 160) {
|
|
639
|
+
score = 15;
|
|
640
|
+
} else if (description && description.length > 0) {
|
|
641
|
+
score = 10;
|
|
642
|
+
if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
|
|
643
|
+
if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
|
|
644
|
+
} else {
|
|
645
|
+
issues.push("Missing meta description");
|
|
646
|
+
}
|
|
647
|
+
return { score, issues };
|
|
648
|
+
};
|
|
649
|
+
var scoreOpenGraph = (openGraph2) => {
|
|
650
|
+
const issues = [];
|
|
651
|
+
let score = 0;
|
|
652
|
+
if (openGraph2) {
|
|
653
|
+
if (openGraph2.title) score += 6;
|
|
654
|
+
else issues.push("Missing OG title");
|
|
655
|
+
if (openGraph2.description) score += 6;
|
|
656
|
+
else issues.push("Missing OG description");
|
|
657
|
+
if (openGraph2.image) score += 6;
|
|
658
|
+
else issues.push("Missing OG image");
|
|
659
|
+
if (openGraph2.type) score += 7;
|
|
660
|
+
else issues.push("Missing OG type");
|
|
661
|
+
} else {
|
|
662
|
+
issues.push("Open Graph not configured");
|
|
663
|
+
}
|
|
664
|
+
return { score, issues };
|
|
665
|
+
};
|
|
666
|
+
var scoreTwitterCard = (twitter2) => {
|
|
667
|
+
const issues = [];
|
|
668
|
+
let score = 0;
|
|
669
|
+
if (twitter2) {
|
|
670
|
+
if (twitter2.title) score += 5;
|
|
671
|
+
else issues.push("Missing Twitter title");
|
|
672
|
+
if (twitter2.description) score += 5;
|
|
673
|
+
else issues.push("Missing Twitter description");
|
|
674
|
+
if (twitter2.image) score += 5;
|
|
675
|
+
else issues.push("Missing Twitter image");
|
|
676
|
+
} else {
|
|
677
|
+
issues.push("Twitter Card not configured");
|
|
678
|
+
}
|
|
679
|
+
return { score, issues };
|
|
680
|
+
};
|
|
681
|
+
var calculateHealthScore = (doc) => {
|
|
682
|
+
let totalScore = 0;
|
|
683
|
+
const allIssues = [];
|
|
684
|
+
if (!doc.seo) {
|
|
685
|
+
return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
|
|
686
|
+
}
|
|
687
|
+
const { title, description, metaImage, keywords, robots, openGraph: openGraph2, twitter: twitter2 } = doc.seo;
|
|
688
|
+
const titleScore = scoreMetaTitle(title);
|
|
689
|
+
totalScore += titleScore.score;
|
|
690
|
+
allIssues.push(...titleScore.issues);
|
|
691
|
+
const descriptionScore = scoreMetaDescription(description);
|
|
692
|
+
totalScore += descriptionScore.score;
|
|
693
|
+
allIssues.push(...descriptionScore.issues);
|
|
694
|
+
if (metaImage) {
|
|
695
|
+
totalScore += 10;
|
|
696
|
+
} else {
|
|
697
|
+
allIssues.push("Missing meta image");
|
|
698
|
+
}
|
|
699
|
+
if (keywords && keywords.length > 0) {
|
|
700
|
+
totalScore += 10;
|
|
701
|
+
} else {
|
|
702
|
+
allIssues.push("No keywords defined");
|
|
703
|
+
}
|
|
704
|
+
const ogScore = scoreOpenGraph(openGraph2);
|
|
705
|
+
totalScore += ogScore.score;
|
|
706
|
+
allIssues.push(...ogScore.issues);
|
|
707
|
+
const twitterScore = scoreTwitterCard(twitter2);
|
|
708
|
+
totalScore += twitterScore.score;
|
|
709
|
+
allIssues.push(...twitterScore.issues);
|
|
710
|
+
if (robots && !robots.noIndex) {
|
|
711
|
+
totalScore += 5;
|
|
712
|
+
}
|
|
713
|
+
const status = getStatusCategory(totalScore);
|
|
714
|
+
return { score: totalScore, status, issues: allIssues };
|
|
715
|
+
};
|
|
716
|
+
var resolveTypeLabel = (type, typeLabels) => {
|
|
717
|
+
var _a;
|
|
718
|
+
return (_a = typeLabels == null ? void 0 : typeLabels[type]) != null ? _a : type;
|
|
719
|
+
};
|
|
720
|
+
var buildTitleProjection = (titleField) => {
|
|
721
|
+
if (!titleField || titleField === "title") return "title";
|
|
722
|
+
if (typeof titleField === "string") return `"title": ${titleField}`;
|
|
723
|
+
const cases = Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ");
|
|
724
|
+
return `"title": select(${cases}, title)`;
|
|
725
|
+
};
|
|
726
|
+
var generateDummyData = () => {
|
|
727
|
+
const dummyDocs = [
|
|
728
|
+
{
|
|
729
|
+
_id: "preview-post-1",
|
|
730
|
+
_type: "post",
|
|
731
|
+
title: "Getting Started with SEO Best Practices",
|
|
732
|
+
slug: { current: "getting-started-seo" },
|
|
733
|
+
_updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
734
|
+
seo: {
|
|
735
|
+
title: "Getting Started with SEO Best Practices | My Blog",
|
|
736
|
+
description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
|
|
737
|
+
keywords: ["seo", "best practices", "optimization"],
|
|
738
|
+
metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
|
|
739
|
+
openGraph: {
|
|
740
|
+
title: "SEO Best Practices Guide",
|
|
741
|
+
description: "Master SEO optimization",
|
|
742
|
+
image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
|
|
743
|
+
type: "article"
|
|
744
|
+
},
|
|
745
|
+
twitter: {
|
|
746
|
+
title: "SEO Best Practices",
|
|
747
|
+
description: "Learn SEO optimization",
|
|
748
|
+
image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
|
|
749
|
+
card: "summary_large_image"
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
_id: "preview-post-2",
|
|
755
|
+
_type: "post",
|
|
756
|
+
title: "Advanced Analytics Strategy",
|
|
757
|
+
slug: { current: "advanced-analytics" },
|
|
758
|
+
_updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
759
|
+
seo: {
|
|
760
|
+
title: "Advanced Analytics",
|
|
761
|
+
description: "Strategy tips",
|
|
762
|
+
keywords: ["analytics", "data"],
|
|
763
|
+
openGraph: {
|
|
764
|
+
title: "Analytics Guide"
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
_id: "preview-page-1",
|
|
770
|
+
_type: "page",
|
|
771
|
+
title: "About Us",
|
|
772
|
+
slug: { current: "about" },
|
|
773
|
+
_updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
774
|
+
seo: {
|
|
775
|
+
title: "About",
|
|
776
|
+
keywords: ["company", "team"],
|
|
777
|
+
metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
_id: "preview-post-3",
|
|
782
|
+
_type: "post",
|
|
783
|
+
title: "Content Marketing Trends for 2024",
|
|
784
|
+
slug: { current: "content-marketing-trends" },
|
|
785
|
+
_updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
786
|
+
seo: {
|
|
787
|
+
title: "Content Marketing Trends 2024",
|
|
788
|
+
description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
|
|
789
|
+
keywords: ["content marketing", "trends", "strategy", "engagement"],
|
|
790
|
+
metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
|
|
791
|
+
openGraph: {
|
|
792
|
+
title: "Content Marketing Trends 2024",
|
|
793
|
+
description: "Latest trends in content marketing",
|
|
794
|
+
image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
|
|
795
|
+
type: "article"
|
|
796
|
+
},
|
|
797
|
+
twitter: {
|
|
798
|
+
title: "Content Marketing Trends",
|
|
799
|
+
description: "Discover the latest trends",
|
|
800
|
+
card: "summary"
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
_id: "preview-post-4",
|
|
806
|
+
_type: "product",
|
|
807
|
+
title: "Pro Plan",
|
|
808
|
+
slug: { current: "pro-plan" },
|
|
809
|
+
_updatedAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
810
|
+
seo: {
|
|
811
|
+
title: "Pro",
|
|
812
|
+
keywords: ["pricing"]
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
_id: "preview-page-2",
|
|
817
|
+
_type: "page",
|
|
818
|
+
title: "Contact",
|
|
819
|
+
slug: { current: "contact" },
|
|
820
|
+
_updatedAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
821
|
+
seo: {
|
|
822
|
+
openGraph: {
|
|
823
|
+
title: "Get in Touch"
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
_id: "preview-post-5",
|
|
829
|
+
_type: "post",
|
|
830
|
+
title: "Mobile Optimization Guide",
|
|
831
|
+
slug: { current: "mobile-optimization" },
|
|
832
|
+
_updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
833
|
+
seo: {
|
|
834
|
+
title: "Mobile Optimization Guide: Best Practices for Responsive Design",
|
|
835
|
+
description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
|
|
836
|
+
keywords: ["mobile", "optimization", "responsive", "performance"],
|
|
837
|
+
metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
|
|
838
|
+
openGraph: {
|
|
839
|
+
title: "Mobile Optimization Best Practices",
|
|
840
|
+
description: "Master mobile web optimization",
|
|
841
|
+
image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
|
|
842
|
+
type: "article"
|
|
843
|
+
},
|
|
844
|
+
twitter: {
|
|
845
|
+
title: "Mobile Optimization Tips",
|
|
846
|
+
description: "Responsive design best practices",
|
|
847
|
+
image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
|
|
848
|
+
card: "summary_large_image"
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
];
|
|
853
|
+
return dummyDocs.map((doc) => __spreadProps(__spreadValues({}, doc), {
|
|
854
|
+
health: calculateHealthScore(doc)
|
|
855
|
+
}));
|
|
856
|
+
};
|
|
857
|
+
var SeoHealthDashboard = ({
|
|
858
|
+
icon = "\u{1F4CA}",
|
|
859
|
+
title = "SEO Health Dashboard",
|
|
860
|
+
description = "Monitor and optimize SEO fields across all your documents",
|
|
861
|
+
showTypeColumn = true,
|
|
862
|
+
showDocumentId = true,
|
|
863
|
+
queryTypes,
|
|
864
|
+
queryRequireSeo = true,
|
|
865
|
+
customQuery,
|
|
866
|
+
apiVersion = "2023-01-01",
|
|
867
|
+
licenseKey,
|
|
868
|
+
typeLabels,
|
|
869
|
+
typeColumnMode = "badge",
|
|
870
|
+
titleField,
|
|
871
|
+
docBadge,
|
|
872
|
+
loadingLicense,
|
|
873
|
+
loadingDocuments,
|
|
874
|
+
noDocuments,
|
|
875
|
+
previewMode = false,
|
|
876
|
+
openInPane = false,
|
|
877
|
+
structureTool
|
|
878
|
+
}) => {
|
|
879
|
+
const client = (0, import_sanity.useClient)({ apiVersion });
|
|
880
|
+
const [licenseStatus, setLicenseStatus] = (0, import_react.useState)("loading");
|
|
881
|
+
const [documents, setDocuments] = (0, import_react.useState)([]);
|
|
882
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
883
|
+
const [searchQuery, setSearchQuery] = (0, import_react.useState)("");
|
|
884
|
+
const [filterStatus, setFilterStatus] = (0, import_react.useState)("all");
|
|
885
|
+
const [filterType, setFilterType] = (0, import_react.useState)("all");
|
|
886
|
+
const [sortBy, setSortBy] = (0, import_react.useState)("score");
|
|
887
|
+
const [activePopover, setActivePopover] = (0, import_react.useState)(null);
|
|
888
|
+
const VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
|
|
889
|
+
const CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
890
|
+
const validateLicense = (0, import_react.useCallback)(
|
|
891
|
+
async (forceRefresh = false) => {
|
|
892
|
+
var _a;
|
|
893
|
+
if (previewMode) {
|
|
894
|
+
setLicenseStatus("valid");
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (!licenseKey) {
|
|
898
|
+
setLicenseStatus("invalid");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const projectId = (_a = client.config().projectId) != null ? _a : "";
|
|
902
|
+
const cacheKey = `seofields_license_${projectId}`;
|
|
903
|
+
if (forceRefresh) {
|
|
904
|
+
try {
|
|
905
|
+
sessionStorage.removeItem(cacheKey);
|
|
906
|
+
} catch (e) {
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (!forceRefresh) {
|
|
910
|
+
try {
|
|
911
|
+
const cached = sessionStorage.getItem(cacheKey);
|
|
912
|
+
if (cached) {
|
|
913
|
+
const { valid, ts } = JSON.parse(cached);
|
|
914
|
+
if (Date.now() - ts < CACHE_TTL_MS) {
|
|
915
|
+
setLicenseStatus(valid ? "valid" : "invalid");
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
} catch (e) {
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
setLicenseStatus("loading");
|
|
923
|
+
try {
|
|
924
|
+
const res = await fetch(VALIDATION_ENDPOINT, {
|
|
925
|
+
method: "POST",
|
|
926
|
+
headers: { "Content-Type": "application/json" },
|
|
927
|
+
body: JSON.stringify({ licenseKey, projectId })
|
|
928
|
+
});
|
|
929
|
+
const valid = res.ok;
|
|
930
|
+
setLicenseStatus(valid ? "valid" : "invalid");
|
|
931
|
+
try {
|
|
932
|
+
sessionStorage.setItem(cacheKey, JSON.stringify({ valid, ts: Date.now() }));
|
|
933
|
+
} catch (e) {
|
|
934
|
+
}
|
|
935
|
+
} catch (e) {
|
|
936
|
+
setLicenseStatus("valid");
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
940
|
+
[licenseKey, previewMode]
|
|
941
|
+
);
|
|
942
|
+
(0, import_react.useEffect)(() => {
|
|
943
|
+
validateLicense();
|
|
944
|
+
}, [licenseKey, previewMode]);
|
|
945
|
+
const handleMouseEnterIssues = (el, issues) => {
|
|
946
|
+
if (!el) return;
|
|
947
|
+
const rect = el.getBoundingClientRect();
|
|
948
|
+
const popoverWidth = 280;
|
|
949
|
+
const viewportWidth = window.innerWidth;
|
|
950
|
+
let left = rect.left;
|
|
951
|
+
if (left + popoverWidth > viewportWidth - 10) left = viewportWidth - popoverWidth - 10;
|
|
952
|
+
if (left < 10) left = 10;
|
|
953
|
+
setActivePopover({ top: rect.top, left, issues });
|
|
954
|
+
};
|
|
955
|
+
(0, import_react.useEffect)(() => {
|
|
956
|
+
const fetchDocuments = async () => {
|
|
957
|
+
try {
|
|
958
|
+
setLoading(true);
|
|
959
|
+
if (previewMode) {
|
|
960
|
+
setDocuments(generateDummyData());
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
let groqQuery;
|
|
964
|
+
let params = {};
|
|
965
|
+
if (customQuery) {
|
|
966
|
+
groqQuery = customQuery;
|
|
967
|
+
} else if (queryTypes && queryTypes.length > 0) {
|
|
968
|
+
const seoFilter = queryRequireSeo ? " && seo != null" : "";
|
|
969
|
+
const titleProj = buildTitleProjection(titleField);
|
|
970
|
+
groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
|
|
971
|
+
_id,
|
|
972
|
+
_type,
|
|
973
|
+
${titleProj},
|
|
974
|
+
slug,
|
|
975
|
+
seo,
|
|
976
|
+
_updatedAt
|
|
977
|
+
}`;
|
|
978
|
+
params = { types: queryTypes };
|
|
979
|
+
} else {
|
|
980
|
+
const titleProj = buildTitleProjection(titleField);
|
|
981
|
+
groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
|
|
982
|
+
_id,
|
|
983
|
+
_type,
|
|
984
|
+
${titleProj},
|
|
985
|
+
slug,
|
|
986
|
+
seo,
|
|
987
|
+
_updatedAt
|
|
988
|
+
}`;
|
|
989
|
+
}
|
|
990
|
+
const result = await client.fetch(groqQuery, params, { perspective: "published" });
|
|
991
|
+
const docsWithHealth = result.map((doc) => __spreadProps(__spreadValues({}, doc), {
|
|
992
|
+
health: calculateHealthScore(doc)
|
|
993
|
+
}));
|
|
994
|
+
setDocuments(docsWithHealth);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
console.error("Error fetching documents:", error);
|
|
997
|
+
} finally {
|
|
998
|
+
setLoading(false);
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
fetchDocuments();
|
|
1002
|
+
}, [
|
|
1003
|
+
client,
|
|
1004
|
+
customQuery,
|
|
1005
|
+
queryRequireSeo,
|
|
1006
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1007
|
+
JSON.stringify(queryTypes),
|
|
1008
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1009
|
+
JSON.stringify(titleField),
|
|
1010
|
+
previewMode
|
|
1011
|
+
]);
|
|
1012
|
+
const uniqueDocumentTypes = (0, import_react.useMemo)(() => {
|
|
1013
|
+
const types2 = new Set(documents.map((doc) => doc._type));
|
|
1014
|
+
return Array.from(types2).sort();
|
|
1015
|
+
}, [documents]);
|
|
1016
|
+
const filteredAndSortedDocs = (0, import_react.useMemo)(() => {
|
|
1017
|
+
let filtered = documents;
|
|
1018
|
+
if (searchQuery) {
|
|
1019
|
+
filtered = filtered.filter(
|
|
1020
|
+
(doc) => {
|
|
1021
|
+
var _a, _b;
|
|
1022
|
+
return ((_a = doc.title) == null ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase())) || ((_b = doc._id) == null ? void 0 : _b.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
if (filterStatus !== "all") {
|
|
1027
|
+
filtered = filtered.filter((doc) => doc.health.status === filterStatus);
|
|
1028
|
+
}
|
|
1029
|
+
if (filterType !== "all") {
|
|
1030
|
+
filtered = filtered.filter((doc) => doc._type === filterType);
|
|
1031
|
+
}
|
|
1032
|
+
const sorted = [...filtered].sort((a, b) => {
|
|
1033
|
+
if (sortBy === "score") {
|
|
1034
|
+
return b.health.score - a.health.score;
|
|
1035
|
+
}
|
|
1036
|
+
return (a.title || "").localeCompare(b.title || "");
|
|
1037
|
+
});
|
|
1038
|
+
return sorted;
|
|
1039
|
+
}, [documents, searchQuery, filterStatus, filterType, sortBy]);
|
|
1040
|
+
const stats = (0, import_react.useMemo)(() => {
|
|
1041
|
+
const total = documents.length;
|
|
1042
|
+
const excellent = documents.filter((d) => d.health.score >= 80).length;
|
|
1043
|
+
const good = documents.filter((d) => d.health.score >= 60 && d.health.score < 80).length;
|
|
1044
|
+
const fair = documents.filter((d) => d.health.score >= 40 && d.health.score < 60).length;
|
|
1045
|
+
const poor = documents.filter((d) => d.health.score > 0 && d.health.score < 40).length;
|
|
1046
|
+
const missing = documents.filter((d) => d.health.score === 0).length;
|
|
1047
|
+
const avgScore = total > 0 ? Math.round(documents.reduce((sum, d) => sum + d.health.score, 0) / total) : 0;
|
|
1048
|
+
return { total, excellent, good, fair, poor, missing, avgScore };
|
|
1049
|
+
}, [documents]);
|
|
1050
|
+
const handleMouseLeave = (0, import_react.useCallback)(() => {
|
|
1051
|
+
setActivePopover(null);
|
|
1052
|
+
}, []);
|
|
1053
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DashboardContainer, { children: [
|
|
1054
|
+
licenseStatus === "loading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(LoadingState, { style: { padding: "80px 24px" }, children: [
|
|
1055
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
1056
|
+
loadingLicense != null ? loadingLicense : "Verifying license\u2026"
|
|
1057
|
+
] }),
|
|
1058
|
+
licenseStatus === "invalid" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1059
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeLock, { children: "\u274C" }),
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeTitle, { children: "Invalid License Key" }),
|
|
1061
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeText, { children: "The license key you provided is invalid or has been revoked. Please check your key and update it in the plugin config." }),
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeCode, { children: `seofields({
|
|
1063
|
+
healthDashboard: {
|
|
1064
|
+
licenseKey: 'YOUR_LICENSE_KEY', // \u2190 replace with a valid key
|
|
1065
|
+
},
|
|
1066
|
+
})` }),
|
|
1067
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1068
|
+
UpgradeButton,
|
|
1069
|
+
{
|
|
1070
|
+
href: "https://sanity-plugin-seofields.thehardik.in",
|
|
1071
|
+
target: "_blank",
|
|
1072
|
+
rel: "noopener noreferrer",
|
|
1073
|
+
children: "Get a New License Key \u2192"
|
|
1074
|
+
}
|
|
1075
|
+
),
|
|
1076
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReloadButton, { onClick: () => validateLicense(true), children: "Click here If You Just Updated Your Key" })
|
|
1078
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeLock, { children: "\u{1F512}" }),
|
|
1080
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeTitle, { children: "SEO Health Dashboard" }),
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeText, { children: "This feature requires a license key. Add your key to the plugin config to unlock the full dashboard." }),
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpgradeCode, { children: `// sanity.config.ts
|
|
1083
|
+
import { seofields } from 'sanity-plugin-seofields'
|
|
1084
|
+
|
|
1085
|
+
export default defineConfig({
|
|
1086
|
+
plugins: [
|
|
1087
|
+
seofields({
|
|
1088
|
+
healthDashboard: {
|
|
1089
|
+
licenseKey: 'SEOF-XXXX-XXXX-XXXX',
|
|
1090
|
+
},
|
|
1091
|
+
}),
|
|
1092
|
+
],
|
|
1093
|
+
})` }),
|
|
1094
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1095
|
+
UpgradeButton,
|
|
1096
|
+
{
|
|
1097
|
+
href: "https://sanity-plugin-seofields.thehardik.in",
|
|
1098
|
+
target: "_blank",
|
|
1099
|
+
rel: "noopener noreferrer",
|
|
1100
|
+
children: "Get a License Key \u2192"
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] }) }) }),
|
|
1104
|
+
licenseStatus === "valid" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageHeader, { children: [
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageTitle, { children: [
|
|
1107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1108
|
+
icon,
|
|
1109
|
+
" ",
|
|
1110
|
+
title
|
|
1111
|
+
] }),
|
|
1112
|
+
previewMode && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PreviewBadge, { children: "Preview Mode" })
|
|
1113
|
+
] }),
|
|
1114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageSubtitle, { children: description })
|
|
1115
|
+
] }),
|
|
1116
|
+
!loading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatsGrid, { children: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { children: [
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Total Docs" }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatValue, { children: stats.total })
|
|
1120
|
+
] }),
|
|
1121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { children: [
|
|
1122
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Avg Score" }),
|
|
1123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatValue, { children: [
|
|
1124
|
+
stats.avgScore,
|
|
1125
|
+
"%"
|
|
1126
|
+
] })
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { $accent: "#10b981", children: [
|
|
1129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Excellent (80+)" }),
|
|
1130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatValue, { children: stats.excellent })
|
|
1131
|
+
] }),
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { $accent: "#f59e0b", children: [
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Good (60\u201379)" }),
|
|
1134
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatValue, { children: stats.good })
|
|
1135
|
+
] }),
|
|
1136
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { $accent: "#f97316", children: [
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Fair (40\u201359)" }),
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatValue, { children: stats.fair })
|
|
1139
|
+
] }),
|
|
1140
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(StatCard, { $accent: "#ef4444", children: [
|
|
1141
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatLabel, { children: "Poor / Missing" }),
|
|
1142
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatValue, { children: stats.poor + stats.missing })
|
|
1143
|
+
] })
|
|
1144
|
+
] }),
|
|
1145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ControlsBar, { children: [
|
|
1146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SearchWrapper, { children: [
|
|
1147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIconSvg, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1148
|
+
"path",
|
|
1149
|
+
{
|
|
1150
|
+
fillRule: "evenodd",
|
|
1151
|
+
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",
|
|
1152
|
+
clipRule: "evenodd"
|
|
1153
|
+
}
|
|
1154
|
+
) }) }),
|
|
1155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1156
|
+
SearchInput,
|
|
1157
|
+
{
|
|
1158
|
+
placeholder: "Search documents...",
|
|
1159
|
+
value: searchQuery,
|
|
1160
|
+
onChange: (e) => setSearchQuery(e.currentTarget.value)
|
|
1161
|
+
}
|
|
1162
|
+
)
|
|
1163
|
+
] }),
|
|
1164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1165
|
+
StyledSelect,
|
|
1166
|
+
{
|
|
1167
|
+
value: filterStatus,
|
|
1168
|
+
onChange: (e) => setFilterStatus(e.currentTarget.value),
|
|
1169
|
+
children: [
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "All Status" }),
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "excellent", children: "Excellent" }),
|
|
1172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "good", children: "Good" }),
|
|
1173
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "fair", children: "Fair" }),
|
|
1174
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "poor", children: "Poor" }),
|
|
1175
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "missing", children: "Missing" })
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
),
|
|
1179
|
+
uniqueDocumentTypes.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1180
|
+
StyledSelect,
|
|
1181
|
+
{
|
|
1182
|
+
value: filterType,
|
|
1183
|
+
onChange: (e) => setFilterType(e.currentTarget.value),
|
|
1184
|
+
children: [
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "All Types" }),
|
|
1186
|
+
uniqueDocumentTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: type, children: resolveTypeLabel(type, typeLabels) }, type))
|
|
1187
|
+
]
|
|
1188
|
+
}
|
|
1189
|
+
),
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1191
|
+
StyledSelect,
|
|
1192
|
+
{
|
|
1193
|
+
value: sortBy,
|
|
1194
|
+
onChange: (e) => setSortBy(e.currentTarget.value),
|
|
1195
|
+
children: [
|
|
1196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "score", children: "Sort by Score" }),
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "title", children: "Sort by Title" })
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
)
|
|
1201
|
+
] }),
|
|
1202
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableCard, { children: [
|
|
1203
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(LoadingState, { children: [
|
|
1204
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
1205
|
+
loadingDocuments != null ? loadingDocuments : "Loading documents\u2026"
|
|
1206
|
+
] }),
|
|
1207
|
+
!loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyState, { children: noDocuments != null ? noDocuments : "No documents found" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableHeader, { children: [
|
|
1209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColTitle, { children: "Title" }),
|
|
1210
|
+
showTypeColumn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColType, { children: "Type" }),
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColScore, { children: "Score" }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColIssues, { children: "Top Issues" })
|
|
1213
|
+
] }),
|
|
1214
|
+
filteredAndSortedDocs.map((doc) => {
|
|
1215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColTitle, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TitleWrapper, { children: [
|
|
1217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TitleCell, { children: [
|
|
1218
|
+
openInPane ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocTitleAnchor, { id: doc._id, type: doc._type, structureTool, children: doc.title || "Untitled" }),
|
|
1219
|
+
showDocumentId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocId, { children: doc._id })
|
|
1220
|
+
] }),
|
|
1221
|
+
docBadge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1222
|
+
DocBadgeRenderer,
|
|
1223
|
+
{
|
|
1224
|
+
doc,
|
|
1225
|
+
docBadge
|
|
1226
|
+
}
|
|
1227
|
+
)
|
|
1228
|
+
] }) }),
|
|
1229
|
+
showTypeColumn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : (() => {
|
|
1230
|
+
const typeColor = getTypeColor(doc._type);
|
|
1231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, typeLabels) });
|
|
1232
|
+
})() }),
|
|
1233
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColScore, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ScoreBadge, { $score: doc.health.score, children: [
|
|
1234
|
+
doc.health.score,
|
|
1235
|
+
"%"
|
|
1236
|
+
] }) }),
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ColIssues, { children: [
|
|
1238
|
+
doc.health.issues.slice(0, 2).map((issue) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(IssueTag, { children: [
|
|
1239
|
+
"\u2022 ",
|
|
1240
|
+
issue
|
|
1241
|
+
] }, `issue-${doc._id}-${issue}`)),
|
|
1242
|
+
doc.health.issues.length > 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1243
|
+
MoreIssuesWrapper,
|
|
1244
|
+
{
|
|
1245
|
+
onMouseEnter: function(e) {
|
|
1246
|
+
handleMouseEnterIssues(
|
|
1247
|
+
e.currentTarget,
|
|
1248
|
+
doc.health.issues
|
|
1249
|
+
);
|
|
1250
|
+
},
|
|
1251
|
+
onMouseLeave: handleMouseLeave,
|
|
1252
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MoreIssues, { children: [
|
|
1253
|
+
"+",
|
|
1254
|
+
doc.health.issues.length - 2,
|
|
1255
|
+
" more issues"
|
|
1256
|
+
] })
|
|
1257
|
+
}
|
|
1258
|
+
)
|
|
1259
|
+
] })
|
|
1260
|
+
] }, doc._id);
|
|
1261
|
+
})
|
|
1262
|
+
] }))
|
|
1263
|
+
] }),
|
|
1264
|
+
activePopover && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1265
|
+
IssuesPopover,
|
|
1266
|
+
{
|
|
1267
|
+
style: {
|
|
1268
|
+
top: activePopover.top,
|
|
1269
|
+
left: activePopover.left,
|
|
1270
|
+
transform: "translateY(calc(-100% - 10px))"
|
|
1271
|
+
},
|
|
1272
|
+
children: activePopover.issues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PopoverIssueItem, { children: [
|
|
1273
|
+
"\u26A0\uFE0F ",
|
|
1274
|
+
issue
|
|
1275
|
+
] }, issue))
|
|
1276
|
+
}
|
|
1277
|
+
),
|
|
1278
|
+
" "
|
|
1279
|
+
] }),
|
|
1280
|
+
" "
|
|
1281
|
+
] });
|
|
1282
|
+
};
|
|
1283
|
+
var SeoHealthDashboard_default = SeoHealthDashboard;
|
|
1284
|
+
|
|
1285
|
+
// src/components/SeoHealthTool.tsx
|
|
1286
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1287
|
+
var SeoHealthTool = (props) => {
|
|
1288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SeoHealthDashboard_default, __spreadValues({}, props));
|
|
1289
|
+
};
|
|
1290
|
+
var SeoHealthTool_default = SeoHealthTool;
|
|
1291
|
+
|
|
1292
|
+
// src/schemas/index.ts
|
|
1293
|
+
var import_sanity11 = require("sanity");
|
|
1294
|
+
|
|
1295
|
+
// src/components/meta/MetaDescription.tsx
|
|
1296
|
+
var import_ui = require("@sanity/ui");
|
|
1297
|
+
var import_react2 = require("react");
|
|
1298
|
+
var import_sanity2 = require("sanity");
|
|
1299
|
+
|
|
1300
|
+
// src/utils/seoUtils.ts
|
|
1301
|
+
var stopWords = ["the", "a", "an", "and", "or", "but"];
|
|
1302
|
+
var hasMatchingKeyword = (title, keywordList) => {
|
|
1303
|
+
if (!title || keywordList.length === 0) return false;
|
|
1304
|
+
const lowerTitle = title.toLowerCase();
|
|
1305
|
+
return keywordList.some((keyword) => keyword && lowerTitle.includes(keyword.toLowerCase()));
|
|
1306
|
+
};
|
|
1307
|
+
var hasKeywordOveruse = (title, keywordList, maxOccurrences = 3) => {
|
|
1308
|
+
if (!title || keywordList.length === 0) return false;
|
|
1309
|
+
const lowerTitle = title.toLowerCase();
|
|
1310
|
+
return keywordList.some((keyword) => {
|
|
1311
|
+
if (!keyword) return false;
|
|
1312
|
+
const matches = lowerTitle.match(new RegExp(keyword.toLowerCase(), "g"));
|
|
1313
|
+
return matches ? matches.length > maxOccurrences : false;
|
|
1314
|
+
});
|
|
1315
|
+
};
|
|
1316
|
+
var startsWithStopWord = (title) => {
|
|
1317
|
+
if (!title) return false;
|
|
1318
|
+
const firstWord = title.trim().split(" ")[0].toLowerCase();
|
|
1319
|
+
return stopWords.includes(firstWord);
|
|
1320
|
+
};
|
|
1321
|
+
var truncate = (text, maxLength) => text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
|
|
1322
|
+
var hasExcessivePunctuation = (title) => /[!@#$%^&*]{2,}/.test(title);
|
|
1323
|
+
var getMetaTitleValidationMessages = (title, keywords, isParentseoField) => {
|
|
1324
|
+
const feedback = [];
|
|
1325
|
+
const minChar = 50;
|
|
1326
|
+
const maxChar = 60;
|
|
1327
|
+
const charCount = (title == null ? void 0 : title.length) || 0;
|
|
1328
|
+
if (!(title == null ? void 0 : title.trim())) {
|
|
1329
|
+
feedback.push({ text: "Meta Title is empty. Add content to improve SEO.", color: "red" });
|
|
1330
|
+
return feedback;
|
|
1331
|
+
}
|
|
1332
|
+
if (charCount < minChar)
|
|
1333
|
+
feedback.push({
|
|
1334
|
+
text: `Title is ${charCount} characters \u2014 below recommended ${minChar}.`,
|
|
1335
|
+
color: "orange"
|
|
1336
|
+
});
|
|
1337
|
+
else if (charCount > maxChar)
|
|
1338
|
+
feedback.push({
|
|
1339
|
+
text: `Title is ${charCount} characters \u2014 exceeds recommended ${maxChar}.`,
|
|
1340
|
+
color: "red"
|
|
1341
|
+
});
|
|
1342
|
+
else feedback.push({ text: `Title length (${charCount}) looks good for SEO.`, color: "green" });
|
|
1343
|
+
if (isParentseoField) {
|
|
1344
|
+
if (keywords.length > 0) {
|
|
1345
|
+
const hasKeyword = hasMatchingKeyword(title, keywords);
|
|
1346
|
+
feedback.push({
|
|
1347
|
+
text: hasKeyword ? "Keyword found in title \u2014 good job!" : "Keywords defined but missing in title.",
|
|
1348
|
+
color: hasKeyword ? "green" : "red"
|
|
1349
|
+
});
|
|
1350
|
+
if (hasKeywordOveruse(title, keywords)) {
|
|
1351
|
+
feedback.push({
|
|
1352
|
+
text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
|
|
1353
|
+
color: "orange"
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
} else {
|
|
1357
|
+
feedback.push({
|
|
1358
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1359
|
+
color: "orange"
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (startsWithStopWord(title))
|
|
1364
|
+
feedback.push({ text: "Title starts with a stop word \u2014 consider rephrasing.", color: "orange" });
|
|
1365
|
+
if (hasExcessivePunctuation(title))
|
|
1366
|
+
feedback.push({ text: "Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
|
|
1367
|
+
return feedback;
|
|
1368
|
+
};
|
|
1369
|
+
var getMetaDescriptionValidationMessages = (description, keywords, isParentseoField) => {
|
|
1370
|
+
const feedback = [];
|
|
1371
|
+
const minChar = 120;
|
|
1372
|
+
const maxChar = 160;
|
|
1373
|
+
const charCount = (description == null ? void 0 : description.length) || 0;
|
|
1374
|
+
if (!(description == null ? void 0 : description.trim())) {
|
|
1375
|
+
feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" });
|
|
1376
|
+
return feedback;
|
|
1377
|
+
}
|
|
1378
|
+
if (charCount < minChar)
|
|
1379
|
+
feedback.push({
|
|
1380
|
+
text: `Description is ${charCount} chars \u2014 below recommended ${minChar}.`,
|
|
1381
|
+
color: "orange"
|
|
1382
|
+
});
|
|
1383
|
+
else if (charCount > maxChar)
|
|
1384
|
+
feedback.push({
|
|
1385
|
+
text: `Description is ${charCount} chars \u2014 exceeds recommended ${maxChar}.`,
|
|
1386
|
+
color: "red"
|
|
1387
|
+
});
|
|
1388
|
+
else
|
|
1389
|
+
feedback.push({ text: `Description length (${charCount}) looks good for SEO.`, color: "green" });
|
|
1390
|
+
if (isParentseoField) {
|
|
1391
|
+
if (keywords.length > 0) {
|
|
1392
|
+
const hasKeyword = hasMatchingKeyword(description, keywords);
|
|
1393
|
+
feedback.push({
|
|
1394
|
+
text: hasKeyword ? "Keyword found in description \u2014 good job!" : "Keywords defined but missing in description.",
|
|
1395
|
+
color: hasKeyword ? "green" : "red"
|
|
1396
|
+
});
|
|
1397
|
+
if (hasKeywordOveruse(description, keywords)) {
|
|
1398
|
+
feedback.push({
|
|
1399
|
+
text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
|
|
1400
|
+
color: "orange"
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
} else {
|
|
1404
|
+
feedback.push({
|
|
1405
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1406
|
+
color: "orange"
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (startsWithStopWord(description))
|
|
1411
|
+
feedback.push({
|
|
1412
|
+
text: "Description starts with a stop word \u2014 consider rephrasing.",
|
|
1413
|
+
color: "orange"
|
|
1414
|
+
});
|
|
1415
|
+
if (hasExcessivePunctuation(description))
|
|
1416
|
+
feedback.push({
|
|
1417
|
+
text: "Description contains excessive punctuation \u2014 simplify it.",
|
|
1418
|
+
color: "orange"
|
|
1419
|
+
});
|
|
1420
|
+
return feedback;
|
|
1421
|
+
};
|
|
1422
|
+
var getOgTitleValidation = (title, keywords = [], isParentseoField) => {
|
|
1423
|
+
const feedback = [];
|
|
1424
|
+
const min = 40;
|
|
1425
|
+
const max = 60;
|
|
1426
|
+
const count = (title == null ? void 0 : title.length) || 0;
|
|
1427
|
+
if (!(title == null ? void 0 : title.trim())) {
|
|
1428
|
+
feedback.push({ text: "OG Title is empty. Add content for better social preview.", color: "red" });
|
|
1429
|
+
return feedback;
|
|
1430
|
+
}
|
|
1431
|
+
if (count < min)
|
|
1432
|
+
feedback.push({
|
|
1433
|
+
text: `OG Title is ${count} chars \u2014 shorter than recommended ${min}.`,
|
|
1434
|
+
color: "orange"
|
|
1435
|
+
});
|
|
1436
|
+
else if (count > max)
|
|
1437
|
+
feedback.push({ text: `OG Title is ${count} chars \u2014 exceeds recommended ${max}.`, color: "red" });
|
|
1438
|
+
else feedback.push({ text: `OG Title length (${count}) looks good.`, color: "green" });
|
|
1439
|
+
if (isParentseoField) {
|
|
1440
|
+
if (keywords.length > 0) {
|
|
1441
|
+
const hasKeyword = hasMatchingKeyword(title, keywords);
|
|
1442
|
+
feedback.push({
|
|
1443
|
+
text: hasKeyword ? "Keyword found in OG title \u2014 good job!" : "Keywords defined but missing in OG title.",
|
|
1444
|
+
color: hasKeyword ? "green" : "red"
|
|
1445
|
+
});
|
|
1446
|
+
if (hasKeywordOveruse(title, keywords)) {
|
|
1447
|
+
feedback.push({
|
|
1448
|
+
text: "Keyword appears too many times in OG title \u2014 avoid keyword stuffing.",
|
|
1449
|
+
color: "orange"
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
} else {
|
|
1453
|
+
feedback.push({
|
|
1454
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1455
|
+
color: "orange"
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (startsWithStopWord(title))
|
|
1460
|
+
feedback.push({
|
|
1461
|
+
text: "OG Title starts with a stop word \u2014 consider rephrasing.",
|
|
1462
|
+
color: "orange"
|
|
1463
|
+
});
|
|
1464
|
+
if (hasExcessivePunctuation(title))
|
|
1465
|
+
feedback.push({ text: "OG Title contains excessive punctuation \u2014 simplify it.", color: "orange" });
|
|
1466
|
+
return feedback;
|
|
1467
|
+
};
|
|
1468
|
+
var getOgDescriptionValidation = (desc, keywords = [], isParentseoField) => {
|
|
1469
|
+
const feedback = [];
|
|
1470
|
+
const min = 90;
|
|
1471
|
+
const max = 120;
|
|
1472
|
+
const count = (desc == null ? void 0 : desc.length) || 0;
|
|
1473
|
+
if (!(desc == null ? void 0 : desc.trim())) {
|
|
1474
|
+
feedback.push({
|
|
1475
|
+
text: "OG Description is empty. Add content for better social preview.",
|
|
1476
|
+
color: "red"
|
|
1477
|
+
});
|
|
1478
|
+
return feedback;
|
|
1479
|
+
}
|
|
1480
|
+
if (count < min)
|
|
1481
|
+
feedback.push({
|
|
1482
|
+
text: `OG Description is ${count} chars \u2014 shorter than recommended ${min}.`,
|
|
1483
|
+
color: "orange"
|
|
1484
|
+
});
|
|
1485
|
+
else if (count > max)
|
|
1486
|
+
feedback.push({
|
|
1487
|
+
text: `OG Description is ${count} chars \u2014 exceeds recommended ${max}.`,
|
|
1488
|
+
color: "red"
|
|
1489
|
+
});
|
|
1490
|
+
else feedback.push({ text: `OG Description length (${count}) looks good.`, color: "green" });
|
|
1491
|
+
if (isParentseoField) {
|
|
1492
|
+
if (keywords.length > 0) {
|
|
1493
|
+
const hasKeyword = hasMatchingKeyword(desc, keywords);
|
|
1494
|
+
feedback.push({
|
|
1495
|
+
text: hasKeyword ? "Keyword found in OG description \u2014 good job!" : "Keywords defined but missing in OG description.",
|
|
1496
|
+
color: hasKeyword ? "green" : "red"
|
|
1497
|
+
});
|
|
1498
|
+
if (hasKeywordOveruse(desc, keywords)) {
|
|
1499
|
+
feedback.push({
|
|
1500
|
+
text: "Keyword appears too many times in OG description \u2014 avoid keyword stuffing.",
|
|
1501
|
+
color: "orange"
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
} else {
|
|
1505
|
+
feedback.push({
|
|
1506
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1507
|
+
color: "orange"
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (startsWithStopWord(desc))
|
|
1512
|
+
feedback.push({
|
|
1513
|
+
text: "OG Description starts with a stop word \u2014 consider rephrasing.",
|
|
1514
|
+
color: "orange"
|
|
1515
|
+
});
|
|
1516
|
+
if (hasExcessivePunctuation(desc))
|
|
1517
|
+
feedback.push({
|
|
1518
|
+
text: "OG Description contains excessive punctuation \u2014 simplify it.",
|
|
1519
|
+
color: "orange"
|
|
1520
|
+
});
|
|
1521
|
+
return feedback;
|
|
1522
|
+
};
|
|
1523
|
+
var getTwitterTitleValidation = (title, keywords = [], isParentseoField) => {
|
|
1524
|
+
const feedback = [];
|
|
1525
|
+
const min = 30;
|
|
1526
|
+
const max = 70;
|
|
1527
|
+
const count = (title == null ? void 0 : title.length) || 0;
|
|
1528
|
+
if (!(title == null ? void 0 : title.trim())) {
|
|
1529
|
+
feedback.push({ text: "X Title is empty. Add content for better SEO.", color: "red" });
|
|
1530
|
+
return feedback;
|
|
1531
|
+
}
|
|
1532
|
+
if (count < min)
|
|
1533
|
+
feedback.push({
|
|
1534
|
+
text: `X Title is ${count} chars \u2014 shorter than recommended ${min}.`,
|
|
1535
|
+
color: "orange"
|
|
1536
|
+
});
|
|
1537
|
+
else if (count > max)
|
|
1538
|
+
feedback.push({
|
|
1539
|
+
text: `X Title is ${count} chars \u2014 exceeds recommended ${max}.`,
|
|
1540
|
+
color: "red"
|
|
1541
|
+
});
|
|
1542
|
+
else feedback.push({ text: `X Title length (${count}) looks good.`, color: "green" });
|
|
1543
|
+
if (isParentseoField) {
|
|
1544
|
+
if (keywords.length > 0) {
|
|
1545
|
+
const hasKeyword = hasMatchingKeyword(title, keywords);
|
|
1546
|
+
feedback.push({
|
|
1547
|
+
text: hasKeyword ? "Keyword found in X title \u2014 good job!" : "Keywords defined but missing in X title.",
|
|
1548
|
+
color: hasKeyword ? "green" : "red"
|
|
1549
|
+
});
|
|
1550
|
+
} else {
|
|
1551
|
+
feedback.push({
|
|
1552
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1553
|
+
color: "orange"
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (/[!@#$%^&*]{2,}/.test(title))
|
|
1558
|
+
feedback.push({ text: "X Title has excessive punctuation \u2014 simplify it.", color: "orange" });
|
|
1559
|
+
return feedback;
|
|
1560
|
+
};
|
|
1561
|
+
var getTwitterDescriptionValidation = (desc, keywords = [], isParentseoField) => {
|
|
1562
|
+
const feedback = [];
|
|
1563
|
+
const min = 50;
|
|
1564
|
+
const max = 200;
|
|
1565
|
+
const count = (desc == null ? void 0 : desc.length) || 0;
|
|
1566
|
+
if (!(desc == null ? void 0 : desc.trim())) {
|
|
1567
|
+
feedback.push({ text: "X Description is empty. Add content for better SEO.", color: "red" });
|
|
1568
|
+
return feedback;
|
|
1569
|
+
}
|
|
1570
|
+
if (count < min)
|
|
1571
|
+
feedback.push({
|
|
1572
|
+
text: `X Description is ${count} chars \u2014 shorter than recommended ${min}.`,
|
|
1573
|
+
color: "orange"
|
|
1574
|
+
});
|
|
1575
|
+
else if (count > max)
|
|
1576
|
+
feedback.push({
|
|
1577
|
+
text: `X Description is ${count} chars \u2014 exceeds recommended ${max}.`,
|
|
1578
|
+
color: "red"
|
|
1579
|
+
});
|
|
1580
|
+
else feedback.push({ text: `X Description length (${count}) looks good.`, color: "green" });
|
|
1581
|
+
if (isParentseoField) {
|
|
1582
|
+
if (keywords.length > 0) {
|
|
1583
|
+
const hasKeyword = hasMatchingKeyword(desc, keywords);
|
|
1584
|
+
feedback.push({
|
|
1585
|
+
text: hasKeyword ? "Keyword found in X description \u2014 good job!" : "Keywords defined but missing in X description.",
|
|
1586
|
+
color: hasKeyword ? "green" : "red"
|
|
1587
|
+
});
|
|
1588
|
+
} else {
|
|
1589
|
+
feedback.push({
|
|
1590
|
+
text: "No keywords defined. Consider adding relevant keywords.",
|
|
1591
|
+
color: "orange"
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
if (/[!@#$%^&*]{2,}/.test(desc))
|
|
1596
|
+
feedback.push({
|
|
1597
|
+
text: "X Description has excessive punctuation \u2014 simplify it.",
|
|
1598
|
+
color: "orange"
|
|
1599
|
+
});
|
|
1600
|
+
return feedback;
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// src/components/meta/MetaDescription.tsx
|
|
1604
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1605
|
+
var MetaDescription = (props) => {
|
|
1606
|
+
const { value, renderDefault, path } = props;
|
|
1607
|
+
const parent = (0, import_sanity2.useFormValue)([path[0]]);
|
|
1608
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
1609
|
+
const keywords = (0, import_react2.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1610
|
+
const feedbackItems = (0, import_react2.useMemo)(
|
|
1611
|
+
() => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
|
|
1612
|
+
[value, keywords, isParentseoField]
|
|
1613
|
+
);
|
|
1614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui.Stack, { space: 3, children: [
|
|
1615
|
+
renderDefault(props),
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1617
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1618
|
+
"div",
|
|
1619
|
+
{
|
|
1620
|
+
style: { width: 10, height: 10, borderRadius: "50%", backgroundColor: item.color }
|
|
1621
|
+
}
|
|
1622
|
+
),
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
1624
|
+
] }, item.text)) })
|
|
1625
|
+
] });
|
|
1626
|
+
};
|
|
1627
|
+
var MetaDescription_default = MetaDescription;
|
|
1628
|
+
|
|
1629
|
+
// src/components/meta/MetaTitle.tsx
|
|
1630
|
+
var import_ui2 = require("@sanity/ui");
|
|
1631
|
+
var import_react3 = require("react");
|
|
1632
|
+
var import_sanity3 = require("sanity");
|
|
1633
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1634
|
+
var MetaTitle = (props) => {
|
|
1635
|
+
const { value, renderDefault, path } = props;
|
|
1636
|
+
const parent = (0, import_sanity3.useFormValue)([path[0]]);
|
|
1637
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
1638
|
+
const keywords = (0, import_react3.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1639
|
+
const feedbackItems = (0, import_react3.useMemo)(
|
|
1640
|
+
() => getMetaTitleValidationMessages(value || "", keywords, isParentseoField),
|
|
1641
|
+
[value, keywords, isParentseoField]
|
|
1642
|
+
);
|
|
1643
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ui2.Stack, { space: 3, children: [
|
|
1644
|
+
renderDefault(props),
|
|
1645
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui2.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1646
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1647
|
+
"div",
|
|
1648
|
+
{
|
|
1649
|
+
style: {
|
|
1650
|
+
minWidth: 10,
|
|
1651
|
+
height: 10,
|
|
1652
|
+
borderRadius: "50%",
|
|
1653
|
+
backgroundColor: item.color
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
),
|
|
1657
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui2.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
1658
|
+
] }, item.text)) })
|
|
1659
|
+
] });
|
|
1660
|
+
};
|
|
1661
|
+
var MetaTitle_default = MetaTitle;
|
|
1662
|
+
|
|
1663
|
+
// src/components/SeoPreview.tsx
|
|
1664
|
+
var import_ui3 = require("@sanity/ui");
|
|
1665
|
+
var import_sanity4 = require("sanity");
|
|
1666
|
+
var import_styled_components2 = __toESM(require("styled-components"), 1);
|
|
1667
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1668
|
+
var PreviewContainer = import_styled_components2.default.div`
|
|
1669
|
+
max-width: 600px;
|
|
1670
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1671
|
+
background: #ffffff;
|
|
1672
|
+
border: 1px solid #dadce0;
|
|
1673
|
+
border-radius: 8px;
|
|
1674
|
+
overflow: hidden;
|
|
1675
|
+
`;
|
|
1676
|
+
var PreviewHeader = import_styled_components2.default.div`
|
|
1677
|
+
background: #f8f9fa;
|
|
1678
|
+
padding: 12px 16px;
|
|
1679
|
+
border-bottom: 1px solid #dadce0;
|
|
1680
|
+
display: flex;
|
|
1681
|
+
align-items: center;
|
|
1682
|
+
justify-content: space-between;
|
|
1683
|
+
gap: 8px;
|
|
1684
|
+
`;
|
|
1685
|
+
var PreviewBody = import_styled_components2.default.div`
|
|
1686
|
+
padding: 16px;
|
|
1687
|
+
`;
|
|
1688
|
+
var SerpUrl = import_styled_components2.default.p`
|
|
1689
|
+
margin: 0 0 4px;
|
|
1690
|
+
color: #006621;
|
|
1691
|
+
font-size: 13px;
|
|
1692
|
+
line-height: 1.4;
|
|
1693
|
+
word-break: break-word;
|
|
1694
|
+
`;
|
|
1695
|
+
var SerpTitle = import_styled_components2.default.h3`
|
|
1696
|
+
margin: 0 0 8px;
|
|
1697
|
+
color: #1a0dab;
|
|
1698
|
+
font-size: 18px;
|
|
1699
|
+
font-weight: 500;
|
|
1700
|
+
line-height: 1.4;
|
|
1701
|
+
word-break: break-word;
|
|
1702
|
+
|
|
1703
|
+
&:hover {
|
|
1704
|
+
text-decoration: underline;
|
|
1705
|
+
}
|
|
1706
|
+
`;
|
|
1707
|
+
var SerpDescription = import_styled_components2.default.p`
|
|
1708
|
+
margin: 0;
|
|
1709
|
+
color: #545454;
|
|
1710
|
+
font-size: 14px;
|
|
1711
|
+
line-height: 1.6;
|
|
1712
|
+
word-break: break-word;
|
|
1713
|
+
display: -webkit-box;
|
|
1714
|
+
-webkit-line-clamp: 2;
|
|
1715
|
+
-webkit-box-orient: vertical;
|
|
1716
|
+
overflow: hidden;
|
|
1717
|
+
`;
|
|
1718
|
+
var LiveIndicator = import_styled_components2.default.span`
|
|
1719
|
+
display: inline-flex;
|
|
1720
|
+
align-items: center;
|
|
1721
|
+
gap: 4px;
|
|
1722
|
+
font-size: 11px;
|
|
1723
|
+
font-weight: 600;
|
|
1724
|
+
text-transform: uppercase;
|
|
1725
|
+
letter-spacing: 0.05em;
|
|
1726
|
+
color: #4f46e5;
|
|
1727
|
+
background: #f0f4ff;
|
|
1728
|
+
padding: 4px 8px;
|
|
1729
|
+
border-radius: 4px;
|
|
1730
|
+
`;
|
|
1731
|
+
var SeoPreview = (props) => {
|
|
1732
|
+
var _a, _b;
|
|
1733
|
+
const { path, schemaType } = props;
|
|
1734
|
+
const { options } = schemaType;
|
|
1735
|
+
const baseUrl = (options == null ? void 0 : options.baseUrl) || "https://www.example.com";
|
|
1736
|
+
const prefixFunction = options == null ? void 0 : options.prefix;
|
|
1737
|
+
const parent = (0, import_sanity4.useFormValue)([path[0]]) || {
|
|
1738
|
+
title: "",
|
|
1739
|
+
description: "",
|
|
1740
|
+
canonicalUrl: ""
|
|
1741
|
+
};
|
|
1742
|
+
const rootDoc = (0, import_sanity4.useFormValue)([]) || {
|
|
1743
|
+
slug: { current: "" }
|
|
1744
|
+
};
|
|
1745
|
+
const slug = ((_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current) || "";
|
|
1746
|
+
const {
|
|
1747
|
+
title,
|
|
1748
|
+
description,
|
|
1749
|
+
canonicalUrl: url
|
|
1750
|
+
} = parent;
|
|
1751
|
+
const base = (_b = url || baseUrl) == null ? void 0 : _b.replace(/\/+$/, "");
|
|
1752
|
+
const slugStr = String(slug || "").replace(/^\/+/, "");
|
|
1753
|
+
const pref = String(
|
|
1754
|
+
prefixFunction ? prefixFunction(rootDoc) : ""
|
|
1755
|
+
).replace(/^\/+|\/+$/g, "");
|
|
1756
|
+
const urlPath = [pref, slugStr].filter(Boolean).join("/");
|
|
1757
|
+
const finalUrl = urlPath ? `${base}/${urlPath}` : base;
|
|
1758
|
+
const domain = (() => {
|
|
1759
|
+
try {
|
|
1760
|
+
const u = new URL(finalUrl || base);
|
|
1761
|
+
return u.hostname;
|
|
1762
|
+
} catch (e) {
|
|
1763
|
+
return "example.com";
|
|
1764
|
+
}
|
|
1765
|
+
})();
|
|
1766
|
+
const urlDisplay = `${domain}${urlPath ? ` \u203A ${urlPath.split("/").slice(-1)[0]}` : ""}`;
|
|
1767
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui3.Box, { padding: 3, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PreviewContainer, { children: [
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PreviewHeader, { children: [
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1770
|
+
"span",
|
|
1771
|
+
{
|
|
1772
|
+
style: {
|
|
1773
|
+
fontSize: "11px",
|
|
1774
|
+
color: "#5f6368",
|
|
1775
|
+
textTransform: "uppercase",
|
|
1776
|
+
letterSpacing: "0.05em"
|
|
1777
|
+
},
|
|
1778
|
+
children: "Search Preview"
|
|
1779
|
+
}
|
|
1780
|
+
),
|
|
1781
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(LiveIndicator, { children: [
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1783
|
+
"span",
|
|
1784
|
+
{
|
|
1785
|
+
style: {
|
|
1786
|
+
width: "4px",
|
|
1787
|
+
height: "4px",
|
|
1788
|
+
borderRadius: "50%",
|
|
1789
|
+
backgroundColor: "#4f46e5",
|
|
1790
|
+
display: "inline-block"
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
),
|
|
1794
|
+
"Live"
|
|
1795
|
+
] })
|
|
1796
|
+
] }),
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PreviewBody, { children: [
|
|
1798
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SerpUrl, { children: finalUrl ? urlDisplay : "example.com \u203A page-url" }),
|
|
1799
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SerpTitle, { children: title && title.length > 0 ? truncate(title, 60) : "Your SEO Title will appear here" }),
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SerpDescription, { children: description && description.length > 0 ? truncate(description, 160) : "Your meta description will show up here. Make it compelling!" })
|
|
1801
|
+
] })
|
|
1802
|
+
] }) });
|
|
1803
|
+
};
|
|
1804
|
+
var SeoPreview_default = SeoPreview;
|
|
1805
|
+
|
|
1806
|
+
// src/utils/fieldsUtils.ts
|
|
1807
|
+
var DEFAULT_FIELD_INFO = {
|
|
1808
|
+
title: {
|
|
1809
|
+
title: "Meta Title",
|
|
1810
|
+
description: "The meta title is displayed in search engine results as the clickable headline for a given result. It should be concise and accurately reflect the content of the page."
|
|
1811
|
+
},
|
|
1812
|
+
description: {
|
|
1813
|
+
title: "Meta Description",
|
|
1814
|
+
description: "Provide a concise summary of the page content. This description may be used by search engines in search results."
|
|
1815
|
+
},
|
|
1816
|
+
metaImage: {
|
|
1817
|
+
title: "Meta Image",
|
|
1818
|
+
description: "Upload an image that represents the content of the page. This image may be used in social media previews and search engine results."
|
|
1819
|
+
},
|
|
1820
|
+
keywords: {
|
|
1821
|
+
title: "Keywords",
|
|
1822
|
+
description: "Add relevant keywords for this page. These keywords will be used for SEO purposes."
|
|
1823
|
+
},
|
|
1824
|
+
canonicalUrl: {
|
|
1825
|
+
title: "Canonical URL",
|
|
1826
|
+
description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page."
|
|
1827
|
+
},
|
|
1828
|
+
robots: {
|
|
1829
|
+
title: "Robots Settings",
|
|
1830
|
+
description: "Configure how search engine crawlers should index and follow links on this page."
|
|
1831
|
+
},
|
|
1832
|
+
metaAttributes: {
|
|
1833
|
+
title: "Additional Meta Attributes",
|
|
1834
|
+
description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
|
|
1835
|
+
},
|
|
1836
|
+
openGraphTitle: {
|
|
1837
|
+
title: "Open Graph Title",
|
|
1838
|
+
description: ""
|
|
1839
|
+
},
|
|
1840
|
+
openGraphUrl: {
|
|
1841
|
+
title: "Open Graph URL",
|
|
1842
|
+
description: "The canonical URL of the page. This should be the full URL including protocol (https://)."
|
|
1843
|
+
},
|
|
1844
|
+
openGraphDescription: {
|
|
1845
|
+
title: "Open Graph Description",
|
|
1846
|
+
description: ""
|
|
1847
|
+
},
|
|
1848
|
+
openGraphSiteName: {
|
|
1849
|
+
title: "Open Graph Site Name",
|
|
1850
|
+
description: "The name of your website. This is often the site title."
|
|
1851
|
+
},
|
|
1852
|
+
openGraphType: {
|
|
1853
|
+
title: "Open Graph Type",
|
|
1854
|
+
description: "Select the type of content for Open Graph."
|
|
1855
|
+
},
|
|
1856
|
+
openGraphImageType: {
|
|
1857
|
+
title: "Image Type",
|
|
1858
|
+
description: "Choose whether to upload an image or provide an image URL for Open Graph."
|
|
1859
|
+
},
|
|
1860
|
+
openGraphImage: {
|
|
1861
|
+
title: "Open Graph Image",
|
|
1862
|
+
description: "Recommended size: 1200x630px (minimum 600x315px). Max file size: 5MB. Aspect ratio 1.91:1."
|
|
1863
|
+
},
|
|
1864
|
+
openGraphImageUrl: {
|
|
1865
|
+
title: "Open Graph Image URL",
|
|
1866
|
+
description: "Enter the full URL of the image. Ensure the image is accessible and meets the recommended size of 1200x630px (minimum 600x315px)."
|
|
1867
|
+
},
|
|
1868
|
+
twitterCard: {
|
|
1869
|
+
title: "X Card Type",
|
|
1870
|
+
description: ""
|
|
1871
|
+
},
|
|
1872
|
+
twitterSite: {
|
|
1873
|
+
title: "Site X Handle",
|
|
1874
|
+
description: "The X (formerly Twitter) handle of the website (e.g., @example)"
|
|
1875
|
+
},
|
|
1876
|
+
twitterCreator: {
|
|
1877
|
+
title: "X Creator Handle",
|
|
1878
|
+
description: "The X (formerly Twitter) handle of the content creator (e.g., @creator)"
|
|
1879
|
+
},
|
|
1880
|
+
twitterTitle: {
|
|
1881
|
+
title: "X Title",
|
|
1882
|
+
description: "The title of the content as it should appear on X (formerly Twitter)."
|
|
1883
|
+
},
|
|
1884
|
+
twitterDescription: {
|
|
1885
|
+
title: "X Description",
|
|
1886
|
+
description: "A brief description of the content for X (formerly Twitter)."
|
|
1887
|
+
},
|
|
1888
|
+
twitterImageType: {
|
|
1889
|
+
title: "X Image Type",
|
|
1890
|
+
description: "Choose whether to upload an image or provide an image URL for X (formerly Twitter)."
|
|
1891
|
+
},
|
|
1892
|
+
twitterImage: {
|
|
1893
|
+
title: "X Image",
|
|
1894
|
+
description: 'An image URL which should be at least 120x120px for "summary" card and 280x150px for "summary_large_image" card.'
|
|
1895
|
+
},
|
|
1896
|
+
twitterImageUrl: {
|
|
1897
|
+
title: "X Image URL",
|
|
1898
|
+
description: "Enter the full URL of the image for X (formerly Twitter). Ensure the image is accessible and meets the recommended size."
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
var getFieldInfo = (fieldName, fieldOverrides) => {
|
|
1902
|
+
const fieldInfo = fieldOverrides && fieldOverrides[fieldName] || DEFAULT_FIELD_INFO[fieldName];
|
|
1903
|
+
return fieldInfo ? { title: fieldInfo.title || "", description: fieldInfo.description || "" } : { title: "", description: "" };
|
|
1904
|
+
};
|
|
1905
|
+
var isFieldHidden = (fieldName, config, documentType) => {
|
|
1906
|
+
var _a, _b, _c, _d;
|
|
1907
|
+
if ((_a = config.defaultHiddenFields) == null ? void 0 : _a.includes(fieldName)) {
|
|
1908
|
+
return true;
|
|
1909
|
+
}
|
|
1910
|
+
if (documentType && ((_d = (_c = (_b = config.fieldVisibility) == null ? void 0 : _b[documentType]) == null ? void 0 : _c.hiddenFields) == null ? void 0 : _d.includes(fieldName))) {
|
|
1911
|
+
return true;
|
|
1912
|
+
}
|
|
1913
|
+
return false;
|
|
1914
|
+
};
|
|
1915
|
+
var getFieldHiddenFunction = (fieldName, config) => {
|
|
1916
|
+
return ({
|
|
1917
|
+
document
|
|
1918
|
+
}) => {
|
|
1919
|
+
const documentType = document == null ? void 0 : document._type;
|
|
1920
|
+
return isFieldHidden(fieldName, config, documentType);
|
|
1921
|
+
};
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
// src/utils/utils.ts
|
|
1925
|
+
var isEmpty = (value) => {
|
|
1926
|
+
return value === null || value === void 0 || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0 || typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
// src/schemas/types/openGraph/index.ts
|
|
1930
|
+
var import_sanity7 = require("sanity");
|
|
1931
|
+
|
|
1932
|
+
// src/components/openGraph/OgDescription.tsx
|
|
1933
|
+
var import_ui4 = require("@sanity/ui");
|
|
1934
|
+
var import_react4 = require("react");
|
|
1935
|
+
var import_sanity5 = require("sanity");
|
|
1936
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1937
|
+
var OgDescription = (props) => {
|
|
1938
|
+
const { value, renderDefault, path } = props;
|
|
1939
|
+
const parent = (0, import_sanity5.useFormValue)([path[0]]);
|
|
1940
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
1941
|
+
const keywords = (0, import_react4.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1942
|
+
const feedbackItems = (0, import_react4.useMemo)(
|
|
1943
|
+
() => getOgDescriptionValidation(value || "", keywords, isParentseoField),
|
|
1944
|
+
[value, keywords, isParentseoField]
|
|
1945
|
+
);
|
|
1946
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui4.Stack, { space: 3, children: [
|
|
1947
|
+
renderDefault(props),
|
|
1948
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui4.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1950
|
+
"div",
|
|
1951
|
+
{
|
|
1952
|
+
style: {
|
|
1953
|
+
minWidth: 10,
|
|
1954
|
+
height: 10,
|
|
1955
|
+
borderRadius: "50%",
|
|
1956
|
+
backgroundColor: item.color
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
),
|
|
1960
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui4.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
1961
|
+
] }, item.text)) })
|
|
1962
|
+
] });
|
|
1963
|
+
};
|
|
1964
|
+
var OgDescription_default = OgDescription;
|
|
1965
|
+
|
|
1966
|
+
// src/components/openGraph/OgTitle.tsx
|
|
1967
|
+
var import_ui5 = require("@sanity/ui");
|
|
1968
|
+
var import_react5 = require("react");
|
|
1969
|
+
var import_sanity6 = require("sanity");
|
|
1970
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1971
|
+
var OgTitle = (props) => {
|
|
1972
|
+
const { value, renderDefault, path } = props;
|
|
1973
|
+
const parent = (0, import_sanity6.useFormValue)([path[0]]);
|
|
1974
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
1975
|
+
const keywords = (0, import_react5.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1976
|
+
const feedbackItems = (0, import_react5.useMemo)(
|
|
1977
|
+
() => getOgTitleValidation(value || "", keywords, isParentseoField),
|
|
1978
|
+
[value, keywords, isParentseoField]
|
|
1979
|
+
);
|
|
1980
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui5.Stack, { space: 3, children: [
|
|
1981
|
+
renderDefault(props),
|
|
1982
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui5.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1983
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1984
|
+
"div",
|
|
1985
|
+
{
|
|
1986
|
+
style: {
|
|
1987
|
+
minWidth: 10,
|
|
1988
|
+
height: 10,
|
|
1989
|
+
borderRadius: "50%",
|
|
1990
|
+
backgroundColor: item.color
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
),
|
|
1994
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui5.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
1995
|
+
] }, item.text)) })
|
|
1996
|
+
] });
|
|
1997
|
+
};
|
|
1998
|
+
var OgTitle_default = OgTitle;
|
|
1999
|
+
|
|
2000
|
+
// src/schemas/types/openGraph/index.ts
|
|
2001
|
+
function openGraph(config = {}) {
|
|
2002
|
+
return (0, import_sanity7.defineType)({
|
|
2003
|
+
name: "openGraph",
|
|
2004
|
+
title: "Open Graph Settings",
|
|
2005
|
+
type: "object",
|
|
2006
|
+
fields: [
|
|
2007
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2008
|
+
name: "url",
|
|
2009
|
+
type: "url"
|
|
2010
|
+
}, getFieldInfo("openGraphUrl", config.fieldOverrides)), {
|
|
2011
|
+
hidden: getFieldHiddenFunction("openGraphUrl", config),
|
|
2012
|
+
description: "The canonical URL of the page. This should be the full URL including protocol (https://)."
|
|
2013
|
+
})),
|
|
2014
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2015
|
+
name: "title"
|
|
2016
|
+
}, getFieldInfo("openGraphTitle", config.fieldOverrides)), {
|
|
2017
|
+
type: "string",
|
|
2018
|
+
hidden: getFieldHiddenFunction("openGraphTitle", config),
|
|
2019
|
+
components: {
|
|
2020
|
+
input: OgTitle_default
|
|
2021
|
+
// Can also wrap with a string input + preview
|
|
2022
|
+
}
|
|
2023
|
+
})),
|
|
2024
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2025
|
+
name: "description"
|
|
2026
|
+
}, getFieldInfo("openGraphDescription", config.fieldOverrides)), {
|
|
2027
|
+
type: "text",
|
|
2028
|
+
rows: 3,
|
|
2029
|
+
hidden: getFieldHiddenFunction("openGraphDescription", config),
|
|
2030
|
+
components: {
|
|
2031
|
+
input: OgDescription_default
|
|
2032
|
+
// Can also wrap with a text area + preview
|
|
2033
|
+
}
|
|
2034
|
+
})),
|
|
2035
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2036
|
+
name: "siteName"
|
|
2037
|
+
}, getFieldInfo("openGraphSiteName", config.fieldOverrides)), {
|
|
2038
|
+
type: "string",
|
|
2039
|
+
hidden: getFieldHiddenFunction("openGraphSiteName", config)
|
|
2040
|
+
})),
|
|
2041
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2042
|
+
name: "type"
|
|
2043
|
+
}, getFieldInfo("openGraphType", config.fieldOverrides)), {
|
|
2044
|
+
type: "string",
|
|
2045
|
+
options: {
|
|
2046
|
+
list: [
|
|
2047
|
+
{ title: "Website", value: "website" },
|
|
2048
|
+
{ title: "Article", value: "article" },
|
|
2049
|
+
{ title: "Profile", value: "profile" },
|
|
2050
|
+
{ title: "Book", value: "book" },
|
|
2051
|
+
{ title: "Music", value: "music" },
|
|
2052
|
+
{ title: "Video", value: "video" },
|
|
2053
|
+
{ title: "Product", value: "product" }
|
|
2054
|
+
]
|
|
2055
|
+
// layout: 'radio', // Display as radio buttons
|
|
2056
|
+
},
|
|
2057
|
+
hidden: getFieldHiddenFunction("openGraphType", config),
|
|
2058
|
+
initialValue: "website"
|
|
2059
|
+
})),
|
|
2060
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2061
|
+
name: "imageType"
|
|
2062
|
+
}, getFieldInfo("openGraphImageType", config.fieldOverrides)), {
|
|
2063
|
+
type: "string",
|
|
2064
|
+
options: {
|
|
2065
|
+
list: [
|
|
2066
|
+
{ title: "Upload Image", value: "upload" },
|
|
2067
|
+
{ title: "Image URL", value: "url" }
|
|
2068
|
+
]
|
|
2069
|
+
},
|
|
2070
|
+
hidden: getFieldHiddenFunction("openGraphImage", config),
|
|
2071
|
+
initialValue: "upload"
|
|
2072
|
+
})),
|
|
2073
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2074
|
+
name: "image"
|
|
2075
|
+
}, getFieldInfo("openGraphImage", config.fieldOverrides)), {
|
|
2076
|
+
type: "image",
|
|
2077
|
+
options: {
|
|
2078
|
+
hotspot: true
|
|
2079
|
+
},
|
|
2080
|
+
fields: [
|
|
2081
|
+
(0, import_sanity7.defineField)({
|
|
2082
|
+
name: "alt",
|
|
2083
|
+
title: "Image Alt Text",
|
|
2084
|
+
type: "string",
|
|
2085
|
+
description: "A description of the image for accessibility purposes."
|
|
2086
|
+
})
|
|
2087
|
+
],
|
|
2088
|
+
hidden: (context) => {
|
|
2089
|
+
const { parent } = context;
|
|
2090
|
+
if ((parent == null ? void 0 : parent.imageType) !== "upload") return true;
|
|
2091
|
+
const hiddenFn = getFieldHiddenFunction("openGraphImage", config);
|
|
2092
|
+
return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
|
|
2093
|
+
}
|
|
2094
|
+
})),
|
|
2095
|
+
(0, import_sanity7.defineField)(__spreadProps(__spreadValues({
|
|
2096
|
+
name: "imageUrl"
|
|
2097
|
+
}, getFieldInfo("openGraphImageUrl", config.fieldOverrides)), {
|
|
2098
|
+
type: "url",
|
|
2099
|
+
hidden: (context) => {
|
|
2100
|
+
const { parent } = context;
|
|
2101
|
+
if ((parent == null ? void 0 : parent.imageType) !== "url") return true;
|
|
2102
|
+
const hiddenFn = getFieldHiddenFunction("openGraphImage", config);
|
|
2103
|
+
return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
|
|
2104
|
+
}
|
|
2105
|
+
}))
|
|
2106
|
+
]
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// src/schemas/types/twitter/index.ts
|
|
2111
|
+
var import_sanity10 = require("sanity");
|
|
2112
|
+
|
|
2113
|
+
// src/components/twitter/twitterDescription.tsx
|
|
2114
|
+
var import_ui6 = require("@sanity/ui");
|
|
2115
|
+
var import_react6 = require("react");
|
|
2116
|
+
var import_sanity8 = require("sanity");
|
|
2117
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2118
|
+
var TwitterDescription = (props) => {
|
|
2119
|
+
const { value, renderDefault, path } = props;
|
|
2120
|
+
const parent = (0, import_sanity8.useFormValue)([path[0]]);
|
|
2121
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
2122
|
+
const keywords = (0, import_react6.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
2123
|
+
const feedbackItems = (0, import_react6.useMemo)(
|
|
2124
|
+
() => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
|
|
2125
|
+
[value, keywords, isParentseoField]
|
|
2126
|
+
);
|
|
2127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui6.Stack, { space: 3, children: [
|
|
2128
|
+
renderDefault(props),
|
|
2129
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui6.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
2130
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2131
|
+
"div",
|
|
2132
|
+
{
|
|
2133
|
+
style: {
|
|
2134
|
+
minWidth: 10,
|
|
2135
|
+
height: 10,
|
|
2136
|
+
borderRadius: "50%",
|
|
2137
|
+
backgroundColor: item.color
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
),
|
|
2141
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui6.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
2142
|
+
] }, item.text)) })
|
|
2143
|
+
] });
|
|
2144
|
+
};
|
|
2145
|
+
var twitterDescription_default = TwitterDescription;
|
|
2146
|
+
|
|
2147
|
+
// src/components/twitter/twitterTitle.tsx
|
|
2148
|
+
var import_ui7 = require("@sanity/ui");
|
|
2149
|
+
var import_react7 = require("react");
|
|
2150
|
+
var import_sanity9 = require("sanity");
|
|
2151
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2152
|
+
var TwitterTitle = (props) => {
|
|
2153
|
+
const { value, renderDefault, path } = props;
|
|
2154
|
+
const parent = (0, import_sanity9.useFormValue)([path[0]]);
|
|
2155
|
+
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
2156
|
+
const keywords = (0, import_react7.useMemo)(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
2157
|
+
const feedbackItems = (0, import_react7.useMemo)(
|
|
2158
|
+
() => getTwitterTitleValidation(value || "", keywords, isParentseoField),
|
|
2159
|
+
[value, keywords, isParentseoField]
|
|
2160
|
+
);
|
|
2161
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ui7.Stack, { space: 3, children: [
|
|
2162
|
+
renderDefault(props),
|
|
2163
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui7.Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
2164
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2165
|
+
"div",
|
|
2166
|
+
{
|
|
2167
|
+
style: {
|
|
2168
|
+
minWidth: 10,
|
|
2169
|
+
height: 10,
|
|
2170
|
+
borderRadius: "50%",
|
|
2171
|
+
backgroundColor: item.color
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
),
|
|
2175
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ui7.Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
2176
|
+
] }, item.text)) })
|
|
2177
|
+
] });
|
|
2178
|
+
};
|
|
2179
|
+
var twitterTitle_default = TwitterTitle;
|
|
2180
|
+
|
|
2181
|
+
// src/schemas/types/twitter/index.ts
|
|
2182
|
+
function twitter(config = {}) {
|
|
2183
|
+
return (0, import_sanity10.defineType)({
|
|
2184
|
+
name: "twitter",
|
|
2185
|
+
title: "X (Formerly Twitter)",
|
|
2186
|
+
type: "object",
|
|
2187
|
+
fields: [
|
|
2188
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2189
|
+
name: "card"
|
|
2190
|
+
}, getFieldInfo("twitterCard", config.fieldOverrides)), {
|
|
2191
|
+
type: "string",
|
|
2192
|
+
options: {
|
|
2193
|
+
list: [
|
|
2194
|
+
{ title: "Summary", value: "summary" },
|
|
2195
|
+
{ title: "Summary with Large Image", value: "summary_large_image" },
|
|
2196
|
+
{ title: "App", value: "app" },
|
|
2197
|
+
{ title: "Player", value: "player" }
|
|
2198
|
+
]
|
|
2199
|
+
},
|
|
2200
|
+
hidden: getFieldHiddenFunction("twitterCard", config),
|
|
2201
|
+
initialValue: "summary_large_image"
|
|
2202
|
+
// good default
|
|
2203
|
+
})),
|
|
2204
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2205
|
+
name: "site"
|
|
2206
|
+
}, getFieldInfo("twitterSite", config.fieldOverrides)), {
|
|
2207
|
+
type: "string",
|
|
2208
|
+
hidden: getFieldHiddenFunction("twitterSite", config)
|
|
2209
|
+
})),
|
|
2210
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2211
|
+
name: "creator",
|
|
2212
|
+
type: "string"
|
|
2213
|
+
}, getFieldInfo("twitterCreator", config.fieldOverrides)), {
|
|
2214
|
+
hidden: getFieldHiddenFunction("twitterCreator", config)
|
|
2215
|
+
})),
|
|
2216
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2217
|
+
name: "title",
|
|
2218
|
+
type: "string"
|
|
2219
|
+
}, getFieldInfo("twitterTitle", config.fieldOverrides)), {
|
|
2220
|
+
hidden: getFieldHiddenFunction("twitterTitle", config),
|
|
2221
|
+
components: {
|
|
2222
|
+
input: twitterTitle_default
|
|
2223
|
+
}
|
|
2224
|
+
})),
|
|
2225
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2226
|
+
name: "description",
|
|
2227
|
+
type: "text",
|
|
2228
|
+
rows: 3
|
|
2229
|
+
}, getFieldInfo("twitterDescription", config.fieldOverrides)), {
|
|
2230
|
+
hidden: getFieldHiddenFunction("twitterDescription", config),
|
|
2231
|
+
components: {
|
|
2232
|
+
input: twitterDescription_default
|
|
2233
|
+
}
|
|
2234
|
+
})),
|
|
2235
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2236
|
+
name: "imageType"
|
|
2237
|
+
}, getFieldInfo("twitterImageType", config.fieldOverrides)), {
|
|
2238
|
+
type: "string",
|
|
2239
|
+
options: {
|
|
2240
|
+
list: [
|
|
2241
|
+
{ title: "Upload Image", value: "upload" },
|
|
2242
|
+
{ title: "Image URL", value: "url" }
|
|
2243
|
+
]
|
|
2244
|
+
},
|
|
2245
|
+
hidden: getFieldHiddenFunction("twitterImage", config),
|
|
2246
|
+
initialValue: "upload"
|
|
2247
|
+
})),
|
|
2248
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2249
|
+
name: "image"
|
|
2250
|
+
}, getFieldInfo("twitterImage", config.fieldOverrides)), {
|
|
2251
|
+
type: "image",
|
|
2252
|
+
options: {
|
|
2253
|
+
hotspot: true
|
|
2254
|
+
},
|
|
2255
|
+
fields: [
|
|
2256
|
+
(0, import_sanity10.defineField)({
|
|
2257
|
+
name: "alt",
|
|
2258
|
+
title: "Image Alt Text",
|
|
2259
|
+
type: "string",
|
|
2260
|
+
description: "A description of the image for accessibility purposes."
|
|
2261
|
+
})
|
|
2262
|
+
],
|
|
2263
|
+
hidden: (context) => {
|
|
2264
|
+
const { parent } = context;
|
|
2265
|
+
if ((parent == null ? void 0 : parent.imageType) !== "upload") return true;
|
|
2266
|
+
const hiddenFn = getFieldHiddenFunction("twitterImage", config);
|
|
2267
|
+
return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
|
|
2268
|
+
}
|
|
2269
|
+
})),
|
|
2270
|
+
(0, import_sanity10.defineField)(__spreadProps(__spreadValues({
|
|
2271
|
+
name: "imageUrl"
|
|
2272
|
+
}, getFieldInfo("twitterImageUrl", config.fieldOverrides)), {
|
|
2273
|
+
type: "url",
|
|
2274
|
+
hidden: (context) => {
|
|
2275
|
+
const { parent } = context;
|
|
2276
|
+
if ((parent == null ? void 0 : parent.imageType) !== "url") return true;
|
|
2277
|
+
const hiddenFn = getFieldHiddenFunction("twitterImage", config);
|
|
2278
|
+
return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
|
|
2279
|
+
}
|
|
2280
|
+
}))
|
|
2281
|
+
]
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// src/schemas/index.ts
|
|
2286
|
+
function seoFieldsSchema(config = {}) {
|
|
2287
|
+
return (0, import_sanity11.defineType)({
|
|
2288
|
+
name: "seoFields",
|
|
2289
|
+
title: "SEO Fields",
|
|
2290
|
+
type: "object",
|
|
2291
|
+
fields: [
|
|
2292
|
+
(0, import_sanity11.defineField)({
|
|
2293
|
+
name: "robots",
|
|
2294
|
+
title: "Robots Settings",
|
|
2295
|
+
type: "robots",
|
|
2296
|
+
// Use the separate robots type here
|
|
2297
|
+
hidden: getFieldHiddenFunction("robots", config)
|
|
2298
|
+
}),
|
|
2299
|
+
// 👇 conditionally spread preview field
|
|
2300
|
+
...typeof config.seoPreview === "boolean" && config.seoPreview || typeof config.seoPreview === "object" && !isEmpty(config.seoPreview) ? [
|
|
2301
|
+
(0, import_sanity11.defineField)({
|
|
2302
|
+
name: "preview",
|
|
2303
|
+
title: "SEO Preview",
|
|
2304
|
+
type: "string",
|
|
2305
|
+
components: { input: SeoPreview_default },
|
|
2306
|
+
options: __spreadValues({
|
|
2307
|
+
baseUrl: config.baseUrl || "https://www.example.com"
|
|
2308
|
+
}, typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.prefix ? { prefix: config.seoPreview.prefix } : {}),
|
|
2309
|
+
// Use a readOnly field to prevent editing
|
|
2310
|
+
// This field is just for preview purposes
|
|
2311
|
+
initialValue: "",
|
|
2312
|
+
// Set an initial value
|
|
2313
|
+
readOnly: true
|
|
2314
|
+
})
|
|
2315
|
+
] : [],
|
|
2316
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2317
|
+
name: "title"
|
|
2318
|
+
}, getFieldInfo("title", config.fieldOverrides)), {
|
|
2319
|
+
// title: 'Meta Title',
|
|
2320
|
+
type: "string",
|
|
2321
|
+
// description:
|
|
2322
|
+
// 'The meta title is displayed in search engine results as the clickable headline for a given result. It should be concise and accurately reflect the content of the page.',
|
|
2323
|
+
components: {
|
|
2324
|
+
input: MetaTitle_default
|
|
2325
|
+
},
|
|
2326
|
+
// validation: (Rule) => Rule.max(60).warning('Meta title should be under 60 characters.'),
|
|
2327
|
+
hidden: getFieldHiddenFunction("title", config)
|
|
2328
|
+
})),
|
|
2329
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2330
|
+
name: "description"
|
|
2331
|
+
}, getFieldInfo("description", config.fieldOverrides)), {
|
|
2332
|
+
// title: 'Meta Description',
|
|
2333
|
+
// description:
|
|
2334
|
+
// 'Provide a concise summary of the page content. This description may be used by search engines in search results.',
|
|
2335
|
+
type: "text",
|
|
2336
|
+
rows: 3,
|
|
2337
|
+
components: {
|
|
2338
|
+
input: MetaDescription_default
|
|
2339
|
+
},
|
|
2340
|
+
// validation: (Rule) => Rule.max(160).warning('Meta description should be under 160 characters.'),
|
|
2341
|
+
hidden: getFieldHiddenFunction("description", config)
|
|
2342
|
+
})),
|
|
2343
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2344
|
+
name: "metaImage"
|
|
2345
|
+
}, getFieldInfo("metaImage", config.fieldOverrides)), {
|
|
2346
|
+
// title: 'Meta Image',
|
|
2347
|
+
// description:
|
|
2348
|
+
// 'Upload an image that represents the content of the page. This image may be used in social media previews and search engine results.',
|
|
2349
|
+
type: "image",
|
|
2350
|
+
options: {
|
|
2351
|
+
hotspot: true
|
|
2352
|
+
},
|
|
2353
|
+
hidden: getFieldHiddenFunction("metaImage", config)
|
|
2354
|
+
})),
|
|
2355
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2356
|
+
name: "metaAttributes"
|
|
2357
|
+
}, getFieldInfo("metaAttributes", config.fieldOverrides)), {
|
|
2358
|
+
type: "array",
|
|
2359
|
+
of: [{ type: "metaAttribute" }],
|
|
2360
|
+
// description:
|
|
2361
|
+
// 'Add custom meta attributes to the head of the document for additional SEO and social media integration.',
|
|
2362
|
+
hidden: getFieldHiddenFunction("metaAttributes", config)
|
|
2363
|
+
})),
|
|
2364
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2365
|
+
name: "keywords"
|
|
2366
|
+
}, getFieldInfo("keywords", config.fieldOverrides)), {
|
|
2367
|
+
title: "Keywords",
|
|
2368
|
+
type: "array",
|
|
2369
|
+
of: [{ type: "string" }],
|
|
2370
|
+
description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
|
|
2371
|
+
hidden: getFieldHiddenFunction("keywords", config)
|
|
2372
|
+
})),
|
|
2373
|
+
(0, import_sanity11.defineField)(__spreadProps(__spreadValues({
|
|
2374
|
+
name: "canonicalUrl"
|
|
2375
|
+
}, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
|
|
2376
|
+
title: "Canonical URL",
|
|
2377
|
+
type: "url",
|
|
2378
|
+
description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page.",
|
|
2379
|
+
hidden: getFieldHiddenFunction("canonicalUrl", config)
|
|
2380
|
+
})),
|
|
2381
|
+
openGraph(config),
|
|
2382
|
+
twitter(config)
|
|
2383
|
+
]
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/schemas/types/metaAttribute/index.ts
|
|
2388
|
+
var import_sanity12 = require("sanity");
|
|
2389
|
+
var metaAttribute_default = (0, import_sanity12.defineType)({
|
|
2390
|
+
name: "metaAttribute",
|
|
2391
|
+
title: "Meta Attribute",
|
|
2392
|
+
type: "object",
|
|
2393
|
+
fields: [
|
|
2394
|
+
(0, import_sanity12.defineField)({
|
|
2395
|
+
name: "key",
|
|
2396
|
+
title: "Attribute Name",
|
|
2397
|
+
type: "string"
|
|
2398
|
+
}),
|
|
2399
|
+
(0, import_sanity12.defineField)({
|
|
2400
|
+
name: "type",
|
|
2401
|
+
title: "Attribute Value Type",
|
|
2402
|
+
type: "string",
|
|
2403
|
+
options: {
|
|
2404
|
+
list: [
|
|
2405
|
+
{ title: "String", value: "string" },
|
|
2406
|
+
{ title: "Image", value: "image" }
|
|
2407
|
+
]
|
|
2408
|
+
},
|
|
2409
|
+
initialValue: "string"
|
|
2410
|
+
}),
|
|
2411
|
+
(0, import_sanity12.defineField)({
|
|
2412
|
+
name: "value",
|
|
2413
|
+
title: "Attribute Value",
|
|
2414
|
+
type: "string",
|
|
2415
|
+
hidden: ({ parent }) => (parent == null ? void 0 : parent.type) === "image"
|
|
2416
|
+
}),
|
|
2417
|
+
(0, import_sanity12.defineField)({
|
|
2418
|
+
name: "image",
|
|
2419
|
+
title: "Attribute Image Value",
|
|
2420
|
+
type: "image",
|
|
2421
|
+
hidden: ({ parent }) => (parent == null ? void 0 : parent.type) === "string"
|
|
2422
|
+
})
|
|
2423
|
+
],
|
|
2424
|
+
preview: {
|
|
2425
|
+
select: {
|
|
2426
|
+
attributeName: "key",
|
|
2427
|
+
attributeValueString: "value",
|
|
2428
|
+
attributeValueImage: "image"
|
|
2429
|
+
},
|
|
2430
|
+
prepare({ attributeName, attributeValueString, attributeValueImage }) {
|
|
2431
|
+
let subtitle = "";
|
|
2432
|
+
if (attributeValueString) {
|
|
2433
|
+
subtitle = `Value: ${attributeValueString}`;
|
|
2434
|
+
} else if (attributeValueImage) {
|
|
2435
|
+
subtitle = "Value: [Image]";
|
|
2436
|
+
} else {
|
|
2437
|
+
subtitle = "No Attribute Value";
|
|
2438
|
+
}
|
|
2439
|
+
return {
|
|
2440
|
+
title: attributeName || "No Attribute Name",
|
|
2441
|
+
subtitle,
|
|
2442
|
+
media: attributeValueImage
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
|
|
2448
|
+
// src/schemas/types/metaTag/index.ts
|
|
2449
|
+
var import_sanity13 = require("sanity");
|
|
2450
|
+
var metaTag_default = (0, import_sanity13.defineType)({
|
|
2451
|
+
name: "metaTag",
|
|
2452
|
+
title: "Meta Tag",
|
|
2453
|
+
type: "object",
|
|
2454
|
+
fields: [
|
|
2455
|
+
{
|
|
2456
|
+
name: "metaAttributes",
|
|
2457
|
+
title: "Meta Attributes",
|
|
2458
|
+
type: "array",
|
|
2459
|
+
of: [{ type: "metaAttribute" }],
|
|
2460
|
+
description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
|
|
2461
|
+
}
|
|
2462
|
+
]
|
|
2463
|
+
});
|
|
2464
|
+
|
|
2465
|
+
// src/schemas/types/robots/index.ts
|
|
2466
|
+
var import_sanity14 = require("sanity");
|
|
2467
|
+
var robots_default = (0, import_sanity14.defineType)({
|
|
2468
|
+
name: "robots",
|
|
2469
|
+
title: "Robots Settings",
|
|
2470
|
+
type: "object",
|
|
2471
|
+
fields: [
|
|
2472
|
+
(0, import_sanity14.defineField)({
|
|
2473
|
+
name: "noIndex",
|
|
2474
|
+
title: "No Index",
|
|
2475
|
+
type: "boolean",
|
|
2476
|
+
initialValue: false,
|
|
2477
|
+
description: "Enable this to prevent search engines from indexing this page. The page will not appear in search results."
|
|
2478
|
+
}),
|
|
2479
|
+
(0, import_sanity14.defineField)({
|
|
2480
|
+
name: "noFollow",
|
|
2481
|
+
title: "No Follow",
|
|
2482
|
+
type: "boolean",
|
|
2483
|
+
initialValue: false,
|
|
2484
|
+
description: "Enable this to prevent search engines from following links on this page. Links will not pass SEO value."
|
|
2485
|
+
})
|
|
2486
|
+
],
|
|
2487
|
+
description: "Select how search engines should index and follow links on this page."
|
|
2488
|
+
});
|
|
2489
|
+
|
|
2490
|
+
// src/schemas/types/index.ts
|
|
2491
|
+
function types(config = {}) {
|
|
2492
|
+
return [
|
|
2493
|
+
seoFieldsSchema(config),
|
|
2494
|
+
// pass config here
|
|
2495
|
+
openGraph(config),
|
|
2496
|
+
// pass config here
|
|
2497
|
+
twitter(config),
|
|
2498
|
+
// pass config here
|
|
2499
|
+
metaAttribute_default,
|
|
2500
|
+
metaTag_default,
|
|
2501
|
+
robots_default
|
|
2502
|
+
];
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/plugin.ts
|
|
2506
|
+
var resolveDashboardConfig = (healthDashboard) => {
|
|
2507
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
2508
|
+
const cfg = typeof healthDashboard === "object" ? healthDashboard : void 0;
|
|
2509
|
+
return {
|
|
2510
|
+
enabled: healthDashboard !== false,
|
|
2511
|
+
toolTitle: (_b = (_a = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _a.title) != null ? _b : "SEO Health",
|
|
2512
|
+
toolName: (_d = (_c = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _c.name) != null ? _d : "seo-health-dashboard",
|
|
2513
|
+
icon: (_e = cfg == null ? void 0 : cfg.content) == null ? void 0 : _e.icon,
|
|
2514
|
+
title: (_f = cfg == null ? void 0 : cfg.content) == null ? void 0 : _f.title,
|
|
2515
|
+
description: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.description,
|
|
2516
|
+
showTypeColumn: (_h = cfg == null ? void 0 : cfg.display) == null ? void 0 : _h.typeColumn,
|
|
2517
|
+
showDocumentId: (_i = cfg == null ? void 0 : cfg.display) == null ? void 0 : _i.documentId,
|
|
2518
|
+
queryTypes: (_j = cfg == null ? void 0 : cfg.query) == null ? void 0 : _j.types,
|
|
2519
|
+
queryRequireSeo: (_k = cfg == null ? void 0 : cfg.query) == null ? void 0 : _k.requireSeo,
|
|
2520
|
+
queryGroq: (_l = cfg == null ? void 0 : cfg.query) == null ? void 0 : _l.groq,
|
|
2521
|
+
apiVersion: cfg == null ? void 0 : cfg.apiVersion,
|
|
2522
|
+
licenseKey: cfg == null ? void 0 : cfg.licenseKey,
|
|
2523
|
+
typeLabels: cfg == null ? void 0 : cfg.typeLabels,
|
|
2524
|
+
typeColumnMode: cfg == null ? void 0 : cfg.typeColumnMode,
|
|
2525
|
+
titleField: cfg == null ? void 0 : cfg.titleField,
|
|
2526
|
+
docBadge: cfg == null ? void 0 : cfg.docBadge,
|
|
2527
|
+
loadingLicense: (_m = cfg == null ? void 0 : cfg.content) == null ? void 0 : _m.loadingLicense,
|
|
2528
|
+
loadingDocuments: (_n = cfg == null ? void 0 : cfg.content) == null ? void 0 : _n.loadingDocuments,
|
|
2529
|
+
noDocuments: (_o = cfg == null ? void 0 : cfg.content) == null ? void 0 : _o.noDocuments,
|
|
2530
|
+
previewMode: cfg == null ? void 0 : cfg.previewMode,
|
|
2531
|
+
structureTool: cfg == null ? void 0 : cfg.structureTool
|
|
2532
|
+
};
|
|
2533
|
+
};
|
|
2534
|
+
var seofields = (0, import_sanity15.definePlugin)((config = {}) => {
|
|
2535
|
+
const { healthDashboard = true } = config;
|
|
2536
|
+
const dash = resolveDashboardConfig(healthDashboard);
|
|
2537
|
+
const BoundSeoHealthTool = () => import_react8.default.createElement(SeoHealthTool_default, {
|
|
2538
|
+
icon: dash.icon,
|
|
2539
|
+
title: dash.title,
|
|
2540
|
+
description: dash.description,
|
|
2541
|
+
showTypeColumn: dash.showTypeColumn,
|
|
2542
|
+
showDocumentId: dash.showDocumentId,
|
|
2543
|
+
queryTypes: dash.queryTypes,
|
|
2544
|
+
queryRequireSeo: dash.queryRequireSeo,
|
|
2545
|
+
customQuery: dash.queryGroq,
|
|
2546
|
+
apiVersion: dash.apiVersion,
|
|
2547
|
+
licenseKey: dash.licenseKey,
|
|
2548
|
+
typeLabels: dash.typeLabels,
|
|
2549
|
+
typeColumnMode: dash.typeColumnMode,
|
|
2550
|
+
titleField: dash.titleField,
|
|
2551
|
+
docBadge: dash.docBadge,
|
|
2552
|
+
loadingLicense: dash.loadingLicense,
|
|
2553
|
+
loadingDocuments: dash.loadingDocuments,
|
|
2554
|
+
noDocuments: dash.noDocuments,
|
|
2555
|
+
previewMode: dash.previewMode,
|
|
2556
|
+
structureTool: dash.structureTool
|
|
2557
|
+
});
|
|
2558
|
+
return __spreadValues({
|
|
2559
|
+
name: "sanity-plugin-seofields",
|
|
2560
|
+
schema: {
|
|
2561
|
+
types: types(config)
|
|
2562
|
+
}
|
|
2563
|
+
}, dash.enabled && {
|
|
2564
|
+
tools: [
|
|
2565
|
+
{
|
|
2566
|
+
name: dash.toolName,
|
|
2567
|
+
title: dash.toolTitle,
|
|
2568
|
+
component: BoundSeoHealthTool,
|
|
2569
|
+
icon: () => "\u{1F4CA}"
|
|
2570
|
+
}
|
|
2571
|
+
]
|
|
2572
|
+
});
|
|
2573
|
+
});
|
|
2574
|
+
var plugin_default = seofields;
|
|
2575
|
+
|
|
2576
|
+
// src/components/SeoHealthPane.tsx
|
|
2577
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2578
|
+
function createSeoHealthPane(optionsOrS, optionsWhenS) {
|
|
2579
|
+
const S = optionsOrS;
|
|
2580
|
+
const _a = optionsWhenS != null ? optionsWhenS : {}, { query, openInPane = true, title: paneTitle } = _a, rest = __objRest(_a, ["query", "openInPane", "title"]);
|
|
2581
|
+
const SeoHealthPane = () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SeoHealthDashboard_default, __spreadValues({ customQuery: query, openInPane, title: paneTitle }, rest));
|
|
2582
|
+
SeoHealthPane.displayName = "SeoHealthPane";
|
|
2583
|
+
return S.component(SeoHealthPane).title(paneTitle != null ? paneTitle : "SEO Health").child((docId, { params }) => {
|
|
2584
|
+
const builder = S.document().documentId(docId);
|
|
2585
|
+
return (params == null ? void 0 : params.type) ? builder.schemaType(params.type) : builder;
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/index.ts
|
|
2590
|
+
var src_default = plugin_default;
|
|
2591
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2592
|
+
0 && (module.exports = {
|
|
2593
|
+
SeoHealthDashboard,
|
|
2594
|
+
SeoHealthTool,
|
|
2595
|
+
allSchemas,
|
|
2596
|
+
createSeoHealthPane,
|
|
2597
|
+
metaAttributeSchema,
|
|
2598
|
+
metaTagSchema,
|
|
2599
|
+
openGraphSchema,
|
|
2600
|
+
robotsSchema,
|
|
2601
|
+
seoFieldsSchema,
|
|
2602
|
+
twitterSchema
|
|
2603
|
+
});
|
|
2604
|
+
//# sourceMappingURL=index.cjs.map
|