sanity-plugin-seofields 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.cts +78 -0
- package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.ts +78 -0
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +4 -0
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +1 -0
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +10 -0
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +1 -0
- package/dist/SeoHealthTool-2KKQJLJL.cjs +11 -0
- package/dist/{SeoHealthDashboard-PTUEVYZL.js.map → SeoHealthTool-2KKQJLJL.cjs.map} +1 -1
- package/dist/SeoHealthTool-3CUKGADZ.js +5 -0
- package/dist/{SeoHealthDashboard-IP5BJ322.cjs.map → SeoHealthTool-3CUKGADZ.js.map} +1 -1
- package/dist/{SeoPreview-WYH7NYNM.cjs → SeoPreview-3EXR6FD4.cjs} +3 -3
- package/dist/{SeoPreview-WYH7NYNM.cjs.map → SeoPreview-3EXR6FD4.cjs.map} +1 -1
- package/dist/{SeoPreview-KEGGQSPJ.js → SeoPreview-4PPAF4NO.js} +3 -3
- package/dist/{SeoPreview-KEGGQSPJ.js.map → SeoPreview-4PPAF4NO.js.map} +1 -1
- package/dist/{chunk-S367Y35J.cjs → chunk-254YHUN3.cjs} +2 -15
- package/dist/{chunk-S367Y35J.cjs.map → chunk-254YHUN3.cjs.map} +1 -1
- package/dist/{chunk-AW7OKBQQ.js → chunk-27XSYENH.js} +59 -5
- package/dist/chunk-27XSYENH.js.map +1 -0
- package/dist/{chunk-GWPOJSGW.cjs → chunk-B5IVI5LP.cjs} +821 -156
- package/dist/chunk-B5IVI5LP.cjs.map +1 -0
- package/dist/chunk-BNXMFD3T.cjs +431 -0
- package/dist/chunk-BNXMFD3T.cjs.map +1 -0
- package/dist/{chunk-2NMEKWO5.js → chunk-DDAAVRWG.js} +3 -15
- package/dist/{chunk-2NMEKWO5.js.map → chunk-DDAAVRWG.js.map} +1 -1
- package/dist/chunk-P6IOWIO2.cjs +480 -0
- package/dist/chunk-P6IOWIO2.cjs.map +1 -0
- package/dist/{chunk-FONJYVF2.js → chunk-UOCZFYYP.js} +5 -208
- package/dist/chunk-UOCZFYYP.js.map +1 -0
- package/dist/{chunk-HRKRV2X7.js → chunk-VR44E62I.js} +802 -147
- package/dist/chunk-VR44E62I.js.map +1 -0
- package/dist/chunk-WKXHX3GO.js +424 -0
- package/dist/chunk-WKXHX3GO.js.map +1 -0
- package/dist/{chunk-5YRH47VQ.cjs → chunk-XZDLJ2N6.cjs} +63 -6
- package/dist/chunk-XZDLJ2N6.cjs.map +1 -0
- package/dist/cli.js +18 -18
- package/dist/{component-DQ4KuQQD.d.ts → component-BzI-LHPw.d.cts} +3 -27
- package/dist/{component-D1sBm6ts.d.cts → component-DEXwtemT.d.ts} +3 -27
- package/dist/define-cli.cjs +1 -1
- package/dist/define-cli.js +1 -1
- package/dist/index.cjs +1050 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -295
- package/dist/index.d.ts +76 -295
- package/dist/index.js +1009 -63
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +136 -122
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +9 -3
- package/dist/next.d.ts +9 -3
- package/dist/next.js +38 -32
- package/dist/next.js.map +1 -1
- package/dist/schema/next.cjs +103 -95
- package/dist/schema/next.d.cts +4 -4
- package/dist/schema/next.d.ts +4 -4
- package/dist/schema/next.js +4 -4
- package/dist/schema.cjs +202 -160
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +26 -16
- package/dist/schema.d.ts +26 -16
- package/dist/schema.js +25 -7
- package/dist/schema.js.map +1 -1
- package/dist/{types-9_wbCIi9.d.ts → types-BXSCbl6p.d.cts} +29 -2
- package/dist/{types-_wBLg6RV.d.cts → types-B_vjMPtM.d.cts} +1 -1
- package/dist/{types-Bb49oRBV.d.cts → types-C19cQx5D.d.ts} +29 -2
- package/dist/{types-CeBULNFF.d.ts → types-CW8qFdAn.d.ts} +1 -1
- package/dist/{types-B91ena4g.d.cts → types-yVmQfby9.d.cts} +7 -0
- package/dist/{types-B91ena4g.d.ts → types-yVmQfby9.d.ts} +7 -0
- package/package.json +2 -1
- package/dist/SeoHealthDashboard-IP5BJ322.cjs +0 -10
- package/dist/SeoHealthDashboard-PTUEVYZL.js +0 -4
- package/dist/SeoHealthTool-CZZRMWQK.cjs +0 -14
- package/dist/SeoHealthTool-CZZRMWQK.cjs.map +0 -1
- package/dist/SeoHealthTool-IULT4G25.js +0 -12
- package/dist/SeoHealthTool-IULT4G25.js.map +0 -1
- package/dist/chunk-5YRH47VQ.cjs.map +0 -1
- package/dist/chunk-ARELX3GK.cjs +0 -689
- package/dist/chunk-ARELX3GK.cjs.map +0 -1
- package/dist/chunk-AW7OKBQQ.js.map +0 -1
- package/dist/chunk-FONJYVF2.js.map +0 -1
- package/dist/chunk-GWPOJSGW.cjs.map +0 -1
- package/dist/chunk-HRKRV2X7.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,468 @@
|
|
|
1
|
+
import { tokenizeHtml } from './chunk-VR44E62I.js';
|
|
2
|
+
export { SchemaOrgJsonLdPreview as SchemaOrgDocumentJsonLdPreview, SchemaOrgJsonLdPreview } from './chunk-VR44E62I.js';
|
|
1
3
|
import { getMetaTitleValidationMessages, getMetaDescriptionValidationMessages, getMetaImageValidation, getOgTitleValidation, getOgDescriptionValidation, getOgImageValidation, getOgImageUrlValidation, getTwitterTitleValidation, getTwitterDescriptionValidation, getTwitterImageValidation, getTwitterImageUrlValidation } from './chunk-HDZZQCH7.js';
|
|
2
|
-
import {
|
|
4
|
+
import { createSeoPublishGateAction, buildGEOProChecks, GEOChecklistProChecks, GEOLockedTeaser } from './chunk-WKXHX3GO.js';
|
|
5
|
+
export { createSeoHealthPane } from './chunk-WKXHX3GO.js';
|
|
6
|
+
import './chunk-27XSYENH.js';
|
|
7
|
+
import { __spreadValues, __spreadProps } from './chunk-DDAAVRWG.js';
|
|
3
8
|
import { lazy, createElement, Suspense, useMemo, useState, useEffect } from 'react';
|
|
4
|
-
import { defineType, defineField, definePlugin, useFormValue, useClient } from 'sanity';
|
|
5
|
-
import { Stack, Text } from '@sanity/ui';
|
|
9
|
+
import { defineType, defineField, definePlugin, useFormValue, useClient, useColorScheme } from 'sanity';
|
|
6
10
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
11
|
+
import { Stack, Text, Card, Flex, Button, Box } from '@sanity/ui';
|
|
7
12
|
|
|
13
|
+
var GEOChecklist = (props) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const { path, schemaType } = props;
|
|
16
|
+
const [expanded, setExpanded] = useState(false);
|
|
17
|
+
const licenseKey = (_a = schemaType.options) == null ? void 0 : _a.licenseKey;
|
|
18
|
+
const seoFields = useFormValue([path[0]]);
|
|
19
|
+
const rootDoc = useFormValue([]);
|
|
20
|
+
const freeChecks = useMemo(() => {
|
|
21
|
+
var _a2, _b, _c, _d;
|
|
22
|
+
const desc = (seoFields == null ? void 0 : seoFields.description) || "";
|
|
23
|
+
const title = (seoFields == null ? void 0 : seoFields.title) || "";
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
id: "description_answer_ready",
|
|
27
|
+
label: "Description is answer-ready (100\u2013160 chars)",
|
|
28
|
+
pass: !!desc && desc.length >= 100 && desc.length <= 160,
|
|
29
|
+
hint: "Write a concise 100\u2013160 char answer that directly addresses user intent."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "not_noindexed",
|
|
33
|
+
label: "Page is indexable (not noindex)",
|
|
34
|
+
pass: !((_a2 = seoFields == null ? void 0 : seoFields.robots) == null ? void 0 : _a2.noIndex),
|
|
35
|
+
hint: "Page is set to noindex \u2014 AI overviews cannot cite noindexed pages."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "title_concise",
|
|
39
|
+
label: "Title is 30\u201360 characters",
|
|
40
|
+
pass: !!(title && title.length >= 30 && title.length <= 60),
|
|
41
|
+
hint: "Keep your title between 30\u201360 characters for optimal SERP and AI snippet display."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "og_image_present",
|
|
45
|
+
label: "OG or meta image present",
|
|
46
|
+
pass: !!(((_b = seoFields == null ? void 0 : seoFields.metaImage) == null ? void 0 : _b.asset) || ((_d = (_c = seoFields == null ? void 0 : seoFields.openGraph) == null ? void 0 : _c.image) == null ? void 0 : _d.asset)),
|
|
47
|
+
hint: "Add a meta or OG image for rich results and social previews."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "has_keywords",
|
|
51
|
+
label: "Keywords added",
|
|
52
|
+
pass: !!((seoFields == null ? void 0 : seoFields.keywords) && seoFields.keywords.filter((k) => k && k.trim().length > 0).length > 0),
|
|
53
|
+
hint: "Add at least one keyword to improve topical signal for AI classifiers."
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
}, [seoFields]);
|
|
57
|
+
const proChecks = useMemo(
|
|
58
|
+
() => licenseKey ? buildGEOProChecks(seoFields, rootDoc) : null,
|
|
59
|
+
[licenseKey, seoFields, rootDoc]
|
|
60
|
+
);
|
|
61
|
+
const freePassing = freeChecks.filter((c) => c.pass).length;
|
|
62
|
+
const proPassing = proChecks ? proChecks.filter((c) => c.pass).length : 0;
|
|
63
|
+
const totalPassing = freePassing + (proChecks ? proPassing : 0);
|
|
64
|
+
const totalChecks = freeChecks.length + (proChecks ? proChecks.length : 0);
|
|
65
|
+
const percentage = totalChecks > 0 ? totalPassing / totalChecks * 100 : 0;
|
|
66
|
+
let scoreColor = "#dc2626";
|
|
67
|
+
let scoreBg = "#fef2f2";
|
|
68
|
+
let scoreText = "#b91c1c";
|
|
69
|
+
if (percentage >= 80) {
|
|
70
|
+
scoreColor = "#16a34a";
|
|
71
|
+
scoreBg = "#f0fdf4";
|
|
72
|
+
scoreText = "#15803d";
|
|
73
|
+
} else if (percentage >= 50) {
|
|
74
|
+
scoreColor = "#ca8a04";
|
|
75
|
+
scoreBg = "#fefce8";
|
|
76
|
+
scoreText = "#a16207";
|
|
77
|
+
}
|
|
78
|
+
return /* @__PURE__ */ jsxs(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
style: {
|
|
82
|
+
borderRadius: 10,
|
|
83
|
+
border: "1px solid #e5e7eb",
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
fontFamily: "inherit"
|
|
86
|
+
},
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsxs(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
style: {
|
|
92
|
+
padding: "16px 20px 14px",
|
|
93
|
+
borderBottom: "1px solid #f3f4f6",
|
|
94
|
+
display: "flex",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
justifyContent: "space-between",
|
|
97
|
+
gap: 12
|
|
98
|
+
},
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
101
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#111827", marginBottom: 2 }, children: "AI Overview Readiness" }),
|
|
102
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af" }, children: "Signals that improve visibility in AI-generated overviews" })
|
|
103
|
+
] }),
|
|
104
|
+
/* @__PURE__ */ jsxs(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
style: {
|
|
108
|
+
backgroundColor: scoreBg,
|
|
109
|
+
borderRadius: 20,
|
|
110
|
+
padding: "4px 12px",
|
|
111
|
+
display: "flex",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
gap: 4,
|
|
114
|
+
flexShrink: 0
|
|
115
|
+
},
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 16, fontWeight: 700, color: scoreColor }, children: totalPassing }),
|
|
118
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: scoreText, opacity: 0.7 }, children: [
|
|
119
|
+
"/ ",
|
|
120
|
+
totalChecks
|
|
121
|
+
] })
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsx("div", { style: { height: 3, backgroundColor: "#f3f4f6" }, children: /* @__PURE__ */ jsx(
|
|
129
|
+
"div",
|
|
130
|
+
{
|
|
131
|
+
style: {
|
|
132
|
+
height: "100%",
|
|
133
|
+
width: `${percentage}%`,
|
|
134
|
+
backgroundColor: scoreColor,
|
|
135
|
+
transition: "width 0.4s ease"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
) }),
|
|
139
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "10px 20px" }, children: /* @__PURE__ */ jsxs(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
onClick: () => setExpanded((prev) => !prev),
|
|
144
|
+
style: {
|
|
145
|
+
background: "none",
|
|
146
|
+
border: "none",
|
|
147
|
+
padding: 0,
|
|
148
|
+
cursor: "pointer",
|
|
149
|
+
fontSize: 12,
|
|
150
|
+
color: "#6366f1",
|
|
151
|
+
fontWeight: 500,
|
|
152
|
+
display: "flex",
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
gap: 4
|
|
155
|
+
},
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsx(
|
|
158
|
+
"svg",
|
|
159
|
+
{
|
|
160
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
161
|
+
width: "12",
|
|
162
|
+
height: "12",
|
|
163
|
+
viewBox: "0 0 24 24",
|
|
164
|
+
fill: "none",
|
|
165
|
+
stroke: "currentColor",
|
|
166
|
+
strokeWidth: "2",
|
|
167
|
+
strokeLinecap: "round",
|
|
168
|
+
strokeLinejoin: "round",
|
|
169
|
+
style: {
|
|
170
|
+
display: "inline-block",
|
|
171
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
172
|
+
transition: "transform 0.2s ease",
|
|
173
|
+
flexShrink: 0
|
|
174
|
+
},
|
|
175
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
expanded ? "Hide checks" : "Show all checks"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
) }),
|
|
182
|
+
expanded && /* @__PURE__ */ jsxs("div", { children: [
|
|
183
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "0 20px 16px" }, children: [
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"div",
|
|
186
|
+
{
|
|
187
|
+
style: {
|
|
188
|
+
fontSize: 10,
|
|
189
|
+
fontWeight: 700,
|
|
190
|
+
letterSpacing: "0.08em",
|
|
191
|
+
color: "#9ca3af",
|
|
192
|
+
marginBottom: 10,
|
|
193
|
+
textTransform: "uppercase"
|
|
194
|
+
},
|
|
195
|
+
children: "Core Checks"
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: freeChecks.map((check) => /* @__PURE__ */ jsx(CheckRow, { check }, check.id)) })
|
|
199
|
+
] }),
|
|
200
|
+
/* @__PURE__ */ jsxs(
|
|
201
|
+
"div",
|
|
202
|
+
{
|
|
203
|
+
style: {
|
|
204
|
+
margin: "0 12px 12px",
|
|
205
|
+
borderRadius: 8,
|
|
206
|
+
backgroundColor: licenseKey ? "#faf5ff" : "#f9fafb",
|
|
207
|
+
border: `1px solid ${licenseKey ? "#e9d5ff" : "#f3f4f6"}`,
|
|
208
|
+
overflow: "hidden"
|
|
209
|
+
},
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsxs(
|
|
212
|
+
"div",
|
|
213
|
+
{
|
|
214
|
+
style: {
|
|
215
|
+
padding: "10px 16px",
|
|
216
|
+
borderBottom: `1px solid ${licenseKey ? "#e9d5ff" : "#f3f4f6"}`,
|
|
217
|
+
display: "flex",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
gap: 8
|
|
220
|
+
},
|
|
221
|
+
children: [
|
|
222
|
+
/* @__PURE__ */ jsx(
|
|
223
|
+
"span",
|
|
224
|
+
{
|
|
225
|
+
style: {
|
|
226
|
+
fontSize: 10,
|
|
227
|
+
fontWeight: 700,
|
|
228
|
+
letterSpacing: "0.08em",
|
|
229
|
+
color: licenseKey ? "#7c3aed" : "#9ca3af",
|
|
230
|
+
textTransform: "uppercase"
|
|
231
|
+
},
|
|
232
|
+
children: "Pro Insights"
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
!licenseKey && /* @__PURE__ */ jsx(
|
|
236
|
+
"span",
|
|
237
|
+
{
|
|
238
|
+
style: {
|
|
239
|
+
fontSize: 10,
|
|
240
|
+
backgroundColor: "#f3e8ff",
|
|
241
|
+
color: "#7c3aed",
|
|
242
|
+
borderRadius: 4,
|
|
243
|
+
padding: "1px 6px",
|
|
244
|
+
fontWeight: 600
|
|
245
|
+
},
|
|
246
|
+
children: "Unlock with licenseKey"
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
licenseKey && proChecks && /* @__PURE__ */ jsxs(
|
|
250
|
+
"span",
|
|
251
|
+
{
|
|
252
|
+
style: {
|
|
253
|
+
fontSize: 10,
|
|
254
|
+
backgroundColor: "#d8b4fe",
|
|
255
|
+
color: "#5b21b6",
|
|
256
|
+
borderRadius: 4,
|
|
257
|
+
padding: "1px 6px",
|
|
258
|
+
fontWeight: 600
|
|
259
|
+
},
|
|
260
|
+
children: [
|
|
261
|
+
proPassing,
|
|
262
|
+
" / ",
|
|
263
|
+
proChecks.length,
|
|
264
|
+
" passing"
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "10px 16px" }, children: proChecks ? /* @__PURE__ */ jsx(GEOChecklistProChecks, { checks: proChecks }) : /* @__PURE__ */ jsx(GEOLockedTeaser, {}) })
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] })
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
function CheckRow({ check }) {
|
|
281
|
+
return /* @__PURE__ */ jsxs(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
style: {
|
|
285
|
+
display: "flex",
|
|
286
|
+
alignItems: "flex-start",
|
|
287
|
+
gap: 10,
|
|
288
|
+
padding: "9px 12px",
|
|
289
|
+
borderRadius: 7,
|
|
290
|
+
backgroundColor: check.pass ? "transparent" : "#fff8f8",
|
|
291
|
+
border: `1px solid ${check.pass ? "transparent" : "#fee2e2"}`
|
|
292
|
+
},
|
|
293
|
+
children: [
|
|
294
|
+
/* @__PURE__ */ jsx(
|
|
295
|
+
"div",
|
|
296
|
+
{
|
|
297
|
+
style: {
|
|
298
|
+
width: 20,
|
|
299
|
+
height: 20,
|
|
300
|
+
borderRadius: "50%",
|
|
301
|
+
backgroundColor: check.pass ? "#dcfce7" : "#fee2e2",
|
|
302
|
+
display: "flex",
|
|
303
|
+
alignItems: "center",
|
|
304
|
+
justifyContent: "center",
|
|
305
|
+
flexShrink: 0,
|
|
306
|
+
marginTop: 1
|
|
307
|
+
},
|
|
308
|
+
children: /* @__PURE__ */ jsx(
|
|
309
|
+
"span",
|
|
310
|
+
{
|
|
311
|
+
style: {
|
|
312
|
+
fontSize: 10,
|
|
313
|
+
fontWeight: 800,
|
|
314
|
+
color: check.pass ? "#16a34a" : "#dc2626",
|
|
315
|
+
lineHeight: 1
|
|
316
|
+
},
|
|
317
|
+
children: check.pass ? "\u2713" : "\u2717"
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
323
|
+
/* @__PURE__ */ jsx(
|
|
324
|
+
"div",
|
|
325
|
+
{
|
|
326
|
+
style: {
|
|
327
|
+
fontSize: 12,
|
|
328
|
+
fontWeight: 500,
|
|
329
|
+
color: check.pass ? "#374151" : "#1f2937",
|
|
330
|
+
lineHeight: 1.4
|
|
331
|
+
},
|
|
332
|
+
children: check.label
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
!check.pass && /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginTop: 3, lineHeight: 1.5 }, children: check.hint })
|
|
336
|
+
] })
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
var GEOChecklist_default = GEOChecklist;
|
|
342
|
+
var FocusKeywordInput = (props) => {
|
|
343
|
+
var _a;
|
|
344
|
+
const { value, renderDefault, path } = props;
|
|
345
|
+
const parent = useFormValue([path[0]]);
|
|
346
|
+
const rootDoc = useFormValue([]);
|
|
347
|
+
const feedbackItems = useMemo(() => {
|
|
348
|
+
var _a2, _b;
|
|
349
|
+
if (!value) return [];
|
|
350
|
+
const kw = value.toLowerCase();
|
|
351
|
+
const title = ((parent == null ? void 0 : parent.title) || "").toLowerCase();
|
|
352
|
+
const description = ((parent == null ? void 0 : parent.description) || "").toLowerCase();
|
|
353
|
+
const slug = ((_a2 = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a2.current) || "";
|
|
354
|
+
const items = [];
|
|
355
|
+
if (title) {
|
|
356
|
+
if (title.startsWith(kw)) {
|
|
357
|
+
items.push({ text: "Keyword at start of title \u2014 excellent", color: "green" });
|
|
358
|
+
} else if (title.includes(kw)) {
|
|
359
|
+
items.push({
|
|
360
|
+
text: "Keyword in title \u2014 move it to the start for more prominence",
|
|
361
|
+
color: "orange"
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
364
|
+
items.push({ text: "Keyword missing from meta title", color: "red" });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (description) {
|
|
368
|
+
if (description.includes(kw)) {
|
|
369
|
+
items.push({ text: "Keyword in meta description", color: "green" });
|
|
370
|
+
} else {
|
|
371
|
+
items.push({ text: "Not in description \u2014 include it naturally", color: "orange" });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (slug) {
|
|
375
|
+
const kwSlug = kw.replace(/\s+/g, "-");
|
|
376
|
+
if (slug.includes(kwSlug)) {
|
|
377
|
+
items.push({ text: "Keyword in URL slug", color: "green" });
|
|
378
|
+
} else {
|
|
379
|
+
items.push({
|
|
380
|
+
text: "Keyword not in URL slug \u2014 consider it for new pages",
|
|
381
|
+
color: "orange"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const safeKw = kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
386
|
+
if (((_b = title.match(new RegExp(safeKw, "g"))) != null ? _b : []).length > 2) {
|
|
387
|
+
items.push({
|
|
388
|
+
text: "Keyword appears too many times in title \u2014 avoid stuffing",
|
|
389
|
+
color: "red"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return items;
|
|
393
|
+
}, [value, parent == null ? void 0 : parent.title, parent == null ? void 0 : parent.description, (_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current]);
|
|
394
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
395
|
+
renderDefault(props),
|
|
396
|
+
/* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
397
|
+
/* @__PURE__ */ jsx(
|
|
398
|
+
"div",
|
|
399
|
+
{
|
|
400
|
+
style: {
|
|
401
|
+
minWidth: 10,
|
|
402
|
+
height: 10,
|
|
403
|
+
borderRadius: "50%",
|
|
404
|
+
backgroundColor: item.color
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
/* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
409
|
+
] }, item.text)) })
|
|
410
|
+
] });
|
|
411
|
+
};
|
|
412
|
+
var FocusKeywordInput_default = FocusKeywordInput;
|
|
413
|
+
|
|
414
|
+
// src/utils/readability.ts
|
|
415
|
+
function countSyllables(word) {
|
|
416
|
+
var _a;
|
|
417
|
+
const w = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
418
|
+
if (w.length <= 3) return 1;
|
|
419
|
+
const groups = (_a = w.match(/[aeiouy]+/g)) != null ? _a : [];
|
|
420
|
+
let count = groups.length;
|
|
421
|
+
if (w.endsWith("e") && w.length > 1 && !/[aeiouy]/.test(w[w.length - 2])) {
|
|
422
|
+
count = Math.max(1, count - 1);
|
|
423
|
+
}
|
|
424
|
+
return Math.max(1, count);
|
|
425
|
+
}
|
|
426
|
+
function countSentences(text) {
|
|
427
|
+
const matches = text.match(/[.!?]+/g);
|
|
428
|
+
return matches ? matches.length : 1;
|
|
429
|
+
}
|
|
430
|
+
function analyzeReadability(text) {
|
|
431
|
+
const wordList = text.trim().split(/\s+/).filter(Boolean);
|
|
432
|
+
const words = wordList.length;
|
|
433
|
+
if (words < 10) return null;
|
|
434
|
+
const sentences = countSentences(text);
|
|
435
|
+
const syllables = wordList.reduce((acc, word) => acc + countSyllables(word), 0);
|
|
436
|
+
const rawScore = 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables / words);
|
|
437
|
+
const score = Math.round(rawScore * 10) / 10;
|
|
438
|
+
const rawGrade = 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59;
|
|
439
|
+
const gradeLevel = Math.round(rawGrade * 10) / 10;
|
|
440
|
+
const avgWordsPerSentence = Math.round(words / sentences * 10) / 10;
|
|
441
|
+
let label;
|
|
442
|
+
let color;
|
|
443
|
+
if (score >= 70) {
|
|
444
|
+
label = "Easy to read";
|
|
445
|
+
color = "green";
|
|
446
|
+
} else if (score >= 50) {
|
|
447
|
+
label = "Fairly easy";
|
|
448
|
+
color = "green";
|
|
449
|
+
} else if (score >= 30) {
|
|
450
|
+
label = "Moderately difficult";
|
|
451
|
+
color = "orange";
|
|
452
|
+
} else {
|
|
453
|
+
label = "Difficult to read";
|
|
454
|
+
color = "red";
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
score,
|
|
458
|
+
gradeLevel,
|
|
459
|
+
avgWordsPerSentence,
|
|
460
|
+
words,
|
|
461
|
+
sentences,
|
|
462
|
+
label,
|
|
463
|
+
color
|
|
464
|
+
};
|
|
465
|
+
}
|
|
8
466
|
var MetaDescription = (props) => {
|
|
9
467
|
const { value, renderDefault, path } = props;
|
|
10
468
|
const parent = useFormValue([path[0]]);
|
|
@@ -14,6 +472,7 @@ var MetaDescription = (props) => {
|
|
|
14
472
|
() => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
|
|
15
473
|
[value, keywords, isParentseoField]
|
|
16
474
|
);
|
|
475
|
+
const readability = useMemo(() => analyzeReadability(value || ""), [value]);
|
|
17
476
|
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
18
477
|
renderDefault(props),
|
|
19
478
|
/* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
@@ -24,7 +483,30 @@ var MetaDescription = (props) => {
|
|
|
24
483
|
}
|
|
25
484
|
),
|
|
26
485
|
/* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
27
|
-
] }, item.text)) })
|
|
486
|
+
] }, item.text)) }),
|
|
487
|
+
readability && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
488
|
+
/* @__PURE__ */ jsx(
|
|
489
|
+
"div",
|
|
490
|
+
{
|
|
491
|
+
style: {
|
|
492
|
+
minWidth: 10,
|
|
493
|
+
height: 10,
|
|
494
|
+
borderRadius: "50%",
|
|
495
|
+
backgroundColor: readability.color
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ jsxs(Text, { weight: "bold", muted: true, size: 14, children: [
|
|
500
|
+
"Readability: ",
|
|
501
|
+
readability.label,
|
|
502
|
+
" (Flesch ",
|
|
503
|
+
readability.score,
|
|
504
|
+
" \xB7 Grade",
|
|
505
|
+
" ",
|
|
506
|
+
readability.gradeLevel,
|
|
507
|
+
")"
|
|
508
|
+
] })
|
|
509
|
+
] })
|
|
28
510
|
] });
|
|
29
511
|
};
|
|
30
512
|
var MetaDescription_default = MetaDescription;
|
|
@@ -55,8 +537,204 @@ var MetaImage = (props) => {
|
|
|
55
537
|
] });
|
|
56
538
|
};
|
|
57
539
|
var MetaImage_default = MetaImage;
|
|
540
|
+
function resolveImageUrl(asset, projectId, dataset) {
|
|
541
|
+
const ref = asset == null ? void 0 : asset._ref;
|
|
542
|
+
if (!ref) return null;
|
|
543
|
+
const parts = ref.replace("image-", "").split("-");
|
|
544
|
+
const ext = parts[parts.length - 1];
|
|
545
|
+
const dims = parts[parts.length - 2];
|
|
546
|
+
const id = parts.slice(0, parts.length - 2).join("-");
|
|
547
|
+
return `https://cdn.sanity.io/images/${projectId}/${dataset}/${id}-${dims}.${ext}`;
|
|
548
|
+
}
|
|
549
|
+
var MetaTagsPreview = (props) => {
|
|
550
|
+
const { path } = props;
|
|
551
|
+
const [expanded, setExpanded] = useState(false);
|
|
552
|
+
const [copyLabel, setCopyLabel] = useState("Copy");
|
|
553
|
+
const { scheme } = useColorScheme();
|
|
554
|
+
const seoFields = useFormValue([path[0]]);
|
|
555
|
+
const client = useClient({ apiVersion: "2024-01-01" });
|
|
556
|
+
const { projectId = "", dataset = "" } = client.config();
|
|
557
|
+
const rawHtml = useMemo(() => {
|
|
558
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
559
|
+
const lines = [];
|
|
560
|
+
lines.push('<meta charset="UTF-8" />');
|
|
561
|
+
lines.push('<meta name="viewport" content="width=device-width, initial-scale=1" />');
|
|
562
|
+
if (seoFields == null ? void 0 : seoFields.title) lines.push(`<title>${seoFields.title}</title>`);
|
|
563
|
+
if (seoFields == null ? void 0 : seoFields.description)
|
|
564
|
+
lines.push(`<meta name="description" content="${seoFields.description}" />`);
|
|
565
|
+
if (seoFields == null ? void 0 : seoFields.robots) {
|
|
566
|
+
const parts = [];
|
|
567
|
+
if (seoFields.robots.noIndex) parts.push("noindex");
|
|
568
|
+
if (seoFields.robots.noFollow) parts.push("nofollow");
|
|
569
|
+
if (seoFields.robots.noTranslate) parts.push("notranslate");
|
|
570
|
+
if (seoFields.robots.noImageIndex) parts.push("noimageindex");
|
|
571
|
+
if (parts.length) lines.push(`<meta name="robots" content="${parts.join(", ")}" />`);
|
|
572
|
+
}
|
|
573
|
+
if ((_a = seoFields == null ? void 0 : seoFields.keywords) == null ? void 0 : _a.length)
|
|
574
|
+
lines.push(`<meta name="keywords" content="${seoFields.keywords.join(", ")}" />`);
|
|
575
|
+
if (seoFields == null ? void 0 : seoFields.canonicalUrl)
|
|
576
|
+
lines.push(`<link rel="canonical" href="${seoFields.canonicalUrl}" />`);
|
|
577
|
+
const og = seoFields == null ? void 0 : seoFields.openGraph;
|
|
578
|
+
if (og == null ? void 0 : og.type) lines.push(`<meta property="og:type" content="${og.type}" />`);
|
|
579
|
+
if (og == null ? void 0 : og.title) lines.push(`<meta property="og:title" content="${og.title}" />`);
|
|
580
|
+
if (og == null ? void 0 : og.description)
|
|
581
|
+
lines.push(`<meta property="og:description" content="${og.description}" />`);
|
|
582
|
+
if (og == null ? void 0 : og.siteName) lines.push(`<meta property="og:site_name" content="${og.siteName}" />`);
|
|
583
|
+
if (og == null ? void 0 : og.url) lines.push(`<meta property="og:url" content="${og.url}" />`);
|
|
584
|
+
const ogImg = (og == null ? void 0 : og.imageUrl) || resolveImageUrl((_b = og == null ? void 0 : og.image) == null ? void 0 : _b.asset, projectId, dataset) || resolveImageUrl((_c = seoFields == null ? void 0 : seoFields.metaImage) == null ? void 0 : _c.asset, projectId, dataset);
|
|
585
|
+
if (ogImg) lines.push(`<meta property="og:image" content="${ogImg}" />`);
|
|
586
|
+
const tw = seoFields == null ? void 0 : seoFields.twitter;
|
|
587
|
+
if (tw == null ? void 0 : tw.card) lines.push(`<meta name="twitter:card" content="${tw.card}" />`);
|
|
588
|
+
if (tw == null ? void 0 : tw.site) lines.push(`<meta name="twitter:site" content="${tw.site}" />`);
|
|
589
|
+
if (tw == null ? void 0 : tw.creator) lines.push(`<meta name="twitter:creator" content="${tw.creator}" />`);
|
|
590
|
+
if (tw == null ? void 0 : tw.title) lines.push(`<meta name="twitter:title" content="${tw.title}" />`);
|
|
591
|
+
if (tw == null ? void 0 : tw.description)
|
|
592
|
+
lines.push(`<meta name="twitter:description" content="${tw.description}" />`);
|
|
593
|
+
const twImg = (tw == null ? void 0 : tw.imageUrl) || resolveImageUrl((_d = tw == null ? void 0 : tw.image) == null ? void 0 : _d.asset, projectId, dataset);
|
|
594
|
+
if (twImg) lines.push(`<meta name="twitter:image" content="${twImg}" />`);
|
|
595
|
+
if ((_e = seoFields == null ? void 0 : seoFields.hreflangs) == null ? void 0 : _e.length) {
|
|
596
|
+
for (const e of seoFields.hreflangs) {
|
|
597
|
+
if (e.locale && e.url)
|
|
598
|
+
lines.push(`<link rel="alternate" hreflang="${e.locale}" href="${e.url}" />`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if ((_f = seoFields == null ? void 0 : seoFields.metaAttributes) == null ? void 0 : _f.length) {
|
|
602
|
+
for (const attr of seoFields.metaAttributes) {
|
|
603
|
+
if (attr.key && attr.type === "string" && attr.value) {
|
|
604
|
+
lines.push(`<meta name="${attr.key}" content="${attr.value}" />`);
|
|
605
|
+
} else if (attr.key && attr.type === "image" && ((_g = attr.image) == null ? void 0 : _g.asset)) {
|
|
606
|
+
const imgUrl = resolveImageUrl(attr.image.asset, projectId, dataset);
|
|
607
|
+
if (imgUrl) lines.push(`<meta name="${attr.key}" content="${imgUrl}" />`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return lines.join("\n");
|
|
612
|
+
}, [seoFields, projectId, dataset]);
|
|
613
|
+
const tagCount = useMemo(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
|
|
614
|
+
const tokens = useMemo(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
|
|
615
|
+
const handleCopy = () => {
|
|
616
|
+
navigator.clipboard.writeText(rawHtml).then(() => {
|
|
617
|
+
setCopyLabel("Copied!");
|
|
618
|
+
setTimeout(() => setCopyLabel("Copy"), 2e3);
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
const isDark = scheme === "dark";
|
|
622
|
+
const codeBg = isDark ? "#161b22" : "#f6f8fa";
|
|
623
|
+
const codeBorder = isDark ? "#30363d" : "#d0d7de";
|
|
624
|
+
const headerBg = isDark ? "#21262d" : "#f0f2f4";
|
|
625
|
+
const headerBorder = isDark ? "#30363d" : "#d0d7de";
|
|
626
|
+
const headerText = isDark ? "#e6edf3" : "#24292f";
|
|
627
|
+
return /* @__PURE__ */ jsx(Card, { padding: 3, radius: 2, tone: "default", border: true, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
628
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
629
|
+
/* @__PURE__ */ jsx(Text, { weight: "semibold", size: 1, children: "Meta Tags Preview" }),
|
|
630
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
|
|
631
|
+
/* @__PURE__ */ jsxs(Text, { size: 0, muted: true, children: [
|
|
632
|
+
tagCount,
|
|
633
|
+
" tags generated"
|
|
634
|
+
] }),
|
|
635
|
+
/* @__PURE__ */ jsx(
|
|
636
|
+
Button,
|
|
637
|
+
{
|
|
638
|
+
text: copyLabel,
|
|
639
|
+
tone: "default",
|
|
640
|
+
mode: "ghost",
|
|
641
|
+
fontSize: 1,
|
|
642
|
+
padding: 2,
|
|
643
|
+
onClick: handleCopy
|
|
644
|
+
}
|
|
645
|
+
)
|
|
646
|
+
] })
|
|
647
|
+
] }),
|
|
648
|
+
/* @__PURE__ */ jsxs(
|
|
649
|
+
"button",
|
|
650
|
+
{
|
|
651
|
+
type: "button",
|
|
652
|
+
onClick: () => setExpanded((p) => !p),
|
|
653
|
+
style: {
|
|
654
|
+
display: "flex",
|
|
655
|
+
alignItems: "center",
|
|
656
|
+
justifyContent: "center",
|
|
657
|
+
gap: 6,
|
|
658
|
+
width: "100%",
|
|
659
|
+
background: headerBg,
|
|
660
|
+
border: `1px solid ${headerBorder}`,
|
|
661
|
+
borderRadius: 6,
|
|
662
|
+
padding: "6px 12px",
|
|
663
|
+
cursor: "pointer",
|
|
664
|
+
fontSize: 12,
|
|
665
|
+
fontWeight: 500,
|
|
666
|
+
color: headerText
|
|
667
|
+
},
|
|
668
|
+
children: [
|
|
669
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
|
|
670
|
+
expanded ? "Hide tags" : "Show tags"
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
expanded && /* @__PURE__ */ jsxs(
|
|
675
|
+
"div",
|
|
676
|
+
{
|
|
677
|
+
style: {
|
|
678
|
+
borderRadius: 6,
|
|
679
|
+
border: `1px solid ${codeBorder}`,
|
|
680
|
+
overflow: "hidden"
|
|
681
|
+
},
|
|
682
|
+
children: [
|
|
683
|
+
/* @__PURE__ */ jsxs(
|
|
684
|
+
"div",
|
|
685
|
+
{
|
|
686
|
+
style: {
|
|
687
|
+
background: headerBg,
|
|
688
|
+
borderBottom: `1px solid ${headerBorder}`,
|
|
689
|
+
padding: "6px 14px",
|
|
690
|
+
display: "flex",
|
|
691
|
+
alignItems: "center",
|
|
692
|
+
gap: 6
|
|
693
|
+
},
|
|
694
|
+
children: [
|
|
695
|
+
/* @__PURE__ */ jsx(
|
|
696
|
+
"span",
|
|
697
|
+
{
|
|
698
|
+
style: {
|
|
699
|
+
width: 8,
|
|
700
|
+
height: 8,
|
|
701
|
+
borderRadius: "50%",
|
|
702
|
+
background: isDark ? "#388bfd" : "#0550ae",
|
|
703
|
+
display: "inline-block"
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
),
|
|
707
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
|
|
708
|
+
]
|
|
709
|
+
}
|
|
710
|
+
),
|
|
711
|
+
/* @__PURE__ */ jsx(
|
|
712
|
+
Box,
|
|
713
|
+
{
|
|
714
|
+
as: "pre",
|
|
715
|
+
padding: 3,
|
|
716
|
+
style: {
|
|
717
|
+
margin: 0,
|
|
718
|
+
maxHeight: 420,
|
|
719
|
+
overflow: "auto",
|
|
720
|
+
fontSize: 12,
|
|
721
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
722
|
+
lineHeight: 1.7,
|
|
723
|
+
whiteSpace: "pre-wrap",
|
|
724
|
+
wordBreak: "break-word",
|
|
725
|
+
backgroundColor: codeBg
|
|
726
|
+
},
|
|
727
|
+
children: tokens.map((token, idx) => /* @__PURE__ */ jsx("span", { style: { color: token.color }, children: token.text }, idx))
|
|
728
|
+
}
|
|
729
|
+
)
|
|
730
|
+
]
|
|
731
|
+
}
|
|
732
|
+
)
|
|
733
|
+
] }) });
|
|
734
|
+
};
|
|
735
|
+
var MetaTagsPreview_default = MetaTagsPreview;
|
|
58
736
|
var MetaTitle = (props) => {
|
|
59
|
-
var _a, _b;
|
|
737
|
+
var _a, _b, _c;
|
|
60
738
|
const { value, renderDefault, path, schemaType } = props;
|
|
61
739
|
const { options } = schemaType;
|
|
62
740
|
const parent = useFormValue([path[0]]);
|
|
@@ -65,6 +743,7 @@ var MetaTitle = (props) => {
|
|
|
65
743
|
const rootDoc = (_a = useFormValue([])) != null ? _a : {};
|
|
66
744
|
const client = useClient({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
|
|
67
745
|
const [groqTitleSuffix, setGroqTitleSuffix] = useState("");
|
|
746
|
+
const [isDuplicateTitle, setIsDuplicateTitle] = useState(false);
|
|
68
747
|
const titleSuffixQuery = options == null ? void 0 : options.titleSuffixQuery;
|
|
69
748
|
const titleSuffixOption = options == null ? void 0 : options.titleSuffix;
|
|
70
749
|
useEffect(() => {
|
|
@@ -75,6 +754,21 @@ var MetaTitle = (props) => {
|
|
|
75
754
|
setGroqTitleSuffix("");
|
|
76
755
|
});
|
|
77
756
|
}, [titleSuffixQuery, client]);
|
|
757
|
+
const currentDocId = (_c = rootDoc._id) != null ? _c : "";
|
|
758
|
+
useEffect(() => {
|
|
759
|
+
if (!(value == null ? void 0 : value.trim()) || !currentDocId) {
|
|
760
|
+
setIsDuplicateTitle(false);
|
|
761
|
+
return () => {
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const timer = setTimeout(() => {
|
|
765
|
+
client.fetch(
|
|
766
|
+
`count(*[seo.title == $title && _id != $id && !(_id in path("drafts.**"))])`,
|
|
767
|
+
{ title: value, id: currentDocId }
|
|
768
|
+
).then((count) => setIsDuplicateTitle(count > 0)).catch(() => setIsDuplicateTitle(false));
|
|
769
|
+
}, 600);
|
|
770
|
+
return () => clearTimeout(timer);
|
|
771
|
+
}, [value, client, currentDocId]);
|
|
78
772
|
const resolvedSuffix = useMemo(() => {
|
|
79
773
|
if (titleSuffixQuery) return groqTitleSuffix;
|
|
80
774
|
if (!titleSuffixOption) return "";
|
|
@@ -90,20 +784,36 @@ var MetaTitle = (props) => {
|
|
|
90
784
|
);
|
|
91
785
|
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
92
786
|
renderDefault(props),
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
787
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
788
|
+
feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
789
|
+
/* @__PURE__ */ jsx(
|
|
790
|
+
"div",
|
|
791
|
+
{
|
|
792
|
+
style: {
|
|
793
|
+
minWidth: 10,
|
|
794
|
+
height: 10,
|
|
795
|
+
borderRadius: "50%",
|
|
796
|
+
backgroundColor: item.color
|
|
797
|
+
}
|
|
102
798
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
799
|
+
),
|
|
800
|
+
/* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
801
|
+
] }, item.text)),
|
|
802
|
+
isDuplicateTitle && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
803
|
+
/* @__PURE__ */ jsx(
|
|
804
|
+
"div",
|
|
805
|
+
{
|
|
806
|
+
style: {
|
|
807
|
+
minWidth: 10,
|
|
808
|
+
height: 10,
|
|
809
|
+
borderRadius: "50%",
|
|
810
|
+
backgroundColor: "orange"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
),
|
|
814
|
+
/* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
|
|
815
|
+
] })
|
|
816
|
+
] })
|
|
107
817
|
] });
|
|
108
818
|
};
|
|
109
819
|
var MetaTitle_default = MetaTitle;
|
|
@@ -132,7 +842,7 @@ var DEFAULT_FIELD_INFO = {
|
|
|
132
842
|
},
|
|
133
843
|
robots: {
|
|
134
844
|
title: "Robots Settings",
|
|
135
|
-
description: "Configure how search engine crawlers should index
|
|
845
|
+
description: "Configure how search engine crawlers should index, follow links, translate content, and index images on this page."
|
|
136
846
|
},
|
|
137
847
|
metaAttributes: {
|
|
138
848
|
title: "Additional Meta Attributes",
|
|
@@ -201,6 +911,22 @@ var DEFAULT_FIELD_INFO = {
|
|
|
201
911
|
twitterImageUrl: {
|
|
202
912
|
title: "X Image URL",
|
|
203
913
|
description: "Enter the full URL of the image for X (formerly Twitter). Ensure the image is accessible and meets the recommended size."
|
|
914
|
+
},
|
|
915
|
+
focusKeyword: {
|
|
916
|
+
title: "Focus Keyword",
|
|
917
|
+
description: "The primary keyword you want this page to rank for. Used to check keyword placement in title, description, and URL."
|
|
918
|
+
},
|
|
919
|
+
hreflangs: {
|
|
920
|
+
title: "Hreflang Entries",
|
|
921
|
+
description: "Add alternate language/region versions of this page. Include x-default for the fallback URL."
|
|
922
|
+
},
|
|
923
|
+
geoChecklist: {
|
|
924
|
+
title: "AI Overview Readiness",
|
|
925
|
+
description: "Checklist of signals that improve visibility in AI-generated overviews."
|
|
926
|
+
},
|
|
927
|
+
metaTagsPreview: {
|
|
928
|
+
title: "Meta Tags Preview",
|
|
929
|
+
description: "Preview the full HTML <head> meta tags generated for this page."
|
|
204
930
|
}
|
|
205
931
|
};
|
|
206
932
|
var getFieldInfo = (fieldName, fieldOverrides) => {
|
|
@@ -677,7 +1403,7 @@ function twitter(config = {}) {
|
|
|
677
1403
|
}
|
|
678
1404
|
|
|
679
1405
|
// src/schemas/index.ts
|
|
680
|
-
var LazySeoPreview = lazy(() => import('./SeoPreview-
|
|
1406
|
+
var LazySeoPreview = lazy(() => import('./SeoPreview-4PPAF4NO.js'));
|
|
681
1407
|
var SeoPreviewWrapper = (props) => createElement(Suspense, { fallback: null }, createElement(LazySeoPreview, props));
|
|
682
1408
|
function buildFieldGroupMap(groups) {
|
|
683
1409
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -822,6 +1548,60 @@ function seoFieldsSchema(config = {}) {
|
|
|
822
1548
|
})),
|
|
823
1549
|
fieldGroupMap
|
|
824
1550
|
),
|
|
1551
|
+
withGroup(
|
|
1552
|
+
defineField(__spreadProps(__spreadValues({
|
|
1553
|
+
name: "focusKeyword"
|
|
1554
|
+
}, getFieldInfo("focusKeyword", config.fieldOverrides)), {
|
|
1555
|
+
type: "string",
|
|
1556
|
+
components: {
|
|
1557
|
+
input: FocusKeywordInput_default
|
|
1558
|
+
},
|
|
1559
|
+
hidden: getFieldHiddenFunction("focusKeyword", config)
|
|
1560
|
+
})),
|
|
1561
|
+
fieldGroupMap
|
|
1562
|
+
),
|
|
1563
|
+
withGroup(
|
|
1564
|
+
defineField(__spreadProps(__spreadValues({
|
|
1565
|
+
name: "hreflangs"
|
|
1566
|
+
}, getFieldInfo("hreflangs", config.fieldOverrides)), {
|
|
1567
|
+
type: "array",
|
|
1568
|
+
of: [{ type: "hreflangEntry" }],
|
|
1569
|
+
description: "Add alternate language/region versions. Include x-default for the fallback URL.",
|
|
1570
|
+
hidden: getFieldHiddenFunction("hreflangs", config)
|
|
1571
|
+
})),
|
|
1572
|
+
fieldGroupMap
|
|
1573
|
+
),
|
|
1574
|
+
...config.geo === false ? [] : [
|
|
1575
|
+
withGroup(
|
|
1576
|
+
defineField(__spreadProps(__spreadValues({
|
|
1577
|
+
name: "geoChecklist"
|
|
1578
|
+
}, getFieldInfo("geoChecklist", config.fieldOverrides)), {
|
|
1579
|
+
type: "string",
|
|
1580
|
+
readOnly: true,
|
|
1581
|
+
components: {
|
|
1582
|
+
input: GEOChecklist_default
|
|
1583
|
+
},
|
|
1584
|
+
options: __spreadValues({}, config.licenseKey ? { licenseKey: config.licenseKey } : {}),
|
|
1585
|
+
hidden: getFieldHiddenFunction("geoChecklist", config)
|
|
1586
|
+
})),
|
|
1587
|
+
fieldGroupMap
|
|
1588
|
+
)
|
|
1589
|
+
],
|
|
1590
|
+
...config.metaTagsPreview === false ? [] : [
|
|
1591
|
+
withGroup(
|
|
1592
|
+
defineField(__spreadProps(__spreadValues({
|
|
1593
|
+
name: "metaTagsPreview"
|
|
1594
|
+
}, getFieldInfo("metaTagsPreview", config.fieldOverrides)), {
|
|
1595
|
+
type: "string",
|
|
1596
|
+
readOnly: true,
|
|
1597
|
+
components: {
|
|
1598
|
+
input: MetaTagsPreview_default
|
|
1599
|
+
},
|
|
1600
|
+
hidden: getFieldHiddenFunction("metaTagsPreview", config)
|
|
1601
|
+
})),
|
|
1602
|
+
fieldGroupMap
|
|
1603
|
+
)
|
|
1604
|
+
],
|
|
825
1605
|
withGroup(openGraph(config), fieldGroupMap),
|
|
826
1606
|
withGroup(twitter(config), fieldGroupMap)
|
|
827
1607
|
]
|
|
@@ -891,6 +1671,38 @@ function baseMeta(config = {}) {
|
|
|
891
1671
|
]
|
|
892
1672
|
});
|
|
893
1673
|
}
|
|
1674
|
+
var hreflang_default = defineType({
|
|
1675
|
+
name: "hreflangEntry",
|
|
1676
|
+
title: "Hreflang Entry",
|
|
1677
|
+
type: "object",
|
|
1678
|
+
fields: [
|
|
1679
|
+
defineField({
|
|
1680
|
+
name: "locale",
|
|
1681
|
+
title: "Language / Region",
|
|
1682
|
+
type: "string",
|
|
1683
|
+
description: "BCP 47 tag \u2014 e.g. en, fr-FR, x-default",
|
|
1684
|
+
validation: (Rule) => Rule.required().custom((value) => {
|
|
1685
|
+
if (!value) return "Required";
|
|
1686
|
+
if (!/^(x-default|[a-z]{2,3}(-[A-Z]{2,3})?)$/.test(value)) {
|
|
1687
|
+
return "Must be a valid BCP 47 tag (e.g. en, fr-FR, x-default)";
|
|
1688
|
+
}
|
|
1689
|
+
return true;
|
|
1690
|
+
})
|
|
1691
|
+
}),
|
|
1692
|
+
defineField({
|
|
1693
|
+
name: "url",
|
|
1694
|
+
title: "URL",
|
|
1695
|
+
type: "url",
|
|
1696
|
+
validation: (Rule) => Rule.required().uri({ scheme: ["http", "https"] })
|
|
1697
|
+
})
|
|
1698
|
+
],
|
|
1699
|
+
preview: {
|
|
1700
|
+
select: {
|
|
1701
|
+
title: "locale",
|
|
1702
|
+
subtitle: "url"
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
894
1706
|
var metaAttribute_default = defineType({
|
|
895
1707
|
name: "metaAttribute",
|
|
896
1708
|
title: "Meta Attribute",
|
|
@@ -981,9 +1793,23 @@ var robots_default = defineType({
|
|
|
981
1793
|
type: "boolean",
|
|
982
1794
|
initialValue: false,
|
|
983
1795
|
description: "Enable this to prevent search engines from following links on this page. Links will not pass SEO value."
|
|
1796
|
+
}),
|
|
1797
|
+
defineField({
|
|
1798
|
+
name: "noTranslate",
|
|
1799
|
+
title: "No Translate",
|
|
1800
|
+
type: "boolean",
|
|
1801
|
+
initialValue: false,
|
|
1802
|
+
description: "Enable this to prevent search engines from offering a translated version of this page in search results."
|
|
1803
|
+
}),
|
|
1804
|
+
defineField({
|
|
1805
|
+
name: "noImageIndex",
|
|
1806
|
+
title: "No Image Index",
|
|
1807
|
+
type: "boolean",
|
|
1808
|
+
initialValue: false,
|
|
1809
|
+
description: "Enable this to prevent images on this page from appearing in image search results."
|
|
984
1810
|
})
|
|
985
1811
|
],
|
|
986
|
-
description: "Select how search engines should index
|
|
1812
|
+
description: "Select how search engines should index, follow links, translate content, and index images on this page."
|
|
987
1813
|
});
|
|
988
1814
|
|
|
989
1815
|
// src/schemas/types/index.ts
|
|
@@ -999,13 +1825,14 @@ function types(config = {}) {
|
|
|
999
1825
|
// pass config here
|
|
1000
1826
|
metaAttribute_default,
|
|
1001
1827
|
metaTag_default,
|
|
1002
|
-
robots_default
|
|
1828
|
+
robots_default,
|
|
1829
|
+
hreflang_default
|
|
1003
1830
|
];
|
|
1004
1831
|
}
|
|
1005
1832
|
|
|
1006
1833
|
// src/plugin.ts
|
|
1007
|
-
var resolveDashboardConfig = (healthDashboard, rootApiVersion) => {
|
|
1008
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
1834
|
+
var resolveDashboardConfig = (healthDashboard, rootApiVersion, rootLicenseKey) => {
|
|
1835
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
1009
1836
|
const cfg = typeof healthDashboard === "object" ? healthDashboard : void 0;
|
|
1010
1837
|
const deprecationWarnings = [];
|
|
1011
1838
|
if ((cfg == null ? void 0 : cfg.apiVersion) !== void 0) {
|
|
@@ -1018,27 +1845,37 @@ var resolveDashboardConfig = (healthDashboard, rootApiVersion) => {
|
|
|
1018
1845
|
'[sanity-plugin-seofields] "healthDashboard.apiVersion" is deprecated. Move it to the root "apiVersion" option instead.'
|
|
1019
1846
|
);
|
|
1020
1847
|
}
|
|
1848
|
+
if ((cfg == null ? void 0 : cfg.licenseKey) !== void 0 && !rootLicenseKey) {
|
|
1849
|
+
deprecationWarnings.push({
|
|
1850
|
+
key: "healthDashboard.licenseKey \u2192 licenseKey",
|
|
1851
|
+
version: "v1.8.0",
|
|
1852
|
+
changelogUrl: "https://github.com/hardik-143/sanity-plugin-seofields/blob/main/CHANGELOG.md"
|
|
1853
|
+
});
|
|
1854
|
+
console.warn(
|
|
1855
|
+
'[sanity-plugin-seofields] "healthDashboard.licenseKey" is deprecated. Move it to the root "licenseKey" option instead.'
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1021
1858
|
return {
|
|
1022
1859
|
enabled: healthDashboard !== false,
|
|
1023
|
-
toolTitle: (_b = (_a = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _a.title) != null ? _b : "SEO Health",
|
|
1024
|
-
toolName: (
|
|
1025
|
-
icon: (
|
|
1026
|
-
title: (
|
|
1027
|
-
description: (
|
|
1860
|
+
toolTitle: (_c = (_b = (_a = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _a.title) != null ? _b : cfg == null ? void 0 : cfg.toolTitle) != null ? _c : "SEO Health",
|
|
1861
|
+
toolName: (_e = (_d = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _d.name) != null ? _e : "seo-health-dashboard",
|
|
1862
|
+
icon: (_f = cfg == null ? void 0 : cfg.content) == null ? void 0 : _f.icon,
|
|
1863
|
+
title: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.title,
|
|
1864
|
+
description: (_h = cfg == null ? void 0 : cfg.content) == null ? void 0 : _h.description,
|
|
1028
1865
|
showTypeColumn: cfg == null ? void 0 : cfg.showTypeColumn,
|
|
1029
1866
|
showDocumentId: cfg == null ? void 0 : cfg.showDocumentId,
|
|
1030
|
-
queryTypes: (
|
|
1031
|
-
queryRequireSeo: (
|
|
1032
|
-
queryGroq: (
|
|
1033
|
-
apiVersion: (
|
|
1034
|
-
licenseKey: cfg == null ? void 0 : cfg.licenseKey,
|
|
1867
|
+
queryTypes: (_i = cfg == null ? void 0 : cfg.query) == null ? void 0 : _i.types,
|
|
1868
|
+
queryRequireSeo: (_j = cfg == null ? void 0 : cfg.query) == null ? void 0 : _j.requireSeo,
|
|
1869
|
+
queryGroq: (_k = cfg == null ? void 0 : cfg.query) == null ? void 0 : _k.groq,
|
|
1870
|
+
apiVersion: (_l = cfg == null ? void 0 : cfg.apiVersion) != null ? _l : rootApiVersion,
|
|
1871
|
+
licenseKey: rootLicenseKey != null ? rootLicenseKey : cfg == null ? void 0 : cfg.licenseKey,
|
|
1035
1872
|
typeDisplayLabels: cfg == null ? void 0 : cfg.typeDisplayLabels,
|
|
1036
1873
|
typeColumnMode: cfg == null ? void 0 : cfg.typeColumnMode,
|
|
1037
1874
|
titleField: cfg == null ? void 0 : cfg.titleField,
|
|
1038
1875
|
getDocumentBadge: cfg == null ? void 0 : cfg.getDocumentBadge,
|
|
1039
|
-
loadingLicense: (
|
|
1040
|
-
loadingDocuments: (
|
|
1041
|
-
noDocuments: (
|
|
1876
|
+
loadingLicense: (_m = cfg == null ? void 0 : cfg.content) == null ? void 0 : _m.loadingLicense,
|
|
1877
|
+
loadingDocuments: (_n = cfg == null ? void 0 : cfg.content) == null ? void 0 : _n.loadingDocuments,
|
|
1878
|
+
noDocuments: (_o = cfg == null ? void 0 : cfg.content) == null ? void 0 : _o.noDocuments,
|
|
1042
1879
|
previewMode: cfg == null ? void 0 : cfg.previewMode,
|
|
1043
1880
|
structureTool: cfg == null ? void 0 : cfg.structureTool,
|
|
1044
1881
|
exportEnabled: (() => {
|
|
@@ -1053,14 +1890,15 @@ var resolveDashboardConfig = (healthDashboard, rootApiVersion) => {
|
|
|
1053
1890
|
if (typeof exportCfg === "object" && exportCfg.formats) return exportCfg.formats;
|
|
1054
1891
|
return ["csv", "json"];
|
|
1055
1892
|
})(),
|
|
1056
|
-
compactStats: (
|
|
1893
|
+
compactStats: (_p = cfg == null ? void 0 : cfg.compactStats) != null ? _p : false,
|
|
1057
1894
|
deprecationWarnings
|
|
1058
1895
|
};
|
|
1059
1896
|
};
|
|
1060
1897
|
var seofields = definePlugin((config = {}) => {
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1898
|
+
const cfg = config;
|
|
1899
|
+
const { healthDashboard = true, licenseKey, publishGate } = cfg;
|
|
1900
|
+
const dash = resolveDashboardConfig(healthDashboard, cfg.apiVersion, licenseKey);
|
|
1901
|
+
const LazySeoHealthTool = lazy(() => import('./SeoHealthTool-3CUKGADZ.js'));
|
|
1064
1902
|
const BoundSeoHealthTool = () => createElement(
|
|
1065
1903
|
Suspense,
|
|
1066
1904
|
{ fallback: null },
|
|
@@ -1090,12 +1928,23 @@ var seofields = definePlugin((config = {}) => {
|
|
|
1090
1928
|
_deprecationWarnings: dash.deprecationWarnings
|
|
1091
1929
|
})
|
|
1092
1930
|
);
|
|
1093
|
-
return __spreadValues({
|
|
1931
|
+
return __spreadValues(__spreadValues({
|
|
1094
1932
|
name: "sanity-plugin-seofields",
|
|
1095
1933
|
schema: {
|
|
1096
|
-
types: types(
|
|
1934
|
+
types: types(cfg)
|
|
1097
1935
|
}
|
|
1098
|
-
},
|
|
1936
|
+
}, publishGate && licenseKey && {
|
|
1937
|
+
document: {
|
|
1938
|
+
actions: (prev, context) => {
|
|
1939
|
+
var _a;
|
|
1940
|
+
if (((_a = publishGate.types) == null ? void 0 : _a.length) && !publishGate.types.includes(context.schemaType))
|
|
1941
|
+
return prev;
|
|
1942
|
+
return prev.map(
|
|
1943
|
+
(Action) => Action.action === "publish" ? createSeoPublishGateAction(Action, __spreadProps(__spreadValues({}, publishGate), { licenseKey })) : Action
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}), dash.enabled && {
|
|
1099
1948
|
tools: [
|
|
1100
1949
|
{
|
|
1101
1950
|
name: dash.toolName,
|
|
@@ -1107,28 +1956,125 @@ var seofields = definePlugin((config = {}) => {
|
|
|
1107
1956
|
});
|
|
1108
1957
|
});
|
|
1109
1958
|
var plugin_default = seofields;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1959
|
+
|
|
1960
|
+
// src/utils/scoring.ts
|
|
1961
|
+
var getStatusCategory = (score) => {
|
|
1962
|
+
if (score >= 80) return "excellent";
|
|
1963
|
+
if (score >= 60) return "good";
|
|
1964
|
+
if (score >= 40) return "fair";
|
|
1965
|
+
if (score > 0) return "poor";
|
|
1966
|
+
return "missing";
|
|
1967
|
+
};
|
|
1968
|
+
var scoreMetaTitle = (title) => {
|
|
1969
|
+
const issues = [];
|
|
1970
|
+
let score = 0;
|
|
1971
|
+
if (title && title.length >= 50 && title.length <= 60) {
|
|
1972
|
+
score = 15;
|
|
1973
|
+
} else if (title && title.length > 0) {
|
|
1974
|
+
score = 10;
|
|
1975
|
+
if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
|
|
1976
|
+
if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
|
|
1977
|
+
} else {
|
|
1978
|
+
issues.push("Missing meta title");
|
|
1979
|
+
}
|
|
1980
|
+
return { score, issues };
|
|
1981
|
+
};
|
|
1982
|
+
var scoreMetaDescription = (description) => {
|
|
1983
|
+
const issues = [];
|
|
1984
|
+
let score = 0;
|
|
1985
|
+
if (description && description.length >= 120 && description.length <= 160) {
|
|
1986
|
+
score = 15;
|
|
1987
|
+
} else if (description && description.length > 0) {
|
|
1988
|
+
score = 10;
|
|
1989
|
+
if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
|
|
1990
|
+
if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
|
|
1991
|
+
} else {
|
|
1992
|
+
issues.push("Missing meta description");
|
|
1993
|
+
}
|
|
1994
|
+
return { score, issues };
|
|
1995
|
+
};
|
|
1996
|
+
var scoreOpenGraph = (openGraph2) => {
|
|
1997
|
+
const issues = [];
|
|
1998
|
+
let score = 0;
|
|
1999
|
+
if (openGraph2) {
|
|
2000
|
+
if (openGraph2.title) score += 6;
|
|
2001
|
+
else issues.push("Missing OG title");
|
|
2002
|
+
if (openGraph2.description) score += 6;
|
|
2003
|
+
else issues.push("Missing OG description");
|
|
2004
|
+
if (openGraph2.image) score += 6;
|
|
2005
|
+
else issues.push("Missing OG image");
|
|
2006
|
+
if (openGraph2.type) score += 7;
|
|
2007
|
+
else issues.push("Missing OG type");
|
|
2008
|
+
} else {
|
|
2009
|
+
issues.push("Open Graph not configured");
|
|
2010
|
+
}
|
|
2011
|
+
return { score, issues };
|
|
2012
|
+
};
|
|
2013
|
+
var scoreTwitterCard = (twitter2) => {
|
|
2014
|
+
const issues = [];
|
|
2015
|
+
let score = 0;
|
|
2016
|
+
if (twitter2) {
|
|
2017
|
+
if (twitter2.title) score += 5;
|
|
2018
|
+
else issues.push("Missing Twitter title");
|
|
2019
|
+
if (twitter2.description) score += 5;
|
|
2020
|
+
else issues.push("Missing Twitter description");
|
|
2021
|
+
if (twitter2.image) score += 5;
|
|
2022
|
+
else issues.push("Missing Twitter image");
|
|
2023
|
+
} else {
|
|
2024
|
+
issues.push("Twitter Card not configured");
|
|
2025
|
+
}
|
|
2026
|
+
return { score, issues };
|
|
2027
|
+
};
|
|
2028
|
+
var calculateHealthScore = (doc) => {
|
|
2029
|
+
if (!doc.seo) {
|
|
2030
|
+
return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
|
|
2031
|
+
}
|
|
2032
|
+
const seo = doc.seo;
|
|
2033
|
+
const title = seo.title;
|
|
2034
|
+
const description = seo.description;
|
|
2035
|
+
const keywords = seo.keywords;
|
|
2036
|
+
const robots = seo.robots;
|
|
2037
|
+
const openGraph2 = seo.openGraph;
|
|
2038
|
+
const twitter2 = seo.twitter;
|
|
2039
|
+
let score = 0;
|
|
2040
|
+
const issues = [];
|
|
2041
|
+
const titleResult = scoreMetaTitle(title);
|
|
2042
|
+
score += titleResult.score;
|
|
2043
|
+
issues.push(...titleResult.issues);
|
|
2044
|
+
const descResult = scoreMetaDescription(description);
|
|
2045
|
+
score += descResult.score;
|
|
2046
|
+
issues.push(...descResult.issues);
|
|
2047
|
+
if (seo.metaImage) score += 10;
|
|
2048
|
+
else issues.push("Missing meta image");
|
|
2049
|
+
if (keywords && keywords.length > 0) score += 10;
|
|
2050
|
+
else issues.push("No keywords defined");
|
|
2051
|
+
if (robots && !robots.noIndex) score += 5;
|
|
2052
|
+
else if (!robots) score += 5;
|
|
2053
|
+
const ogResult = scoreOpenGraph(openGraph2);
|
|
2054
|
+
score += ogResult.score;
|
|
2055
|
+
issues.push(...ogResult.issues);
|
|
2056
|
+
const twResult = scoreTwitterCard(twitter2);
|
|
2057
|
+
score += twResult.score;
|
|
2058
|
+
issues.push(...twResult.issues);
|
|
2059
|
+
const hasMetaImage = !!seo.metaImage;
|
|
2060
|
+
const hasOgImage = !!(openGraph2 && openGraph2.image);
|
|
2061
|
+
const hasTwitterImage = !!(twitter2 && twitter2.image);
|
|
2062
|
+
if (hasMetaImage && hasOgImage && hasTwitterImage) {
|
|
2063
|
+
score += 5;
|
|
2064
|
+
} else {
|
|
2065
|
+
const missingImages = [];
|
|
2066
|
+
if (!hasMetaImage) missingImages.push("meta image");
|
|
2067
|
+
if (!hasOgImage) missingImages.push("OG image");
|
|
2068
|
+
if (!hasTwitterImage) missingImages.push("Twitter image");
|
|
2069
|
+
issues.push(`Missing images for full score: ${missingImages.join(", ")}`);
|
|
2070
|
+
}
|
|
2071
|
+
const status = getStatusCategory(score);
|
|
2072
|
+
return { score, status, issues };
|
|
2073
|
+
};
|
|
1128
2074
|
|
|
1129
2075
|
// src/index.ts
|
|
1130
2076
|
var src_default = plugin_default;
|
|
1131
2077
|
|
|
1132
|
-
export { types as allSchemas, baseMeta as baseMetaSchema,
|
|
2078
|
+
export { types as allSchemas, baseMeta as baseMetaSchema, calculateHealthScore, src_default as default, metaAttribute_default as metaAttributeSchema, metaTag_default as metaTagSchema, openGraph as openGraphSchema, robots_default as robotsSchema, seoFieldsSchema, twitter as twitterSchema };
|
|
1133
2079
|
//# sourceMappingURL=index.js.map
|
|
1134
2080
|
//# sourceMappingURL=index.js.map
|