sanity-plugin-seofields 1.7.0 → 1.9.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 +440 -29
- package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.cts +78 -0
- package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.ts +78 -0
- package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
- package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
- package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
- package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
- package/dist/{SeoPreview-WYH7NYNM.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
- package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
- package/dist/{SeoPreview-KEGGQSPJ.js → SeoPreview-UNQBKTQO.js} +14 -11
- package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
- package/dist/chunk-3OK3S2F7.cjs +673 -0
- package/dist/chunk-3OK3S2F7.cjs.map +1 -0
- package/dist/{chunk-GWPOJSGW.cjs → chunk-5XTQRILL.cjs} +1022 -307
- package/dist/chunk-5XTQRILL.cjs.map +1 -0
- package/dist/{chunk-2NMEKWO5.js → chunk-7N4MLTMR.js} +8 -15
- package/dist/chunk-7N4MLTMR.js.map +1 -0
- package/dist/{chunk-S367Y35J.cjs → chunk-A57XNQC7.cjs} +9 -20
- package/dist/chunk-A57XNQC7.cjs.map +1 -0
- package/dist/chunk-BWLJDK5J.js +624 -0
- package/dist/chunk-BWLJDK5J.js.map +1 -0
- package/dist/chunk-FUUWBQE2.cjs +195 -0
- package/dist/chunk-FUUWBQE2.cjs.map +1 -0
- package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
- package/dist/chunk-HHO2AKAP.js.map +1 -0
- package/dist/chunk-KIEO6QG3.js +195 -0
- package/dist/chunk-KIEO6QG3.js.map +1 -0
- package/dist/chunk-R2U7JF7U.cjs +624 -0
- package/dist/chunk-R2U7JF7U.cjs.map +1 -0
- package/dist/{chunk-HRKRV2X7.js → chunk-XZKQWV2H.js} +974 -148
- package/dist/chunk-XZKQWV2H.js.map +1 -0
- package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
- package/dist/chunk-Y46ACXM4.cjs.map +1 -0
- package/dist/chunk-Z5AOUU4H.js +673 -0
- package/dist/chunk-Z5AOUU4H.js.map +1 -0
- package/dist/cli.js +27 -27
- package/dist/{component-DQ4KuQQD.d.ts → component-CjT1hvxh.d.ts} +3 -27
- package/dist/{component-D1sBm6ts.d.cts → component-WYh0z8as.d.cts} +3 -27
- package/dist/define-cli.cjs +2 -4
- package/dist/define-cli.cjs.map +1 -1
- package/dist/define-cli.js +4 -4
- package/dist/define-cli.js.map +1 -1
- package/dist/head.cjs +14 -0
- package/dist/head.cjs.map +1 -0
- package/dist/head.d.cts +254 -0
- package/dist/head.d.ts +254 -0
- package/dist/head.js +14 -0
- package/dist/head.js.map +1 -0
- package/dist/index.cjs +1797 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -598
- package/dist/index.d.ts +11 -598
- package/dist/index.js +1777 -197
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +210 -447
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +8 -200
- package/dist/next.d.ts +8 -200
- package/dist/next.js +200 -124
- package/dist/next.js.map +1 -1
- package/dist/plugin-DQdThyxG.d.ts +449 -0
- package/dist/plugin-DoGeZP79.d.cts +449 -0
- package/dist/schema/next.cjs +173 -319
- package/dist/schema/next.cjs.map +1 -1
- package/dist/schema/next.d.cts +4 -4
- package/dist/schema/next.d.ts +4 -4
- package/dist/schema/next.js +172 -8
- package/dist/schema/next.js.map +1 -1
- package/dist/schema.cjs +390 -489
- 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 +236 -13
- package/dist/schema.js.map +1 -1
- package/dist/server.cjs +149 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +102 -0
- package/dist/server.d.ts +102 -0
- package/dist/server.js +149 -0
- package/dist/server.js.map +1 -0
- package/dist/{types-CeBULNFF.d.ts → types-BAdN1RMu.d.ts} +2 -2
- package/dist/{types-_wBLg6RV.d.cts → types-C5i3KMgs.d.cts} +2 -2
- package/dist/{types-9_wbCIi9.d.ts → types-DP-DiW6f.d.cts} +29 -2
- package/dist/{types-Bb49oRBV.d.cts → types-DRLUGLtX.d.ts} +29 -2
- package/dist/{types-B91ena4g.d.cts → types-DxlPXihz.d.cts} +8 -1
- package/dist/{types-B91ena4g.d.ts → types-DxlPXihz.d.ts} +8 -1
- package/package.json +25 -3
- package/dist/SeoHealthDashboard-IP5BJ322.cjs +0 -10
- package/dist/SeoHealthDashboard-IP5BJ322.cjs.map +0 -1
- package/dist/SeoHealthDashboard-PTUEVYZL.js +0 -4
- package/dist/SeoHealthDashboard-PTUEVYZL.js.map +0 -1
- 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/SeoPreview-KEGGQSPJ.js.map +0 -1
- package/dist/SeoPreview-WYH7NYNM.cjs.map +0 -1
- package/dist/chunk-2NMEKWO5.js.map +0 -1
- package/dist/chunk-5YRH47VQ.cjs +0 -2167
- 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 +0 -2161
- package/dist/chunk-AW7OKBQQ.js.map +0 -1
- package/dist/chunk-FONJYVF2.js +0 -610
- package/dist/chunk-FONJYVF2.js.map +0 -1
- package/dist/chunk-GWPOJSGW.cjs.map +0 -1
- package/dist/chunk-HDZZQCH7.js.map +0 -1
- package/dist/chunk-HRKRV2X7.js.map +0 -1
- package/dist/chunk-S367Y35J.cjs.map +0 -1
- package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,45 +1,922 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
analyzeReadability,
|
|
3
|
+
generateSeoText,
|
|
4
|
+
pickTestOutput
|
|
5
|
+
} from "./chunk-Z5AOUU4H.js";
|
|
6
|
+
import {
|
|
7
|
+
SchemaOrgJsonLdPreview,
|
|
8
|
+
tokenizeHtml
|
|
9
|
+
} from "./chunk-XZKQWV2H.js";
|
|
10
|
+
import {
|
|
11
|
+
getFocusKeywordPlacement,
|
|
12
|
+
getMetaDescriptionValidationMessages,
|
|
13
|
+
getMetaImageValidation,
|
|
14
|
+
getMetaTitleValidationMessages,
|
|
15
|
+
getOgDescriptionValidation,
|
|
16
|
+
getOgImageUrlValidation,
|
|
17
|
+
getOgImageValidation,
|
|
18
|
+
getOgTitleValidation,
|
|
19
|
+
getTwitterDescriptionValidation,
|
|
20
|
+
getTwitterImageUrlValidation,
|
|
21
|
+
getTwitterImageValidation,
|
|
22
|
+
getTwitterTitleValidation,
|
|
23
|
+
hasKeywordOveruse,
|
|
24
|
+
hasMatchingKeyword
|
|
25
|
+
} from "./chunk-HHO2AKAP.js";
|
|
26
|
+
import {
|
|
27
|
+
__spreadProps,
|
|
28
|
+
__spreadValues
|
|
29
|
+
} from "./chunk-7N4MLTMR.js";
|
|
7
30
|
|
|
31
|
+
// src/plugin.ts
|
|
32
|
+
import { createElement as createElement2, lazy as lazy2, Suspense as Suspense2 } from "react";
|
|
33
|
+
import { definePlugin } from "sanity";
|
|
34
|
+
|
|
35
|
+
// src/actions/SeoPublishGateAction.tsx
|
|
36
|
+
import { createSeoPublishGateAction } from "seofields-pro";
|
|
37
|
+
|
|
38
|
+
// src/schemas/index.ts
|
|
39
|
+
import { createElement, lazy, Suspense } from "react";
|
|
40
|
+
import {
|
|
41
|
+
defineField as defineField3,
|
|
42
|
+
defineType as defineType3
|
|
43
|
+
} from "sanity";
|
|
44
|
+
|
|
45
|
+
// src/components/geo/GEOChecklist.tsx
|
|
46
|
+
import { useMemo, useState } from "react";
|
|
47
|
+
import { useFormValue } from "sanity";
|
|
48
|
+
import {
|
|
49
|
+
buildGEOProChecks,
|
|
50
|
+
GEOChecklistProChecks,
|
|
51
|
+
GEOLockedTeaser
|
|
52
|
+
} from "seofields-pro";
|
|
53
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
54
|
+
var GEOChecklist = (props) => {
|
|
55
|
+
var _a;
|
|
56
|
+
const { path, schemaType } = props;
|
|
57
|
+
const [expanded, setExpanded] = useState(false);
|
|
58
|
+
const licenseKey = (_a = schemaType.options) == null ? void 0 : _a.licenseKey;
|
|
59
|
+
const seoFields = useFormValue([path[0]]);
|
|
60
|
+
const rootDoc = useFormValue([]);
|
|
61
|
+
const freeChecks = useMemo(() => {
|
|
62
|
+
var _a2, _b, _c, _d;
|
|
63
|
+
const desc = (seoFields == null ? void 0 : seoFields.description) || "";
|
|
64
|
+
const title = (seoFields == null ? void 0 : seoFields.title) || "";
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
id: "description_answer_ready",
|
|
68
|
+
label: "Description is answer-ready (100\u2013160 chars)",
|
|
69
|
+
pass: !!desc && desc.length >= 100 && desc.length <= 160,
|
|
70
|
+
hint: "Write a concise 100\u2013160 char answer that directly addresses user intent."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "not_noindexed",
|
|
74
|
+
label: "Page is indexable (not noindex)",
|
|
75
|
+
pass: !((_a2 = seoFields == null ? void 0 : seoFields.robots) == null ? void 0 : _a2.noIndex),
|
|
76
|
+
hint: "Page is set to noindex \u2014 AI overviews cannot cite noindexed pages."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "title_concise",
|
|
80
|
+
label: "Title is 30\u201360 characters",
|
|
81
|
+
pass: !!(title && title.length >= 30 && title.length <= 60),
|
|
82
|
+
hint: "Keep your title between 30\u201360 characters for optimal SERP and AI snippet display."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "og_image_present",
|
|
86
|
+
label: "OG or meta image present",
|
|
87
|
+
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)),
|
|
88
|
+
hint: "Add a meta or OG image for rich results and social previews."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "has_keywords",
|
|
92
|
+
label: "Keywords added",
|
|
93
|
+
pass: !!((seoFields == null ? void 0 : seoFields.keywords) && seoFields.keywords.filter((k) => k && k.trim().length > 0).length > 0),
|
|
94
|
+
hint: "Add at least one keyword to improve topical signal for AI classifiers."
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
}, [seoFields]);
|
|
98
|
+
const proChecks = useMemo(
|
|
99
|
+
() => licenseKey ? buildGEOProChecks(seoFields, rootDoc) : null,
|
|
100
|
+
[licenseKey, seoFields, rootDoc]
|
|
101
|
+
);
|
|
102
|
+
const freePassing = freeChecks.filter((c) => c.pass).length;
|
|
103
|
+
const proPassing = proChecks ? proChecks.filter((c) => c.pass).length : 0;
|
|
104
|
+
const totalPassing = freePassing + (proChecks ? proPassing : 0);
|
|
105
|
+
const totalChecks = freeChecks.length + (proChecks ? proChecks.length : 0);
|
|
106
|
+
const percentage = totalChecks > 0 ? totalPassing / totalChecks * 100 : 0;
|
|
107
|
+
let scoreColor = "#dc2626";
|
|
108
|
+
let scoreBg = "#fef2f2";
|
|
109
|
+
let scoreText = "#b91c1c";
|
|
110
|
+
if (percentage >= 80) {
|
|
111
|
+
scoreColor = "#16a34a";
|
|
112
|
+
scoreBg = "#f0fdf4";
|
|
113
|
+
scoreText = "#15803d";
|
|
114
|
+
} else if (percentage >= 50) {
|
|
115
|
+
scoreColor = "#ca8a04";
|
|
116
|
+
scoreBg = "#fefce8";
|
|
117
|
+
scoreText = "#a16207";
|
|
118
|
+
}
|
|
119
|
+
return /* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
style: {
|
|
123
|
+
borderRadius: 10,
|
|
124
|
+
border: "1px solid #e5e7eb",
|
|
125
|
+
overflow: "hidden",
|
|
126
|
+
fontFamily: "inherit"
|
|
127
|
+
},
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsxs(
|
|
130
|
+
"div",
|
|
131
|
+
{
|
|
132
|
+
style: {
|
|
133
|
+
padding: "16px 20px 14px",
|
|
134
|
+
borderBottom: "1px solid #f3f4f6",
|
|
135
|
+
display: "flex",
|
|
136
|
+
alignItems: "center",
|
|
137
|
+
justifyContent: "space-between",
|
|
138
|
+
gap: 12
|
|
139
|
+
},
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
142
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#111827", marginBottom: 2 }, children: "AI Overview Readiness" }),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af" }, children: "Signals that improve visibility in AI-generated overviews" })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsxs(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
style: {
|
|
149
|
+
backgroundColor: scoreBg,
|
|
150
|
+
borderRadius: 20,
|
|
151
|
+
padding: "4px 12px",
|
|
152
|
+
display: "flex",
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
gap: 4,
|
|
155
|
+
flexShrink: 0
|
|
156
|
+
},
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 16, fontWeight: 700, color: scoreColor }, children: totalPassing }),
|
|
159
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: scoreText, opacity: 0.7 }, children: [
|
|
160
|
+
"/ ",
|
|
161
|
+
totalChecks
|
|
162
|
+
] })
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsx("div", { style: { height: 3, backgroundColor: "#f3f4f6" }, children: /* @__PURE__ */ jsx(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
style: {
|
|
173
|
+
height: "100%",
|
|
174
|
+
width: `${percentage}%`,
|
|
175
|
+
backgroundColor: scoreColor,
|
|
176
|
+
transition: "width 0.4s ease"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
) }),
|
|
180
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "10px 20px" }, children: /* @__PURE__ */ jsxs(
|
|
181
|
+
"button",
|
|
182
|
+
{
|
|
183
|
+
type: "button",
|
|
184
|
+
onClick: () => setExpanded((prev) => !prev),
|
|
185
|
+
style: {
|
|
186
|
+
background: "none",
|
|
187
|
+
border: "none",
|
|
188
|
+
padding: 0,
|
|
189
|
+
cursor: "pointer",
|
|
190
|
+
fontSize: 12,
|
|
191
|
+
color: "#6366f1",
|
|
192
|
+
fontWeight: 500,
|
|
193
|
+
display: "flex",
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
gap: 4
|
|
196
|
+
},
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ jsx(
|
|
199
|
+
"svg",
|
|
200
|
+
{
|
|
201
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
202
|
+
width: "12",
|
|
203
|
+
height: "12",
|
|
204
|
+
viewBox: "0 0 24 24",
|
|
205
|
+
fill: "none",
|
|
206
|
+
stroke: "currentColor",
|
|
207
|
+
strokeWidth: "2",
|
|
208
|
+
strokeLinecap: "round",
|
|
209
|
+
strokeLinejoin: "round",
|
|
210
|
+
style: {
|
|
211
|
+
display: "inline-block",
|
|
212
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
213
|
+
transition: "transform 0.2s ease",
|
|
214
|
+
flexShrink: 0
|
|
215
|
+
},
|
|
216
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
217
|
+
}
|
|
218
|
+
),
|
|
219
|
+
expanded ? "Hide checks" : "Show all checks"
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
) }),
|
|
223
|
+
expanded && /* @__PURE__ */ jsxs("div", { children: [
|
|
224
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "0 20px 16px" }, children: [
|
|
225
|
+
/* @__PURE__ */ jsx(
|
|
226
|
+
"div",
|
|
227
|
+
{
|
|
228
|
+
style: {
|
|
229
|
+
fontSize: 10,
|
|
230
|
+
fontWeight: 700,
|
|
231
|
+
letterSpacing: "0.08em",
|
|
232
|
+
color: "#9ca3af",
|
|
233
|
+
marginBottom: 10,
|
|
234
|
+
textTransform: "uppercase"
|
|
235
|
+
},
|
|
236
|
+
children: "Core Checks"
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: freeChecks.map((check) => /* @__PURE__ */ jsx(CheckRow, { check }, check.id)) })
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsxs(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
style: {
|
|
245
|
+
margin: "0 12px 12px",
|
|
246
|
+
borderRadius: 8,
|
|
247
|
+
backgroundColor: licenseKey ? "#faf5ff" : "#f9fafb",
|
|
248
|
+
border: `1px solid ${licenseKey ? "#e9d5ff" : "#f3f4f6"}`,
|
|
249
|
+
overflow: "hidden"
|
|
250
|
+
},
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ jsxs(
|
|
253
|
+
"div",
|
|
254
|
+
{
|
|
255
|
+
style: {
|
|
256
|
+
padding: "10px 16px",
|
|
257
|
+
borderBottom: `1px solid ${licenseKey ? "#e9d5ff" : "#f3f4f6"}`,
|
|
258
|
+
display: "flex",
|
|
259
|
+
alignItems: "center",
|
|
260
|
+
gap: 8
|
|
261
|
+
},
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ jsx(
|
|
264
|
+
"span",
|
|
265
|
+
{
|
|
266
|
+
style: {
|
|
267
|
+
fontSize: 10,
|
|
268
|
+
fontWeight: 700,
|
|
269
|
+
letterSpacing: "0.08em",
|
|
270
|
+
color: licenseKey ? "#7c3aed" : "#9ca3af",
|
|
271
|
+
textTransform: "uppercase"
|
|
272
|
+
},
|
|
273
|
+
children: "Pro Insights"
|
|
274
|
+
}
|
|
275
|
+
),
|
|
276
|
+
!licenseKey && /* @__PURE__ */ jsx(
|
|
277
|
+
"span",
|
|
278
|
+
{
|
|
279
|
+
style: {
|
|
280
|
+
fontSize: 10,
|
|
281
|
+
backgroundColor: "#f3e8ff",
|
|
282
|
+
color: "#7c3aed",
|
|
283
|
+
borderRadius: 4,
|
|
284
|
+
padding: "1px 6px",
|
|
285
|
+
fontWeight: 600
|
|
286
|
+
},
|
|
287
|
+
children: "Unlock with licenseKey"
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
licenseKey && proChecks && /* @__PURE__ */ jsxs(
|
|
291
|
+
"span",
|
|
292
|
+
{
|
|
293
|
+
style: {
|
|
294
|
+
fontSize: 10,
|
|
295
|
+
backgroundColor: "#d8b4fe",
|
|
296
|
+
color: "#5b21b6",
|
|
297
|
+
borderRadius: 4,
|
|
298
|
+
padding: "1px 6px",
|
|
299
|
+
fontWeight: 600
|
|
300
|
+
},
|
|
301
|
+
children: [
|
|
302
|
+
proPassing,
|
|
303
|
+
" / ",
|
|
304
|
+
proChecks.length,
|
|
305
|
+
" passing"
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "10px 16px" }, children: proChecks ? /* @__PURE__ */ jsx(GEOChecklistProChecks, { checks: proChecks }) : /* @__PURE__ */ jsx(GEOLockedTeaser, {}) })
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
] })
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
function CheckRow({ check }) {
|
|
322
|
+
return /* @__PURE__ */ jsxs(
|
|
323
|
+
"div",
|
|
324
|
+
{
|
|
325
|
+
style: {
|
|
326
|
+
display: "flex",
|
|
327
|
+
alignItems: "flex-start",
|
|
328
|
+
gap: 10,
|
|
329
|
+
padding: "9px 12px",
|
|
330
|
+
borderRadius: 7,
|
|
331
|
+
backgroundColor: check.pass ? "transparent" : "#fff8f8",
|
|
332
|
+
border: `1px solid ${check.pass ? "transparent" : "#fee2e2"}`
|
|
333
|
+
},
|
|
334
|
+
children: [
|
|
335
|
+
/* @__PURE__ */ jsx(
|
|
336
|
+
"div",
|
|
337
|
+
{
|
|
338
|
+
style: {
|
|
339
|
+
width: 20,
|
|
340
|
+
height: 20,
|
|
341
|
+
borderRadius: "50%",
|
|
342
|
+
backgroundColor: check.pass ? "#dcfce7" : "#fee2e2",
|
|
343
|
+
display: "flex",
|
|
344
|
+
alignItems: "center",
|
|
345
|
+
justifyContent: "center",
|
|
346
|
+
flexShrink: 0,
|
|
347
|
+
marginTop: 1
|
|
348
|
+
},
|
|
349
|
+
children: /* @__PURE__ */ jsx(
|
|
350
|
+
"span",
|
|
351
|
+
{
|
|
352
|
+
style: {
|
|
353
|
+
fontSize: 10,
|
|
354
|
+
fontWeight: 800,
|
|
355
|
+
color: check.pass ? "#16a34a" : "#dc2626",
|
|
356
|
+
lineHeight: 1
|
|
357
|
+
},
|
|
358
|
+
children: check.pass ? "\u2713" : "\u2717"
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
),
|
|
363
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
364
|
+
/* @__PURE__ */ jsx(
|
|
365
|
+
"div",
|
|
366
|
+
{
|
|
367
|
+
style: {
|
|
368
|
+
fontSize: 12,
|
|
369
|
+
fontWeight: 500,
|
|
370
|
+
color: check.pass ? "#374151" : "#1f2937",
|
|
371
|
+
lineHeight: 1.4
|
|
372
|
+
},
|
|
373
|
+
children: check.label
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
!check.pass && /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginTop: 3, lineHeight: 1.5 }, children: check.hint })
|
|
377
|
+
] })
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
var GEOChecklist_default = GEOChecklist;
|
|
383
|
+
|
|
384
|
+
// src/components/meta/FocusKeywordInput.tsx
|
|
385
|
+
import { Stack, Text as Text2 } from "@sanity/ui";
|
|
386
|
+
import { useMemo as useMemo2 } from "react";
|
|
387
|
+
import { PatchEvent, set, useFormValue as useFormValue3 } from "sanity";
|
|
388
|
+
|
|
389
|
+
// src/components/ai/SeoGenButton.tsx
|
|
390
|
+
import { SparklesIcon } from "@sanity/icons";
|
|
391
|
+
import { Button, Card, Flex, Text } from "@sanity/ui";
|
|
392
|
+
import { useCallback, useEffect, useRef, useState as useState2 } from "react";
|
|
393
|
+
import { useFormValue as useFormValue2, useWorkspace } from "sanity";
|
|
394
|
+
|
|
395
|
+
// src/utils/extractDocText.ts
|
|
396
|
+
function extractBlockText(block) {
|
|
397
|
+
if (block._type === "block" && Array.isArray(block.children)) {
|
|
398
|
+
return block.children.map((s) => {
|
|
399
|
+
var _a;
|
|
400
|
+
return (_a = s.text) != null ? _a : "";
|
|
401
|
+
}).join("");
|
|
402
|
+
}
|
|
403
|
+
const parts = [];
|
|
404
|
+
for (const key of Object.keys(block)) {
|
|
405
|
+
if (key === "_type" || key === "_key") continue;
|
|
406
|
+
const val = block[key];
|
|
407
|
+
if (typeof val === "string" && val.trim()) parts.push(val.trim());
|
|
408
|
+
else if (Array.isArray(val)) parts.push(extractArrayText(val));
|
|
409
|
+
}
|
|
410
|
+
return parts.filter(Boolean).join(" ");
|
|
411
|
+
}
|
|
412
|
+
function extractArrayText(arr) {
|
|
413
|
+
return arr.map((b) => extractBlockText(b)).filter(Boolean).join("\n");
|
|
414
|
+
}
|
|
415
|
+
function extractFieldText(value) {
|
|
416
|
+
if (typeof value === "string") return value;
|
|
417
|
+
if (!Array.isArray(value)) return "";
|
|
418
|
+
return extractArrayText(value).replace(/\n{3,}/g, "\n\n").trim();
|
|
419
|
+
}
|
|
420
|
+
function extractDocText(doc, bodyField) {
|
|
421
|
+
const fields = [bodyField].flat();
|
|
422
|
+
const text = fields.map((f) => extractFieldText(doc[f])).filter(Boolean).join("\n\n");
|
|
423
|
+
return text.slice(0, 4e3);
|
|
424
|
+
}
|
|
425
|
+
function resolveContentFields(content, docType) {
|
|
426
|
+
var _a;
|
|
427
|
+
if (!content || typeof content === "string" || Array.isArray(content)) return content != null ? content : "body";
|
|
428
|
+
const key = docType && docType in content ? docType : "default";
|
|
429
|
+
return (_a = content[key]) != null ? _a : "body";
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/components/ai/SeoGenButton.tsx
|
|
433
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
434
|
+
function SeoGenButton({
|
|
435
|
+
field,
|
|
436
|
+
ai,
|
|
437
|
+
seoFieldPath,
|
|
438
|
+
onGenerate
|
|
439
|
+
}) {
|
|
440
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
441
|
+
const [state, setState] = useState2("idle");
|
|
442
|
+
const [errorMsg, setErrorMsg] = useState2(null);
|
|
443
|
+
const abortRef = useRef(null);
|
|
444
|
+
const cooldownRef = useRef(null);
|
|
445
|
+
const rootDoc = useFormValue2([]);
|
|
446
|
+
const workspace = useWorkspace();
|
|
447
|
+
const seoObj = useFormValue2([seoFieldPath]);
|
|
448
|
+
const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
|
|
449
|
+
const keywords = (_b = seoObj == null ? void 0 : seoObj.keywords) != null ? _b : [];
|
|
450
|
+
const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
|
|
451
|
+
const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
|
|
452
|
+
const meta = field === "focusKeyword" ? {
|
|
453
|
+
title: (_c = seoObj == null ? void 0 : seoObj.title) != null ? _c : void 0,
|
|
454
|
+
description: (_d = seoObj == null ? void 0 : seoObj.description) != null ? _d : void 0,
|
|
455
|
+
slug: (_f = (_e = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _e.current) != null ? _f : void 0
|
|
456
|
+
} : void 0;
|
|
457
|
+
const handleGenerate = useCallback(async () => {
|
|
458
|
+
var _a2;
|
|
459
|
+
if (!ai) return;
|
|
460
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
461
|
+
if (cooldownRef.current) clearTimeout(cooldownRef.current);
|
|
462
|
+
setState("loading");
|
|
463
|
+
setErrorMsg(null);
|
|
464
|
+
try {
|
|
465
|
+
let result;
|
|
466
|
+
if (ai.testMode) {
|
|
467
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
468
|
+
result = pickTestOutput(field);
|
|
469
|
+
} else {
|
|
470
|
+
const controller = new AbortController();
|
|
471
|
+
abortRef.current = controller;
|
|
472
|
+
result = await generateSeoText(field, content, focusKeyword, keywords, {
|
|
473
|
+
config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
|
|
474
|
+
signal: controller.signal,
|
|
475
|
+
meta
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
onGenerate(result);
|
|
479
|
+
setState("cooldown");
|
|
480
|
+
cooldownRef.current = setTimeout(() => setState("idle"), 2e3);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
if (err.name === "AbortError") {
|
|
483
|
+
setState("idle");
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
setErrorMsg(err instanceof Error ? err.message : "Generation failed.");
|
|
487
|
+
setState("error");
|
|
488
|
+
}
|
|
489
|
+
}, [ai, field, content, focusKeyword, keywords, meta, onGenerate, workspace.projectId]);
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
return () => {
|
|
492
|
+
var _a2;
|
|
493
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
494
|
+
if (cooldownRef.current) clearTimeout(cooldownRef.current);
|
|
495
|
+
};
|
|
496
|
+
}, []);
|
|
497
|
+
const isConfigured = Boolean(
|
|
498
|
+
(ai == null ? void 0 : ai.apiKey) || (ai == null ? void 0 : ai.endpoint) || (ai == null ? void 0 : ai.testMode) || (ai == null ? void 0 : ai.provider) === "ollama"
|
|
499
|
+
);
|
|
500
|
+
if (!isConfigured) return null;
|
|
501
|
+
const isLoading = state === "loading";
|
|
502
|
+
const isTestMode = Boolean(ai == null ? void 0 : ai.testMode);
|
|
503
|
+
let buttonText = "Generate with AI";
|
|
504
|
+
if (isLoading) buttonText = "Generating\u2026";
|
|
505
|
+
else if (isTestMode) buttonText = "Generate (test)";
|
|
506
|
+
const isFullWidth = ((_g = ai == null ? void 0 : ai.buttonWidth) != null ? _g : "full") === "full";
|
|
507
|
+
return /* @__PURE__ */ jsxs2(Flex, { direction: "column", gap: 1, align: isFullWidth ? "stretch" : "flex-start", children: [
|
|
508
|
+
/* @__PURE__ */ jsx2(
|
|
509
|
+
Button,
|
|
510
|
+
{
|
|
511
|
+
mode: "ghost",
|
|
512
|
+
tone: "primary",
|
|
513
|
+
paddingX: isFullWidth ? 2 : 3,
|
|
514
|
+
paddingY: 3,
|
|
515
|
+
fontSize: 1,
|
|
516
|
+
icon: SparklesIcon,
|
|
517
|
+
text: buttonText,
|
|
518
|
+
onClick: handleGenerate,
|
|
519
|
+
disabled: isLoading || state === "cooldown"
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
!content && /* @__PURE__ */ jsxs2(Text, { size: 0, muted: true, children: [
|
|
523
|
+
"(Set ",
|
|
524
|
+
/* @__PURE__ */ jsx2("code", { children: "ai.content" }),
|
|
525
|
+
" in plugin config for better results)"
|
|
526
|
+
] }),
|
|
527
|
+
state === "error" && errorMsg && /* @__PURE__ */ jsx2(Card, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx2(Text, { size: 1, children: errorMsg }) })
|
|
528
|
+
] });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/components/meta/FocusKeywordInput.tsx
|
|
532
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
533
|
+
var FocusKeywordInput = (props) => {
|
|
534
|
+
var _a;
|
|
535
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
536
|
+
const { options } = schemaType;
|
|
537
|
+
const parent = useFormValue3([path[0]]);
|
|
538
|
+
const rootDoc = useFormValue3([]);
|
|
539
|
+
const feedbackItems = useMemo2(() => {
|
|
540
|
+
var _a2;
|
|
541
|
+
if (!value) return [];
|
|
542
|
+
const title = (parent == null ? void 0 : parent.title) || "";
|
|
543
|
+
const description = (parent == null ? void 0 : parent.description) || "";
|
|
544
|
+
const slug = ((_a2 = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a2.current) || "";
|
|
545
|
+
const placement = getFocusKeywordPlacement(value, title, description);
|
|
546
|
+
const items = [];
|
|
547
|
+
if (title) {
|
|
548
|
+
if (placement.atStartOfTitle) {
|
|
549
|
+
items.push({ text: "Keyword at start of title \u2014 excellent", color: "green" });
|
|
550
|
+
} else if (placement.inTitle) {
|
|
551
|
+
items.push({
|
|
552
|
+
text: "Keyword in title \u2014 move it to the start for more prominence",
|
|
553
|
+
color: "orange"
|
|
554
|
+
});
|
|
555
|
+
} else {
|
|
556
|
+
items.push({ text: "Keyword missing from meta title", color: "red" });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (description) {
|
|
560
|
+
if (placement.inDescription) {
|
|
561
|
+
items.push({ text: "Keyword in meta description", color: "green" });
|
|
562
|
+
} else {
|
|
563
|
+
items.push({ text: "Not in description \u2014 include it naturally", color: "orange" });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (slug) {
|
|
567
|
+
const kwSlug = value.toLowerCase().replace(/\s+/g, "-");
|
|
568
|
+
if (slug.includes(kwSlug)) {
|
|
569
|
+
items.push({ text: "Keyword in URL slug", color: "green" });
|
|
570
|
+
} else {
|
|
571
|
+
items.push({
|
|
572
|
+
text: "Keyword not in URL slug \u2014 consider it for new pages",
|
|
573
|
+
color: "orange"
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (placement.isStuffed) {
|
|
578
|
+
items.push({
|
|
579
|
+
text: "Keyword appears too many times in title \u2014 avoid stuffing",
|
|
580
|
+
color: "red"
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return items;
|
|
584
|
+
}, [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]);
|
|
585
|
+
return /* @__PURE__ */ jsxs3(Stack, { space: 3, children: [
|
|
586
|
+
renderDefault(props),
|
|
587
|
+
/* @__PURE__ */ jsx3(
|
|
588
|
+
SeoGenButton,
|
|
589
|
+
{
|
|
590
|
+
field: "focusKeyword",
|
|
591
|
+
ai: options == null ? void 0 : options.ai,
|
|
592
|
+
seoFieldPath: String(path[0]),
|
|
593
|
+
onGenerate: (v) => onChange(PatchEvent.from(set(v)))
|
|
594
|
+
}
|
|
595
|
+
),
|
|
596
|
+
/* @__PURE__ */ jsx3(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
597
|
+
/* @__PURE__ */ jsx3(
|
|
598
|
+
"div",
|
|
599
|
+
{
|
|
600
|
+
style: {
|
|
601
|
+
minWidth: 10,
|
|
602
|
+
height: 10,
|
|
603
|
+
borderRadius: "50%",
|
|
604
|
+
backgroundColor: item.color
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
),
|
|
608
|
+
/* @__PURE__ */ jsx3(Text2, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
609
|
+
] }, item.text)) })
|
|
610
|
+
] });
|
|
611
|
+
};
|
|
612
|
+
var FocusKeywordInput_default = FocusKeywordInput;
|
|
613
|
+
|
|
614
|
+
// src/components/meta/KeywordsInput.tsx
|
|
615
|
+
import { Stack as Stack3 } from "@sanity/ui";
|
|
616
|
+
import { PatchEvent as PatchEvent2, set as set2 } from "sanity";
|
|
617
|
+
|
|
618
|
+
// src/components/ai/KeywordSuggestions.tsx
|
|
619
|
+
import { SparklesIcon as SparklesIcon2 } from "@sanity/icons";
|
|
620
|
+
import { Badge, Box, Button as Button2, Card as Card2, Flex as Flex2, Spinner, Stack as Stack2, Text as Text3 } from "@sanity/ui";
|
|
621
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
622
|
+
import { useFormValue as useFormValue4, useWorkspace as useWorkspace2 } from "sanity";
|
|
623
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
624
|
+
function KeywordSuggestions({
|
|
625
|
+
ai,
|
|
626
|
+
seoFieldPath,
|
|
627
|
+
existing,
|
|
628
|
+
onAdd,
|
|
629
|
+
onAddAll
|
|
630
|
+
}) {
|
|
631
|
+
var _a;
|
|
632
|
+
const [panelState, setPanelState] = useState3("idle");
|
|
633
|
+
const [suggestions, setSuggestions] = useState3([]);
|
|
634
|
+
const [errorMsg, setErrorMsg] = useState3(null);
|
|
635
|
+
const abortRef = useRef2(null);
|
|
636
|
+
const rootDoc = useFormValue4([]);
|
|
637
|
+
const workspace = useWorkspace2();
|
|
638
|
+
const seoObj = useFormValue4([seoFieldPath]);
|
|
639
|
+
const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
|
|
640
|
+
const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
|
|
641
|
+
const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
|
|
642
|
+
const handleSuggest = useCallback2(async () => {
|
|
643
|
+
var _a2;
|
|
644
|
+
if (!ai) return;
|
|
645
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
646
|
+
abortRef.current = new AbortController();
|
|
647
|
+
setPanelState("loading");
|
|
648
|
+
setErrorMsg(null);
|
|
649
|
+
setSuggestions([]);
|
|
650
|
+
try {
|
|
651
|
+
let csv;
|
|
652
|
+
if (ai.testMode) {
|
|
653
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
654
|
+
csv = pickTestOutput("keywords");
|
|
655
|
+
} else {
|
|
656
|
+
csv = await generateSeoText("keywords", content, focusKeyword, existing, {
|
|
657
|
+
config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
|
|
658
|
+
signal: abortRef.current.signal
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const parsed = csv.split(",").map((k) => k.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
|
|
662
|
+
if (parsed.length === 0) {
|
|
663
|
+
setErrorMsg("No keywords returned. Try again.");
|
|
664
|
+
setPanelState("error");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
setSuggestions(parsed);
|
|
668
|
+
setPanelState("suggestions");
|
|
669
|
+
} catch (err) {
|
|
670
|
+
if (err.name === "AbortError") {
|
|
671
|
+
setPanelState("idle");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
setErrorMsg(err instanceof Error ? err.message : "Keyword generation failed.");
|
|
675
|
+
setPanelState("error");
|
|
676
|
+
}
|
|
677
|
+
}, [ai, content, focusKeyword, existing, workspace.projectId]);
|
|
678
|
+
const handleAdd = useCallback2(
|
|
679
|
+
(kw) => {
|
|
680
|
+
onAdd(kw);
|
|
681
|
+
setSuggestions((prev) => prev.filter((s) => s !== kw));
|
|
682
|
+
if (suggestions.length <= 1) setPanelState("idle");
|
|
683
|
+
},
|
|
684
|
+
[onAdd, suggestions.length]
|
|
685
|
+
);
|
|
686
|
+
const handleAddAll = useCallback2(() => {
|
|
687
|
+
const toAdd = suggestions.filter((kw) => !existing.includes(kw));
|
|
688
|
+
if (toAdd.length > 0) onAddAll(toAdd);
|
|
689
|
+
setSuggestions([]);
|
|
690
|
+
setPanelState("idle");
|
|
691
|
+
}, [suggestions, existing, onAddAll]);
|
|
692
|
+
const handleDismiss = useCallback2(() => {
|
|
693
|
+
var _a2;
|
|
694
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
695
|
+
setSuggestions([]);
|
|
696
|
+
setPanelState("idle");
|
|
697
|
+
setErrorMsg(null);
|
|
698
|
+
}, []);
|
|
699
|
+
useEffect2(() => {
|
|
700
|
+
return () => {
|
|
701
|
+
var _a2;
|
|
702
|
+
(_a2 = abortRef.current) == null ? void 0 : _a2.abort();
|
|
703
|
+
};
|
|
704
|
+
}, []);
|
|
705
|
+
const isConfigured = Boolean(
|
|
706
|
+
(ai == null ? void 0 : ai.apiKey) || (ai == null ? void 0 : ai.endpoint) || (ai == null ? void 0 : ai.testMode) || (ai == null ? void 0 : ai.provider) === "ollama"
|
|
707
|
+
);
|
|
708
|
+
if (!isConfigured) return null;
|
|
709
|
+
if (panelState === "idle" || panelState === "error") {
|
|
710
|
+
return /* @__PURE__ */ jsxs4(Stack2, { space: 2, children: [
|
|
711
|
+
/* @__PURE__ */ jsx4(
|
|
712
|
+
Button2,
|
|
713
|
+
{
|
|
714
|
+
mode: "ghost",
|
|
715
|
+
tone: "primary",
|
|
716
|
+
paddingX: 2,
|
|
717
|
+
paddingY: 3,
|
|
718
|
+
fontSize: 1,
|
|
719
|
+
icon: SparklesIcon2,
|
|
720
|
+
text: (ai == null ? void 0 : ai.testMode) ? "Suggest Keywords (test)" : "Suggest Keywords with AI",
|
|
721
|
+
onClick: handleSuggest
|
|
722
|
+
}
|
|
723
|
+
),
|
|
724
|
+
panelState === "error" && errorMsg && /* @__PURE__ */ jsx4(Card2, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx4(Text3, { size: 1, children: errorMsg }) })
|
|
725
|
+
] });
|
|
726
|
+
}
|
|
727
|
+
if (panelState === "loading") {
|
|
728
|
+
return /* @__PURE__ */ jsxs4(Flex2, { align: "center", gap: 2, paddingY: 2, children: [
|
|
729
|
+
/* @__PURE__ */ jsx4(Spinner, { muted: true }),
|
|
730
|
+
/* @__PURE__ */ jsx4(Text3, { size: 1, muted: true, children: "Finding keywords\u2026" })
|
|
731
|
+
] });
|
|
732
|
+
}
|
|
733
|
+
const pendingSuggestions = suggestions.filter((kw) => !existing.includes(kw));
|
|
734
|
+
const alreadyAdded = suggestions.filter((kw) => existing.includes(kw));
|
|
735
|
+
return /* @__PURE__ */ jsx4(Card2, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ jsxs4(Stack2, { space: 3, children: [
|
|
736
|
+
/* @__PURE__ */ jsxs4(Flex2, { align: "center", justify: "space-between", children: [
|
|
737
|
+
/* @__PURE__ */ jsx4(Text3, { size: 1, weight: "semibold", muted: true, children: "AI keyword suggestions" }),
|
|
738
|
+
/* @__PURE__ */ jsxs4(Flex2, { gap: 2, children: [
|
|
739
|
+
pendingSuggestions.length > 1 && /* @__PURE__ */ jsx4(
|
|
740
|
+
Button2,
|
|
741
|
+
{
|
|
742
|
+
mode: "ghost",
|
|
743
|
+
tone: "primary",
|
|
744
|
+
paddingX: 2,
|
|
745
|
+
paddingY: 2,
|
|
746
|
+
fontSize: 1,
|
|
747
|
+
text: "Add all",
|
|
748
|
+
onClick: handleAddAll
|
|
749
|
+
}
|
|
750
|
+
),
|
|
751
|
+
/* @__PURE__ */ jsx4(
|
|
752
|
+
Button2,
|
|
753
|
+
{
|
|
754
|
+
mode: "ghost",
|
|
755
|
+
tone: "default",
|
|
756
|
+
paddingX: 2,
|
|
757
|
+
paddingY: 2,
|
|
758
|
+
fontSize: 1,
|
|
759
|
+
text: "Dismiss",
|
|
760
|
+
onClick: handleDismiss
|
|
761
|
+
}
|
|
762
|
+
)
|
|
763
|
+
] })
|
|
764
|
+
] }),
|
|
765
|
+
pendingSuggestions.length > 0 && /* @__PURE__ */ jsxs4(Box, { children: [
|
|
766
|
+
/* @__PURE__ */ jsx4(Text3, { size: 0, muted: true, style: { marginBottom: 8 }, children: "Click to add:" }),
|
|
767
|
+
/* @__PURE__ */ jsx4(Flex2, { gap: 2, wrap: "wrap", style: { marginTop: 6 }, children: pendingSuggestions.map((kw) => /* @__PURE__ */ jsxs4(
|
|
768
|
+
"button",
|
|
769
|
+
{
|
|
770
|
+
type: "button",
|
|
771
|
+
onClick: () => handleAdd(kw),
|
|
772
|
+
style: {
|
|
773
|
+
padding: "4px 10px",
|
|
774
|
+
borderRadius: 20,
|
|
775
|
+
border: "1px solid var(--card-border-color)",
|
|
776
|
+
background: "transparent",
|
|
777
|
+
color: "var(--card-fg-color)",
|
|
778
|
+
cursor: "pointer",
|
|
779
|
+
fontSize: 12,
|
|
780
|
+
lineHeight: 1.4,
|
|
781
|
+
transition: "background 0.15s"
|
|
782
|
+
},
|
|
783
|
+
onMouseEnter: (e) => {
|
|
784
|
+
;
|
|
785
|
+
e.currentTarget.style.background = "var(--card-border-color)";
|
|
786
|
+
},
|
|
787
|
+
onMouseLeave: (e) => {
|
|
788
|
+
;
|
|
789
|
+
e.currentTarget.style.background = "transparent";
|
|
790
|
+
},
|
|
791
|
+
children: [
|
|
792
|
+
"+ ",
|
|
793
|
+
kw
|
|
794
|
+
]
|
|
795
|
+
},
|
|
796
|
+
kw
|
|
797
|
+
)) })
|
|
798
|
+
] }),
|
|
799
|
+
alreadyAdded.length > 0 && /* @__PURE__ */ jsxs4(Box, { children: [
|
|
800
|
+
/* @__PURE__ */ jsx4(Text3, { size: 0, muted: true, style: { marginBottom: 6 }, children: "Already added:" }),
|
|
801
|
+
/* @__PURE__ */ jsx4(Flex2, { gap: 2, wrap: "wrap", style: { marginTop: 4 }, children: alreadyAdded.map((kw) => /* @__PURE__ */ jsxs4(Badge, { tone: "positive", radius: 5, fontSize: 0, padding: 2, children: [
|
|
802
|
+
"\u2713 ",
|
|
803
|
+
kw
|
|
804
|
+
] }, kw)) })
|
|
805
|
+
] }),
|
|
806
|
+
pendingSuggestions.length === 0 && /* @__PURE__ */ jsx4(Text3, { size: 1, muted: true, children: "All suggested keywords added." })
|
|
807
|
+
] }) });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/components/meta/KeywordsInput.tsx
|
|
811
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
812
|
+
var KeywordsInput = (props) => {
|
|
813
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
814
|
+
const { options } = schemaType;
|
|
815
|
+
const existing = value != null ? value : [];
|
|
816
|
+
const handleAdd = (kw) => {
|
|
817
|
+
if (existing.includes(kw)) return;
|
|
818
|
+
onChange(PatchEvent2.from(set2([...existing, kw])));
|
|
819
|
+
};
|
|
820
|
+
const handleAddAll = (keywords) => {
|
|
821
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...keywords])];
|
|
822
|
+
onChange(PatchEvent2.from(set2(merged)));
|
|
823
|
+
};
|
|
824
|
+
return /* @__PURE__ */ jsxs5(Stack3, { space: 3, children: [
|
|
825
|
+
renderDefault(props),
|
|
826
|
+
/* @__PURE__ */ jsx5(
|
|
827
|
+
KeywordSuggestions,
|
|
828
|
+
{
|
|
829
|
+
ai: options == null ? void 0 : options.ai,
|
|
830
|
+
seoFieldPath: String(path[0]),
|
|
831
|
+
existing,
|
|
832
|
+
onAdd: handleAdd,
|
|
833
|
+
onAddAll: handleAddAll
|
|
834
|
+
}
|
|
835
|
+
)
|
|
836
|
+
] });
|
|
837
|
+
};
|
|
838
|
+
var KeywordsInput_default = KeywordsInput;
|
|
839
|
+
|
|
840
|
+
// src/components/meta/MetaDescription.tsx
|
|
841
|
+
import { Stack as Stack4, Text as Text4 } from "@sanity/ui";
|
|
842
|
+
import { useMemo as useMemo3 } from "react";
|
|
843
|
+
import { PatchEvent as PatchEvent3, set as set3, useFormValue as useFormValue5 } from "sanity";
|
|
844
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8
845
|
var MetaDescription = (props) => {
|
|
9
|
-
const { value, renderDefault, path } = props;
|
|
10
|
-
const
|
|
846
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
847
|
+
const { options } = schemaType;
|
|
848
|
+
const parent = useFormValue5([path[0]]);
|
|
11
849
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
12
|
-
const keywords =
|
|
13
|
-
const feedbackItems =
|
|
850
|
+
const keywords = useMemo3(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
851
|
+
const feedbackItems = useMemo3(
|
|
14
852
|
() => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
|
|
15
853
|
[value, keywords, isParentseoField]
|
|
16
854
|
);
|
|
17
|
-
|
|
855
|
+
const readability = useMemo3(() => analyzeReadability(value || ""), [value]);
|
|
856
|
+
return /* @__PURE__ */ jsxs6(Stack4, { space: 3, children: [
|
|
18
857
|
renderDefault(props),
|
|
19
|
-
/* @__PURE__ */
|
|
20
|
-
|
|
858
|
+
/* @__PURE__ */ jsx6(
|
|
859
|
+
SeoGenButton,
|
|
860
|
+
{
|
|
861
|
+
field: "description",
|
|
862
|
+
ai: options == null ? void 0 : options.ai,
|
|
863
|
+
seoFieldPath: String(path[0]),
|
|
864
|
+
onGenerate: (v) => onChange(PatchEvent3.from(set3(v)))
|
|
865
|
+
}
|
|
866
|
+
),
|
|
867
|
+
/* @__PURE__ */ jsx6(Stack4, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
868
|
+
/* @__PURE__ */ jsx6(
|
|
21
869
|
"div",
|
|
22
870
|
{
|
|
23
871
|
style: { width: 10, height: 10, borderRadius: "50%", backgroundColor: item.color }
|
|
24
872
|
}
|
|
25
873
|
),
|
|
26
|
-
/* @__PURE__ */
|
|
27
|
-
] }, item.text)) })
|
|
874
|
+
/* @__PURE__ */ jsx6(Text4, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
875
|
+
] }, item.text)) }),
|
|
876
|
+
readability && /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
877
|
+
/* @__PURE__ */ jsx6(
|
|
878
|
+
"div",
|
|
879
|
+
{
|
|
880
|
+
style: {
|
|
881
|
+
minWidth: 10,
|
|
882
|
+
height: 10,
|
|
883
|
+
borderRadius: "50%",
|
|
884
|
+
backgroundColor: readability.color
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
),
|
|
888
|
+
/* @__PURE__ */ jsxs6(Text4, { weight: "bold", muted: true, size: 14, children: [
|
|
889
|
+
"Readability: ",
|
|
890
|
+
readability.label,
|
|
891
|
+
" (Flesch ",
|
|
892
|
+
readability.score,
|
|
893
|
+
" \xB7 Grade",
|
|
894
|
+
" ",
|
|
895
|
+
readability.gradeLevel,
|
|
896
|
+
")"
|
|
897
|
+
] })
|
|
898
|
+
] })
|
|
28
899
|
] });
|
|
29
900
|
};
|
|
30
901
|
var MetaDescription_default = MetaDescription;
|
|
902
|
+
|
|
903
|
+
// src/components/meta/MetaImage.tsx
|
|
904
|
+
import { Stack as Stack5, Text as Text5 } from "@sanity/ui";
|
|
905
|
+
import { useMemo as useMemo4 } from "react";
|
|
906
|
+
import { useFormValue as useFormValue6 } from "sanity";
|
|
907
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
31
908
|
var MetaImage = (props) => {
|
|
32
909
|
const { value, renderDefault, path } = props;
|
|
33
|
-
const seoParent =
|
|
910
|
+
const seoParent = useFormValue6([path[0]]);
|
|
34
911
|
const hasImage = !!(value == null ? void 0 : value.asset);
|
|
35
|
-
const feedbackItems =
|
|
912
|
+
const feedbackItems = useMemo4(
|
|
36
913
|
() => getMetaImageValidation(hasImage, seoParent),
|
|
37
914
|
[hasImage, seoParent]
|
|
38
915
|
);
|
|
39
|
-
return /* @__PURE__ */
|
|
916
|
+
return /* @__PURE__ */ jsxs7(Stack5, { space: 3, children: [
|
|
40
917
|
renderDefault(props),
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
/* @__PURE__ */
|
|
918
|
+
/* @__PURE__ */ jsx7(Stack5, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
919
|
+
/* @__PURE__ */ jsx7(
|
|
43
920
|
"div",
|
|
44
921
|
{
|
|
45
922
|
style: {
|
|
@@ -50,24 +927,233 @@ var MetaImage = (props) => {
|
|
|
50
927
|
}
|
|
51
928
|
}
|
|
52
929
|
),
|
|
53
|
-
/* @__PURE__ */
|
|
930
|
+
/* @__PURE__ */ jsx7(Text5, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
54
931
|
] }, item.text)) })
|
|
55
932
|
] });
|
|
56
933
|
};
|
|
57
934
|
var MetaImage_default = MetaImage;
|
|
935
|
+
|
|
936
|
+
// src/components/meta/MetaTagsPreview.tsx
|
|
937
|
+
import { Box as Box2, Button as Button3, Card as Card3, Flex as Flex3, Stack as Stack6, Text as Text6 } from "@sanity/ui";
|
|
938
|
+
import { useMemo as useMemo5, useState as useState4 } from "react";
|
|
939
|
+
import { useClient, useColorScheme, useFormValue as useFormValue7 } from "sanity";
|
|
940
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
941
|
+
function resolveImageUrl(asset, projectId, dataset) {
|
|
942
|
+
const ref = asset == null ? void 0 : asset._ref;
|
|
943
|
+
if (!ref) return null;
|
|
944
|
+
const parts = ref.replace("image-", "").split("-");
|
|
945
|
+
const ext = parts[parts.length - 1];
|
|
946
|
+
const dims = parts[parts.length - 2];
|
|
947
|
+
const id = parts.slice(0, parts.length - 2).join("-");
|
|
948
|
+
return `https://cdn.sanity.io/images/${projectId}/${dataset}/${id}-${dims}.${ext}`;
|
|
949
|
+
}
|
|
950
|
+
var MetaTagsPreview = (props) => {
|
|
951
|
+
const { path } = props;
|
|
952
|
+
const [expanded, setExpanded] = useState4(false);
|
|
953
|
+
const [copyLabel, setCopyLabel] = useState4("Copy");
|
|
954
|
+
const { scheme } = useColorScheme();
|
|
955
|
+
const seoFields = useFormValue7([path[0]]);
|
|
956
|
+
const client = useClient({ apiVersion: "2024-01-01" });
|
|
957
|
+
const { projectId = "", dataset = "" } = client.config();
|
|
958
|
+
const rawHtml = useMemo5(() => {
|
|
959
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
960
|
+
const lines = [];
|
|
961
|
+
lines.push('<meta charset="UTF-8" />');
|
|
962
|
+
lines.push('<meta name="viewport" content="width=device-width, initial-scale=1" />');
|
|
963
|
+
if (seoFields == null ? void 0 : seoFields.title) lines.push(`<title>${seoFields.title}</title>`);
|
|
964
|
+
if (seoFields == null ? void 0 : seoFields.description)
|
|
965
|
+
lines.push(`<meta name="description" content="${seoFields.description}" />`);
|
|
966
|
+
if (seoFields == null ? void 0 : seoFields.robots) {
|
|
967
|
+
const parts = [];
|
|
968
|
+
if (seoFields.robots.noIndex) parts.push("noindex");
|
|
969
|
+
if (seoFields.robots.noFollow) parts.push("nofollow");
|
|
970
|
+
if (seoFields.robots.noTranslate) parts.push("notranslate");
|
|
971
|
+
if (seoFields.robots.noImageIndex) parts.push("noimageindex");
|
|
972
|
+
if (parts.length) lines.push(`<meta name="robots" content="${parts.join(", ")}" />`);
|
|
973
|
+
}
|
|
974
|
+
if ((_a = seoFields == null ? void 0 : seoFields.keywords) == null ? void 0 : _a.length)
|
|
975
|
+
lines.push(`<meta name="keywords" content="${seoFields.keywords.join(", ")}" />`);
|
|
976
|
+
if (seoFields == null ? void 0 : seoFields.canonicalUrl)
|
|
977
|
+
lines.push(`<link rel="canonical" href="${seoFields.canonicalUrl}" />`);
|
|
978
|
+
const og = seoFields == null ? void 0 : seoFields.openGraph;
|
|
979
|
+
if (og == null ? void 0 : og.type) lines.push(`<meta property="og:type" content="${og.type}" />`);
|
|
980
|
+
if (og == null ? void 0 : og.title) lines.push(`<meta property="og:title" content="${og.title}" />`);
|
|
981
|
+
if (og == null ? void 0 : og.description)
|
|
982
|
+
lines.push(`<meta property="og:description" content="${og.description}" />`);
|
|
983
|
+
if (og == null ? void 0 : og.siteName) lines.push(`<meta property="og:site_name" content="${og.siteName}" />`);
|
|
984
|
+
if (og == null ? void 0 : og.url) lines.push(`<meta property="og:url" content="${og.url}" />`);
|
|
985
|
+
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);
|
|
986
|
+
if (ogImg) lines.push(`<meta property="og:image" content="${ogImg}" />`);
|
|
987
|
+
const tw = seoFields == null ? void 0 : seoFields.twitter;
|
|
988
|
+
if (tw == null ? void 0 : tw.card) lines.push(`<meta name="twitter:card" content="${tw.card}" />`);
|
|
989
|
+
if (tw == null ? void 0 : tw.site) lines.push(`<meta name="twitter:site" content="${tw.site}" />`);
|
|
990
|
+
if (tw == null ? void 0 : tw.creator) lines.push(`<meta name="twitter:creator" content="${tw.creator}" />`);
|
|
991
|
+
if (tw == null ? void 0 : tw.title) lines.push(`<meta name="twitter:title" content="${tw.title}" />`);
|
|
992
|
+
if (tw == null ? void 0 : tw.description)
|
|
993
|
+
lines.push(`<meta name="twitter:description" content="${tw.description}" />`);
|
|
994
|
+
const twImg = (tw == null ? void 0 : tw.imageUrl) || resolveImageUrl((_d = tw == null ? void 0 : tw.image) == null ? void 0 : _d.asset, projectId, dataset);
|
|
995
|
+
if (twImg) lines.push(`<meta name="twitter:image" content="${twImg}" />`);
|
|
996
|
+
if ((_e = seoFields == null ? void 0 : seoFields.hreflangs) == null ? void 0 : _e.length) {
|
|
997
|
+
for (const e of seoFields.hreflangs) {
|
|
998
|
+
if (e.locale && e.url)
|
|
999
|
+
lines.push(`<link rel="alternate" hreflang="${e.locale}" href="${e.url}" />`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if ((_f = seoFields == null ? void 0 : seoFields.metaAttributes) == null ? void 0 : _f.length) {
|
|
1003
|
+
for (const attr of seoFields.metaAttributes) {
|
|
1004
|
+
if (attr.key && attr.type === "string" && attr.value) {
|
|
1005
|
+
lines.push(`<meta name="${attr.key}" content="${attr.value}" />`);
|
|
1006
|
+
} else if (attr.key && attr.type === "image" && ((_g = attr.image) == null ? void 0 : _g.asset)) {
|
|
1007
|
+
const imgUrl = resolveImageUrl(attr.image.asset, projectId, dataset);
|
|
1008
|
+
if (imgUrl) lines.push(`<meta name="${attr.key}" content="${imgUrl}" />`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return lines.join("\n");
|
|
1013
|
+
}, [seoFields, projectId, dataset]);
|
|
1014
|
+
const tagCount = useMemo5(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
|
|
1015
|
+
const tokens = useMemo5(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
|
|
1016
|
+
const handleCopy = () => {
|
|
1017
|
+
navigator.clipboard.writeText(rawHtml).then(() => {
|
|
1018
|
+
setCopyLabel("Copied!");
|
|
1019
|
+
setTimeout(() => setCopyLabel("Copy"), 2e3);
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
const isDark = scheme === "dark";
|
|
1023
|
+
const codeBg = isDark ? "#161b22" : "#f6f8fa";
|
|
1024
|
+
const codeBorder = isDark ? "#30363d" : "#d0d7de";
|
|
1025
|
+
const headerBg = isDark ? "#21262d" : "#f0f2f4";
|
|
1026
|
+
const headerBorder = isDark ? "#30363d" : "#d0d7de";
|
|
1027
|
+
const headerText = isDark ? "#e6edf3" : "#24292f";
|
|
1028
|
+
return /* @__PURE__ */ jsx8(Card3, { padding: 3, radius: 2, tone: "default", border: true, children: /* @__PURE__ */ jsxs8(Stack6, { space: 3, children: [
|
|
1029
|
+
/* @__PURE__ */ jsxs8(Flex3, { align: "center", justify: "space-between", gap: 3, children: [
|
|
1030
|
+
/* @__PURE__ */ jsx8(Text6, { weight: "semibold", size: 1, children: "Meta Tags Preview" }),
|
|
1031
|
+
/* @__PURE__ */ jsxs8(Flex3, { gap: 2, align: "center", children: [
|
|
1032
|
+
/* @__PURE__ */ jsxs8(Text6, { size: 0, muted: true, children: [
|
|
1033
|
+
tagCount,
|
|
1034
|
+
" tags generated"
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsx8(
|
|
1037
|
+
Button3,
|
|
1038
|
+
{
|
|
1039
|
+
text: copyLabel,
|
|
1040
|
+
tone: "default",
|
|
1041
|
+
mode: "ghost",
|
|
1042
|
+
fontSize: 1,
|
|
1043
|
+
padding: 2,
|
|
1044
|
+
onClick: handleCopy
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
] })
|
|
1048
|
+
] }),
|
|
1049
|
+
/* @__PURE__ */ jsxs8(
|
|
1050
|
+
"button",
|
|
1051
|
+
{
|
|
1052
|
+
type: "button",
|
|
1053
|
+
onClick: () => setExpanded((p) => !p),
|
|
1054
|
+
style: {
|
|
1055
|
+
display: "flex",
|
|
1056
|
+
alignItems: "center",
|
|
1057
|
+
justifyContent: "center",
|
|
1058
|
+
gap: 6,
|
|
1059
|
+
width: "100%",
|
|
1060
|
+
background: headerBg,
|
|
1061
|
+
border: `1px solid ${headerBorder}`,
|
|
1062
|
+
borderRadius: 6,
|
|
1063
|
+
padding: "6px 12px",
|
|
1064
|
+
cursor: "pointer",
|
|
1065
|
+
fontSize: 12,
|
|
1066
|
+
fontWeight: 500,
|
|
1067
|
+
color: headerText
|
|
1068
|
+
},
|
|
1069
|
+
children: [
|
|
1070
|
+
/* @__PURE__ */ jsx8("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
|
|
1071
|
+
expanded ? "Hide tags" : "Show tags"
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
expanded && /* @__PURE__ */ jsxs8(
|
|
1076
|
+
"div",
|
|
1077
|
+
{
|
|
1078
|
+
style: {
|
|
1079
|
+
borderRadius: 6,
|
|
1080
|
+
border: `1px solid ${codeBorder}`,
|
|
1081
|
+
overflow: "hidden"
|
|
1082
|
+
},
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ jsxs8(
|
|
1085
|
+
"div",
|
|
1086
|
+
{
|
|
1087
|
+
style: {
|
|
1088
|
+
background: headerBg,
|
|
1089
|
+
borderBottom: `1px solid ${headerBorder}`,
|
|
1090
|
+
padding: "6px 14px",
|
|
1091
|
+
display: "flex",
|
|
1092
|
+
alignItems: "center",
|
|
1093
|
+
gap: 6
|
|
1094
|
+
},
|
|
1095
|
+
children: [
|
|
1096
|
+
/* @__PURE__ */ jsx8(
|
|
1097
|
+
"span",
|
|
1098
|
+
{
|
|
1099
|
+
style: {
|
|
1100
|
+
width: 8,
|
|
1101
|
+
height: 8,
|
|
1102
|
+
borderRadius: "50%",
|
|
1103
|
+
background: isDark ? "#388bfd" : "#0550ae",
|
|
1104
|
+
display: "inline-block"
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
),
|
|
1108
|
+
/* @__PURE__ */ jsx8("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
|
|
1109
|
+
]
|
|
1110
|
+
}
|
|
1111
|
+
),
|
|
1112
|
+
/* @__PURE__ */ jsx8(
|
|
1113
|
+
Box2,
|
|
1114
|
+
{
|
|
1115
|
+
as: "pre",
|
|
1116
|
+
padding: 3,
|
|
1117
|
+
style: {
|
|
1118
|
+
margin: 0,
|
|
1119
|
+
maxHeight: 420,
|
|
1120
|
+
overflow: "auto",
|
|
1121
|
+
fontSize: 12,
|
|
1122
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
1123
|
+
lineHeight: 1.7,
|
|
1124
|
+
whiteSpace: "pre-wrap",
|
|
1125
|
+
wordBreak: "break-word",
|
|
1126
|
+
backgroundColor: codeBg
|
|
1127
|
+
},
|
|
1128
|
+
children: tokens.map((token, idx) => /* @__PURE__ */ jsx8("span", { style: { color: token.color }, children: token.text }, idx))
|
|
1129
|
+
}
|
|
1130
|
+
)
|
|
1131
|
+
]
|
|
1132
|
+
}
|
|
1133
|
+
)
|
|
1134
|
+
] }) });
|
|
1135
|
+
};
|
|
1136
|
+
var MetaTagsPreview_default = MetaTagsPreview;
|
|
1137
|
+
|
|
1138
|
+
// src/components/meta/MetaTitle.tsx
|
|
1139
|
+
import { Stack as Stack7, Text as Text7 } from "@sanity/ui";
|
|
1140
|
+
import { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
|
|
1141
|
+
import { PatchEvent as PatchEvent4, set as set4, useClient as useClient2, useFormValue as useFormValue8 } from "sanity";
|
|
1142
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
58
1143
|
var MetaTitle = (props) => {
|
|
59
|
-
var _a, _b;
|
|
60
|
-
const { value, renderDefault, path, schemaType } = props;
|
|
1144
|
+
var _a, _b, _c;
|
|
1145
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
61
1146
|
const { options } = schemaType;
|
|
62
|
-
const parent =
|
|
1147
|
+
const parent = useFormValue8([path[0]]);
|
|
63
1148
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
64
|
-
const keywords =
|
|
65
|
-
const rootDoc = (_a =
|
|
66
|
-
const client =
|
|
67
|
-
const [groqTitleSuffix, setGroqTitleSuffix] =
|
|
1149
|
+
const keywords = useMemo6(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1150
|
+
const rootDoc = (_a = useFormValue8([])) != null ? _a : {};
|
|
1151
|
+
const client = useClient2({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
|
|
1152
|
+
const [groqTitleSuffix, setGroqTitleSuffix] = useState5("");
|
|
1153
|
+
const [isDuplicateTitle, setIsDuplicateTitle] = useState5(false);
|
|
68
1154
|
const titleSuffixQuery = options == null ? void 0 : options.titleSuffixQuery;
|
|
69
1155
|
const titleSuffixOption = options == null ? void 0 : options.titleSuffix;
|
|
70
|
-
|
|
1156
|
+
useEffect3(() => {
|
|
71
1157
|
if (!titleSuffixQuery) return;
|
|
72
1158
|
client.fetch(titleSuffixQuery).then((result) => {
|
|
73
1159
|
setGroqTitleSuffix(result === null || result === void 0 ? "" : String(result));
|
|
@@ -75,7 +1161,22 @@ var MetaTitle = (props) => {
|
|
|
75
1161
|
setGroqTitleSuffix("");
|
|
76
1162
|
});
|
|
77
1163
|
}, [titleSuffixQuery, client]);
|
|
78
|
-
const
|
|
1164
|
+
const currentDocId = (_c = rootDoc._id) != null ? _c : "";
|
|
1165
|
+
useEffect3(() => {
|
|
1166
|
+
if (!(value == null ? void 0 : value.trim()) || !currentDocId) {
|
|
1167
|
+
setIsDuplicateTitle(false);
|
|
1168
|
+
return () => {
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
const timer = setTimeout(() => {
|
|
1172
|
+
client.fetch(
|
|
1173
|
+
`count(*[seo.title == $title && _id != $id && !(_id in path("drafts.**"))])`,
|
|
1174
|
+
{ title: value, id: currentDocId }
|
|
1175
|
+
).then((count) => setIsDuplicateTitle(count > 0)).catch(() => setIsDuplicateTitle(false));
|
|
1176
|
+
}, 600);
|
|
1177
|
+
return () => clearTimeout(timer);
|
|
1178
|
+
}, [value, client, currentDocId]);
|
|
1179
|
+
const resolvedSuffix = useMemo6(() => {
|
|
79
1180
|
if (titleSuffixQuery) return groqTitleSuffix;
|
|
80
1181
|
if (!titleSuffixOption) return "";
|
|
81
1182
|
if (typeof titleSuffixOption === "function") {
|
|
@@ -84,31 +1185,60 @@ var MetaTitle = (props) => {
|
|
|
84
1185
|
return titleSuffixOption;
|
|
85
1186
|
}, [titleSuffixQuery, groqTitleSuffix, titleSuffixOption, rootDoc]);
|
|
86
1187
|
const suffixLength = resolvedSuffix ? resolvedSuffix.length + 3 : 0;
|
|
87
|
-
const feedbackItems =
|
|
1188
|
+
const feedbackItems = useMemo6(
|
|
88
1189
|
() => getMetaTitleValidationMessages(value || "", keywords, isParentseoField, suffixLength),
|
|
89
1190
|
[value, keywords, isParentseoField, suffixLength]
|
|
90
1191
|
);
|
|
91
|
-
return /* @__PURE__ */
|
|
1192
|
+
return /* @__PURE__ */ jsxs9(Stack7, { space: 3, children: [
|
|
92
1193
|
renderDefault(props),
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1194
|
+
/* @__PURE__ */ jsx9(
|
|
1195
|
+
SeoGenButton,
|
|
1196
|
+
{
|
|
1197
|
+
field: "title",
|
|
1198
|
+
ai: options == null ? void 0 : options.ai,
|
|
1199
|
+
seoFieldPath: String(path[0]),
|
|
1200
|
+
onGenerate: (v) => onChange(PatchEvent4.from(set4(v)))
|
|
1201
|
+
}
|
|
1202
|
+
),
|
|
1203
|
+
/* @__PURE__ */ jsxs9(Stack7, { space: 2, children: [
|
|
1204
|
+
feedbackItems.map((item) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1205
|
+
/* @__PURE__ */ jsx9(
|
|
1206
|
+
"div",
|
|
1207
|
+
{
|
|
1208
|
+
style: {
|
|
1209
|
+
minWidth: 10,
|
|
1210
|
+
height: 10,
|
|
1211
|
+
borderRadius: "50%",
|
|
1212
|
+
backgroundColor: item.color
|
|
1213
|
+
}
|
|
102
1214
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
1215
|
+
),
|
|
1216
|
+
/* @__PURE__ */ jsx9(Text7, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
1217
|
+
] }, item.text)),
|
|
1218
|
+
isDuplicateTitle && /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1219
|
+
/* @__PURE__ */ jsx9(
|
|
1220
|
+
"div",
|
|
1221
|
+
{
|
|
1222
|
+
style: {
|
|
1223
|
+
minWidth: 10,
|
|
1224
|
+
height: 10,
|
|
1225
|
+
borderRadius: "50%",
|
|
1226
|
+
backgroundColor: "orange"
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
/* @__PURE__ */ jsx9(Text7, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
|
|
1231
|
+
] })
|
|
1232
|
+
] })
|
|
107
1233
|
] });
|
|
108
1234
|
};
|
|
109
1235
|
var MetaTitle_default = MetaTitle;
|
|
110
1236
|
|
|
111
1237
|
// src/utils/fieldsUtils.ts
|
|
1238
|
+
function withLicense(ai, licenseKey) {
|
|
1239
|
+
if (!ai) return void 0;
|
|
1240
|
+
return licenseKey ? __spreadProps(__spreadValues({}, ai), { _licenseKey: licenseKey }) : ai;
|
|
1241
|
+
}
|
|
112
1242
|
var DEFAULT_FIELD_INFO = {
|
|
113
1243
|
title: {
|
|
114
1244
|
title: "Meta Title",
|
|
@@ -132,7 +1262,7 @@ var DEFAULT_FIELD_INFO = {
|
|
|
132
1262
|
},
|
|
133
1263
|
robots: {
|
|
134
1264
|
title: "Robots Settings",
|
|
135
|
-
description: "Configure how search engine crawlers should index
|
|
1265
|
+
description: "Configure how search engine crawlers should index, follow links, translate content, and index images on this page."
|
|
136
1266
|
},
|
|
137
1267
|
metaAttributes: {
|
|
138
1268
|
title: "Additional Meta Attributes",
|
|
@@ -201,6 +1331,22 @@ var DEFAULT_FIELD_INFO = {
|
|
|
201
1331
|
twitterImageUrl: {
|
|
202
1332
|
title: "X Image URL",
|
|
203
1333
|
description: "Enter the full URL of the image for X (formerly Twitter). Ensure the image is accessible and meets the recommended size."
|
|
1334
|
+
},
|
|
1335
|
+
focusKeyword: {
|
|
1336
|
+
title: "Focus Keyword",
|
|
1337
|
+
description: "The primary keyword you want this page to rank for. Used to check keyword placement in title, description, and URL."
|
|
1338
|
+
},
|
|
1339
|
+
hreflangs: {
|
|
1340
|
+
title: "Hreflang Entries",
|
|
1341
|
+
description: "Add alternate language/region versions of this page. Include x-default for the fallback URL."
|
|
1342
|
+
},
|
|
1343
|
+
geoChecklist: {
|
|
1344
|
+
title: "AI Overview Readiness",
|
|
1345
|
+
description: "Checklist of signals that improve visibility in AI-generated overviews."
|
|
1346
|
+
},
|
|
1347
|
+
metaTagsPreview: {
|
|
1348
|
+
title: "Meta Tags Preview",
|
|
1349
|
+
description: "Preview the full HTML <head> meta tags generated for this page."
|
|
204
1350
|
}
|
|
205
1351
|
};
|
|
206
1352
|
var getFieldInfo = (fieldName, fieldOverrides) => {
|
|
@@ -230,19 +1376,38 @@ var getFieldHiddenFunction = (fieldName, config) => {
|
|
|
230
1376
|
var isEmpty = (value) => {
|
|
231
1377
|
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;
|
|
232
1378
|
};
|
|
1379
|
+
|
|
1380
|
+
// src/schemas/types/openGraph/index.ts
|
|
1381
|
+
import { defineField, defineType } from "sanity";
|
|
1382
|
+
|
|
1383
|
+
// src/components/openGraph/OgDescription.tsx
|
|
1384
|
+
import { Stack as Stack8, Text as Text8 } from "@sanity/ui";
|
|
1385
|
+
import { useMemo as useMemo7 } from "react";
|
|
1386
|
+
import { PatchEvent as PatchEvent5, set as set5, useFormValue as useFormValue9 } from "sanity";
|
|
1387
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
233
1388
|
var OgDescription = (props) => {
|
|
234
|
-
const { value, renderDefault, path } = props;
|
|
235
|
-
const
|
|
1389
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
1390
|
+
const { options } = schemaType;
|
|
1391
|
+
const parent = useFormValue9([path[0]]);
|
|
236
1392
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
237
|
-
const keywords =
|
|
238
|
-
const feedbackItems =
|
|
1393
|
+
const keywords = useMemo7(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1394
|
+
const feedbackItems = useMemo7(
|
|
239
1395
|
() => getOgDescriptionValidation(value || "", keywords, isParentseoField),
|
|
240
1396
|
[value, keywords, isParentseoField]
|
|
241
1397
|
);
|
|
242
|
-
return /* @__PURE__ */
|
|
1398
|
+
return /* @__PURE__ */ jsxs10(Stack8, { space: 3, children: [
|
|
243
1399
|
renderDefault(props),
|
|
244
|
-
/* @__PURE__ */
|
|
245
|
-
|
|
1400
|
+
/* @__PURE__ */ jsx10(
|
|
1401
|
+
SeoGenButton,
|
|
1402
|
+
{
|
|
1403
|
+
field: "ogDescription",
|
|
1404
|
+
ai: options == null ? void 0 : options.ai,
|
|
1405
|
+
seoFieldPath: String(path[0]),
|
|
1406
|
+
onGenerate: (v) => onChange(PatchEvent5.from(set5(v)))
|
|
1407
|
+
}
|
|
1408
|
+
),
|
|
1409
|
+
/* @__PURE__ */ jsx10(Stack8, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1410
|
+
/* @__PURE__ */ jsx10(
|
|
246
1411
|
"div",
|
|
247
1412
|
{
|
|
248
1413
|
style: {
|
|
@@ -253,25 +1418,31 @@ var OgDescription = (props) => {
|
|
|
253
1418
|
}
|
|
254
1419
|
}
|
|
255
1420
|
),
|
|
256
|
-
/* @__PURE__ */
|
|
1421
|
+
/* @__PURE__ */ jsx10(Text8, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
257
1422
|
] }, item.text)) })
|
|
258
1423
|
] });
|
|
259
1424
|
};
|
|
260
1425
|
var OgDescription_default = OgDescription;
|
|
1426
|
+
|
|
1427
|
+
// src/components/openGraph/OgImage.tsx
|
|
1428
|
+
import { Stack as Stack9, Text as Text9 } from "@sanity/ui";
|
|
1429
|
+
import { useMemo as useMemo8 } from "react";
|
|
1430
|
+
import { useFormValue as useFormValue10 } from "sanity";
|
|
1431
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
261
1432
|
var OgImage = (props) => {
|
|
262
1433
|
const { value, renderDefault, path } = props;
|
|
263
|
-
const seoParent =
|
|
1434
|
+
const seoParent = useFormValue10([path[0]]);
|
|
264
1435
|
const imgValue = value;
|
|
265
1436
|
const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
|
|
266
1437
|
const altText = imgValue == null ? void 0 : imgValue.alt;
|
|
267
|
-
const feedbackItems =
|
|
1438
|
+
const feedbackItems = useMemo8(
|
|
268
1439
|
() => getOgImageValidation(hasImage, altText, seoParent),
|
|
269
1440
|
[hasImage, altText, seoParent]
|
|
270
1441
|
);
|
|
271
|
-
return /* @__PURE__ */
|
|
1442
|
+
return /* @__PURE__ */ jsxs11(Stack9, { space: 3, children: [
|
|
272
1443
|
renderDefault(props),
|
|
273
|
-
/* @__PURE__ */
|
|
274
|
-
/* @__PURE__ */
|
|
1444
|
+
/* @__PURE__ */ jsx11(Stack9, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1445
|
+
/* @__PURE__ */ jsx11(
|
|
275
1446
|
"div",
|
|
276
1447
|
{
|
|
277
1448
|
style: {
|
|
@@ -282,19 +1453,25 @@ var OgImage = (props) => {
|
|
|
282
1453
|
}
|
|
283
1454
|
}
|
|
284
1455
|
),
|
|
285
|
-
/* @__PURE__ */
|
|
1456
|
+
/* @__PURE__ */ jsx11(Text9, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
286
1457
|
] }, item.text)) })
|
|
287
1458
|
] });
|
|
288
1459
|
};
|
|
289
1460
|
var OgImage_default = OgImage;
|
|
1461
|
+
|
|
1462
|
+
// src/components/openGraph/OgImageUrl.tsx
|
|
1463
|
+
import { Stack as Stack10, Text as Text10 } from "@sanity/ui";
|
|
1464
|
+
import { useMemo as useMemo9 } from "react";
|
|
1465
|
+
import { useFormValue as useFormValue11 } from "sanity";
|
|
1466
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
290
1467
|
var OgImageUrl = (props) => {
|
|
291
1468
|
const { value, renderDefault, path } = props;
|
|
292
|
-
const seoParent =
|
|
293
|
-
const feedbackItems =
|
|
294
|
-
return /* @__PURE__ */
|
|
1469
|
+
const seoParent = useFormValue11([path[0]]);
|
|
1470
|
+
const feedbackItems = useMemo9(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
|
|
1471
|
+
return /* @__PURE__ */ jsxs12(Stack10, { space: 3, children: [
|
|
295
1472
|
renderDefault(props),
|
|
296
|
-
/* @__PURE__ */
|
|
297
|
-
/* @__PURE__ */
|
|
1473
|
+
/* @__PURE__ */ jsx12(Stack10, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1474
|
+
/* @__PURE__ */ jsx12(
|
|
298
1475
|
"div",
|
|
299
1476
|
{
|
|
300
1477
|
style: {
|
|
@@ -305,24 +1482,40 @@ var OgImageUrl = (props) => {
|
|
|
305
1482
|
}
|
|
306
1483
|
}
|
|
307
1484
|
),
|
|
308
|
-
/* @__PURE__ */
|
|
1485
|
+
/* @__PURE__ */ jsx12(Text10, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
309
1486
|
] }, item.text)) })
|
|
310
1487
|
] });
|
|
311
1488
|
};
|
|
312
1489
|
var OgImageUrl_default = OgImageUrl;
|
|
1490
|
+
|
|
1491
|
+
// src/components/openGraph/OgTitle.tsx
|
|
1492
|
+
import { Stack as Stack11, Text as Text11 } from "@sanity/ui";
|
|
1493
|
+
import { useMemo as useMemo10 } from "react";
|
|
1494
|
+
import { PatchEvent as PatchEvent6, set as set6, useFormValue as useFormValue12 } from "sanity";
|
|
1495
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
313
1496
|
var OgTitle = (props) => {
|
|
314
|
-
const { value, renderDefault, path } = props;
|
|
315
|
-
const
|
|
1497
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
1498
|
+
const { options } = schemaType;
|
|
1499
|
+
const parent = useFormValue12([path[0]]);
|
|
316
1500
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
317
|
-
const keywords =
|
|
318
|
-
const feedbackItems =
|
|
1501
|
+
const keywords = useMemo10(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1502
|
+
const feedbackItems = useMemo10(
|
|
319
1503
|
() => getOgTitleValidation(value || "", keywords, isParentseoField),
|
|
320
1504
|
[value, keywords, isParentseoField]
|
|
321
1505
|
);
|
|
322
|
-
return /* @__PURE__ */
|
|
1506
|
+
return /* @__PURE__ */ jsxs13(Stack11, { space: 3, children: [
|
|
323
1507
|
renderDefault(props),
|
|
324
|
-
/* @__PURE__ */
|
|
325
|
-
|
|
1508
|
+
/* @__PURE__ */ jsx13(
|
|
1509
|
+
SeoGenButton,
|
|
1510
|
+
{
|
|
1511
|
+
field: "ogTitle",
|
|
1512
|
+
ai: options == null ? void 0 : options.ai,
|
|
1513
|
+
seoFieldPath: String(path[0]),
|
|
1514
|
+
onGenerate: (v) => onChange(PatchEvent6.from(set6(v)))
|
|
1515
|
+
}
|
|
1516
|
+
),
|
|
1517
|
+
/* @__PURE__ */ jsx13(Stack11, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1518
|
+
/* @__PURE__ */ jsx13(
|
|
326
1519
|
"div",
|
|
327
1520
|
{
|
|
328
1521
|
style: {
|
|
@@ -333,7 +1526,7 @@ var OgTitle = (props) => {
|
|
|
333
1526
|
}
|
|
334
1527
|
}
|
|
335
1528
|
),
|
|
336
|
-
/* @__PURE__ */
|
|
1529
|
+
/* @__PURE__ */ jsx13(Text11, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
337
1530
|
] }, item.text)) })
|
|
338
1531
|
] });
|
|
339
1532
|
};
|
|
@@ -360,8 +1553,8 @@ function openGraph(config = {}) {
|
|
|
360
1553
|
hidden: getFieldHiddenFunction("openGraphTitle", config),
|
|
361
1554
|
components: {
|
|
362
1555
|
input: OgTitle_default
|
|
363
|
-
|
|
364
|
-
}
|
|
1556
|
+
},
|
|
1557
|
+
options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
|
|
365
1558
|
})),
|
|
366
1559
|
defineField(__spreadProps(__spreadValues({
|
|
367
1560
|
name: "description"
|
|
@@ -371,8 +1564,8 @@ function openGraph(config = {}) {
|
|
|
371
1564
|
hidden: getFieldHiddenFunction("openGraphDescription", config),
|
|
372
1565
|
components: {
|
|
373
1566
|
input: OgDescription_default
|
|
374
|
-
|
|
375
|
-
}
|
|
1567
|
+
},
|
|
1568
|
+
options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
|
|
376
1569
|
})),
|
|
377
1570
|
defineField(__spreadProps(__spreadValues({
|
|
378
1571
|
name: "siteName"
|
|
@@ -454,19 +1647,38 @@ function openGraph(config = {}) {
|
|
|
454
1647
|
]
|
|
455
1648
|
});
|
|
456
1649
|
}
|
|
1650
|
+
|
|
1651
|
+
// src/schemas/types/twitter/index.ts
|
|
1652
|
+
import { defineField as defineField2, defineType as defineType2 } from "sanity";
|
|
1653
|
+
|
|
1654
|
+
// src/components/twitter/twitterDescription.tsx
|
|
1655
|
+
import { Stack as Stack12, Text as Text12 } from "@sanity/ui";
|
|
1656
|
+
import { useMemo as useMemo11 } from "react";
|
|
1657
|
+
import { PatchEvent as PatchEvent7, set as set7, useFormValue as useFormValue13 } from "sanity";
|
|
1658
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
457
1659
|
var TwitterDescription = (props) => {
|
|
458
|
-
const { value, renderDefault, path } = props;
|
|
459
|
-
const
|
|
1660
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
1661
|
+
const { options } = schemaType;
|
|
1662
|
+
const parent = useFormValue13([path[0]]);
|
|
460
1663
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
461
|
-
const keywords =
|
|
462
|
-
const feedbackItems =
|
|
1664
|
+
const keywords = useMemo11(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1665
|
+
const feedbackItems = useMemo11(
|
|
463
1666
|
() => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
|
|
464
1667
|
[value, keywords, isParentseoField]
|
|
465
1668
|
);
|
|
466
|
-
return /* @__PURE__ */
|
|
1669
|
+
return /* @__PURE__ */ jsxs14(Stack12, { space: 3, children: [
|
|
467
1670
|
renderDefault(props),
|
|
468
|
-
/* @__PURE__ */
|
|
469
|
-
|
|
1671
|
+
/* @__PURE__ */ jsx14(
|
|
1672
|
+
SeoGenButton,
|
|
1673
|
+
{
|
|
1674
|
+
field: "twitterDescription",
|
|
1675
|
+
ai: options == null ? void 0 : options.ai,
|
|
1676
|
+
seoFieldPath: String(path[0]),
|
|
1677
|
+
onGenerate: (v) => onChange(PatchEvent7.from(set7(v)))
|
|
1678
|
+
}
|
|
1679
|
+
),
|
|
1680
|
+
/* @__PURE__ */ jsx14(Stack12, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1681
|
+
/* @__PURE__ */ jsx14(
|
|
470
1682
|
"div",
|
|
471
1683
|
{
|
|
472
1684
|
style: {
|
|
@@ -477,25 +1689,31 @@ var TwitterDescription = (props) => {
|
|
|
477
1689
|
}
|
|
478
1690
|
}
|
|
479
1691
|
),
|
|
480
|
-
/* @__PURE__ */
|
|
1692
|
+
/* @__PURE__ */ jsx14(Text12, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
481
1693
|
] }, item.text)) })
|
|
482
1694
|
] });
|
|
483
1695
|
};
|
|
484
1696
|
var twitterDescription_default = TwitterDescription;
|
|
1697
|
+
|
|
1698
|
+
// src/components/twitter/TwitterImage.tsx
|
|
1699
|
+
import { Stack as Stack13, Text as Text13 } from "@sanity/ui";
|
|
1700
|
+
import { useMemo as useMemo12 } from "react";
|
|
1701
|
+
import { useFormValue as useFormValue14 } from "sanity";
|
|
1702
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
485
1703
|
var TwitterImage = (props) => {
|
|
486
1704
|
const { value, renderDefault, path } = props;
|
|
487
|
-
const seoParent =
|
|
1705
|
+
const seoParent = useFormValue14([path[0]]);
|
|
488
1706
|
const imgValue = value;
|
|
489
1707
|
const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
|
|
490
1708
|
const altText = imgValue == null ? void 0 : imgValue.alt;
|
|
491
|
-
const feedbackItems =
|
|
1709
|
+
const feedbackItems = useMemo12(
|
|
492
1710
|
() => getTwitterImageValidation(hasImage, altText, seoParent),
|
|
493
1711
|
[hasImage, altText, seoParent]
|
|
494
1712
|
);
|
|
495
|
-
return /* @__PURE__ */
|
|
1713
|
+
return /* @__PURE__ */ jsxs15(Stack13, { space: 3, children: [
|
|
496
1714
|
renderDefault(props),
|
|
497
|
-
/* @__PURE__ */
|
|
498
|
-
/* @__PURE__ */
|
|
1715
|
+
/* @__PURE__ */ jsx15(Stack13, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs15("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1716
|
+
/* @__PURE__ */ jsx15(
|
|
499
1717
|
"div",
|
|
500
1718
|
{
|
|
501
1719
|
style: {
|
|
@@ -506,22 +1724,28 @@ var TwitterImage = (props) => {
|
|
|
506
1724
|
}
|
|
507
1725
|
}
|
|
508
1726
|
),
|
|
509
|
-
/* @__PURE__ */
|
|
1727
|
+
/* @__PURE__ */ jsx15(Text13, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
510
1728
|
] }, item.text)) })
|
|
511
1729
|
] });
|
|
512
1730
|
};
|
|
513
1731
|
var TwitterImage_default = TwitterImage;
|
|
1732
|
+
|
|
1733
|
+
// src/components/twitter/TwitterImageUrl.tsx
|
|
1734
|
+
import { Stack as Stack14, Text as Text14 } from "@sanity/ui";
|
|
1735
|
+
import { useMemo as useMemo13 } from "react";
|
|
1736
|
+
import { useFormValue as useFormValue15 } from "sanity";
|
|
1737
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
514
1738
|
var TwitterImageUrl = (props) => {
|
|
515
1739
|
const { value, renderDefault, path } = props;
|
|
516
|
-
const seoParent =
|
|
517
|
-
const feedbackItems =
|
|
1740
|
+
const seoParent = useFormValue15([path[0]]);
|
|
1741
|
+
const feedbackItems = useMemo13(
|
|
518
1742
|
() => getTwitterImageUrlValidation(value, seoParent),
|
|
519
1743
|
[value, seoParent]
|
|
520
1744
|
);
|
|
521
|
-
return /* @__PURE__ */
|
|
1745
|
+
return /* @__PURE__ */ jsxs16(Stack14, { space: 3, children: [
|
|
522
1746
|
renderDefault(props),
|
|
523
|
-
/* @__PURE__ */
|
|
524
|
-
/* @__PURE__ */
|
|
1747
|
+
/* @__PURE__ */ jsx16(Stack14, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1748
|
+
/* @__PURE__ */ jsx16(
|
|
525
1749
|
"div",
|
|
526
1750
|
{
|
|
527
1751
|
style: {
|
|
@@ -532,24 +1756,40 @@ var TwitterImageUrl = (props) => {
|
|
|
532
1756
|
}
|
|
533
1757
|
}
|
|
534
1758
|
),
|
|
535
|
-
/* @__PURE__ */
|
|
1759
|
+
/* @__PURE__ */ jsx16(Text14, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
536
1760
|
] }, item.text)) })
|
|
537
1761
|
] });
|
|
538
1762
|
};
|
|
539
1763
|
var TwitterImageUrl_default = TwitterImageUrl;
|
|
1764
|
+
|
|
1765
|
+
// src/components/twitter/twitterTitle.tsx
|
|
1766
|
+
import { Stack as Stack15, Text as Text15 } from "@sanity/ui";
|
|
1767
|
+
import { useMemo as useMemo14 } from "react";
|
|
1768
|
+
import { PatchEvent as PatchEvent8, set as set8, useFormValue as useFormValue16 } from "sanity";
|
|
1769
|
+
import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
540
1770
|
var TwitterTitle = (props) => {
|
|
541
|
-
const { value, renderDefault, path } = props;
|
|
542
|
-
const
|
|
1771
|
+
const { value, renderDefault, path, schemaType, onChange } = props;
|
|
1772
|
+
const { options } = schemaType;
|
|
1773
|
+
const parent = useFormValue16([path[0]]);
|
|
543
1774
|
const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
|
|
544
|
-
const keywords =
|
|
545
|
-
const feedbackItems =
|
|
1775
|
+
const keywords = useMemo14(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
|
|
1776
|
+
const feedbackItems = useMemo14(
|
|
546
1777
|
() => getTwitterTitleValidation(value || "", keywords, isParentseoField),
|
|
547
1778
|
[value, keywords, isParentseoField]
|
|
548
1779
|
);
|
|
549
|
-
return /* @__PURE__ */
|
|
1780
|
+
return /* @__PURE__ */ jsxs17(Stack15, { space: 3, children: [
|
|
550
1781
|
renderDefault(props),
|
|
551
|
-
/* @__PURE__ */
|
|
552
|
-
|
|
1782
|
+
/* @__PURE__ */ jsx17(
|
|
1783
|
+
SeoGenButton,
|
|
1784
|
+
{
|
|
1785
|
+
field: "twitterTitle",
|
|
1786
|
+
ai: options == null ? void 0 : options.ai,
|
|
1787
|
+
seoFieldPath: String(path[0]),
|
|
1788
|
+
onGenerate: (v) => onChange(PatchEvent8.from(set8(v)))
|
|
1789
|
+
}
|
|
1790
|
+
),
|
|
1791
|
+
/* @__PURE__ */ jsx17(Stack15, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
|
|
1792
|
+
/* @__PURE__ */ jsx17(
|
|
553
1793
|
"div",
|
|
554
1794
|
{
|
|
555
1795
|
style: {
|
|
@@ -560,7 +1800,7 @@ var TwitterTitle = (props) => {
|
|
|
560
1800
|
}
|
|
561
1801
|
}
|
|
562
1802
|
),
|
|
563
|
-
/* @__PURE__ */
|
|
1803
|
+
/* @__PURE__ */ jsx17(Text15, { weight: "bold", muted: true, size: 14, children: item.text })
|
|
564
1804
|
] }, item.text)) })
|
|
565
1805
|
] });
|
|
566
1806
|
};
|
|
@@ -568,12 +1808,12 @@ var twitterTitle_default = TwitterTitle;
|
|
|
568
1808
|
|
|
569
1809
|
// src/schemas/types/twitter/index.ts
|
|
570
1810
|
function twitter(config = {}) {
|
|
571
|
-
return
|
|
1811
|
+
return defineType2({
|
|
572
1812
|
name: "twitter",
|
|
573
1813
|
title: "X (Formerly Twitter)",
|
|
574
1814
|
type: "object",
|
|
575
1815
|
fields: [
|
|
576
|
-
|
|
1816
|
+
defineField2(__spreadProps(__spreadValues({
|
|
577
1817
|
name: "card"
|
|
578
1818
|
}, getFieldInfo("twitterCard", config.fieldOverrides)), {
|
|
579
1819
|
type: "string",
|
|
@@ -589,28 +1829,29 @@ function twitter(config = {}) {
|
|
|
589
1829
|
initialValue: "summary_large_image"
|
|
590
1830
|
// good default
|
|
591
1831
|
})),
|
|
592
|
-
|
|
1832
|
+
defineField2(__spreadProps(__spreadValues({
|
|
593
1833
|
name: "site"
|
|
594
1834
|
}, getFieldInfo("twitterSite", config.fieldOverrides)), {
|
|
595
1835
|
type: "string",
|
|
596
1836
|
hidden: getFieldHiddenFunction("twitterSite", config)
|
|
597
1837
|
})),
|
|
598
|
-
|
|
1838
|
+
defineField2(__spreadProps(__spreadValues({
|
|
599
1839
|
name: "creator",
|
|
600
1840
|
type: "string"
|
|
601
1841
|
}, getFieldInfo("twitterCreator", config.fieldOverrides)), {
|
|
602
1842
|
hidden: getFieldHiddenFunction("twitterCreator", config)
|
|
603
1843
|
})),
|
|
604
|
-
|
|
1844
|
+
defineField2(__spreadProps(__spreadValues({
|
|
605
1845
|
name: "title",
|
|
606
1846
|
type: "string"
|
|
607
1847
|
}, getFieldInfo("twitterTitle", config.fieldOverrides)), {
|
|
608
1848
|
hidden: getFieldHiddenFunction("twitterTitle", config),
|
|
609
1849
|
components: {
|
|
610
1850
|
input: twitterTitle_default
|
|
611
|
-
}
|
|
1851
|
+
},
|
|
1852
|
+
options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
|
|
612
1853
|
})),
|
|
613
|
-
|
|
1854
|
+
defineField2(__spreadProps(__spreadValues({
|
|
614
1855
|
name: "description",
|
|
615
1856
|
type: "text",
|
|
616
1857
|
rows: 3
|
|
@@ -618,9 +1859,10 @@ function twitter(config = {}) {
|
|
|
618
1859
|
hidden: getFieldHiddenFunction("twitterDescription", config),
|
|
619
1860
|
components: {
|
|
620
1861
|
input: twitterDescription_default
|
|
621
|
-
}
|
|
1862
|
+
},
|
|
1863
|
+
options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
|
|
622
1864
|
})),
|
|
623
|
-
|
|
1865
|
+
defineField2(__spreadProps(__spreadValues({
|
|
624
1866
|
name: "imageType"
|
|
625
1867
|
}, getFieldInfo("twitterImageType", config.fieldOverrides)), {
|
|
626
1868
|
type: "string",
|
|
@@ -633,7 +1875,7 @@ function twitter(config = {}) {
|
|
|
633
1875
|
hidden: getFieldHiddenFunction("twitterImage", config),
|
|
634
1876
|
initialValue: "upload"
|
|
635
1877
|
})),
|
|
636
|
-
|
|
1878
|
+
defineField2(__spreadProps(__spreadValues({
|
|
637
1879
|
name: "image"
|
|
638
1880
|
}, getFieldInfo("twitterImage", config.fieldOverrides)), {
|
|
639
1881
|
type: "image",
|
|
@@ -644,7 +1886,7 @@ function twitter(config = {}) {
|
|
|
644
1886
|
input: TwitterImage_default
|
|
645
1887
|
},
|
|
646
1888
|
fields: [
|
|
647
|
-
|
|
1889
|
+
defineField2({
|
|
648
1890
|
name: "alt",
|
|
649
1891
|
title: "Image Alt Text",
|
|
650
1892
|
type: "string",
|
|
@@ -658,7 +1900,7 @@ function twitter(config = {}) {
|
|
|
658
1900
|
return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
|
|
659
1901
|
}
|
|
660
1902
|
})),
|
|
661
|
-
|
|
1903
|
+
defineField2(__spreadProps(__spreadValues({
|
|
662
1904
|
name: "imageUrl"
|
|
663
1905
|
}, getFieldInfo("twitterImageUrl", config.fieldOverrides)), {
|
|
664
1906
|
type: "url",
|
|
@@ -677,7 +1919,7 @@ function twitter(config = {}) {
|
|
|
677
1919
|
}
|
|
678
1920
|
|
|
679
1921
|
// src/schemas/index.ts
|
|
680
|
-
var LazySeoPreview = lazy(() => import(
|
|
1922
|
+
var LazySeoPreview = lazy(() => import("./SeoPreview-UNQBKTQO.js"));
|
|
681
1923
|
var SeoPreviewWrapper = (props) => createElement(Suspense, { fallback: null }, createElement(LazySeoPreview, props));
|
|
682
1924
|
function buildFieldGroupMap(groups) {
|
|
683
1925
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -712,18 +1954,86 @@ function withGroup(field, fieldGroupMap) {
|
|
|
712
1954
|
if (!groups || groups.length === 0) return field;
|
|
713
1955
|
return __spreadProps(__spreadValues({}, field), { group: groups.length === 1 ? groups[0] : groups });
|
|
714
1956
|
}
|
|
1957
|
+
function shouldShowPreviewField(seoPreview) {
|
|
1958
|
+
if (typeof seoPreview === "boolean") return seoPreview;
|
|
1959
|
+
if (typeof seoPreview === "object") return !isEmpty(seoPreview);
|
|
1960
|
+
return false;
|
|
1961
|
+
}
|
|
1962
|
+
function getSeoPreviewSuffixOptions(seoPreview) {
|
|
1963
|
+
if (typeof seoPreview !== "object" || !seoPreview) return {};
|
|
1964
|
+
const extra = {};
|
|
1965
|
+
if (seoPreview.titleSuffix) extra.titleSuffix = seoPreview.titleSuffix;
|
|
1966
|
+
if (seoPreview.titleSuffixQuery) extra.titleSuffixQuery = seoPreview.titleSuffixQuery;
|
|
1967
|
+
return extra;
|
|
1968
|
+
}
|
|
1969
|
+
function getPreviewFieldOptions(config) {
|
|
1970
|
+
const seoPreview = config.seoPreview;
|
|
1971
|
+
const extra = {};
|
|
1972
|
+
if (typeof seoPreview === "object" && seoPreview) {
|
|
1973
|
+
if (seoPreview.prefix) extra.prefix = seoPreview.prefix;
|
|
1974
|
+
if (seoPreview.titleSuffixInheritColor) {
|
|
1975
|
+
extra.titleSuffixInheritColor = seoPreview.titleSuffixInheritColor;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return __spreadValues(__spreadValues(__spreadValues({
|
|
1979
|
+
baseUrl: config.baseUrl || "https://www.example.com"
|
|
1980
|
+
}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(seoPreview)), extra);
|
|
1981
|
+
}
|
|
1982
|
+
function getAiOption(config) {
|
|
1983
|
+
return config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {};
|
|
1984
|
+
}
|
|
1985
|
+
function getTitleFieldOptions(config) {
|
|
1986
|
+
return __spreadValues(__spreadValues(__spreadValues({}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(config.seoPreview)), getAiOption(config));
|
|
1987
|
+
}
|
|
1988
|
+
function buildGeoChecklistField(config, fieldGroupMap) {
|
|
1989
|
+
if (config.geo === false) return [];
|
|
1990
|
+
return [
|
|
1991
|
+
withGroup(
|
|
1992
|
+
defineField3(__spreadProps(__spreadValues({
|
|
1993
|
+
name: "geoChecklist"
|
|
1994
|
+
}, getFieldInfo("geoChecklist", config.fieldOverrides)), {
|
|
1995
|
+
type: "string",
|
|
1996
|
+
readOnly: true,
|
|
1997
|
+
components: {
|
|
1998
|
+
input: GEOChecklist_default
|
|
1999
|
+
},
|
|
2000
|
+
options: __spreadValues({}, config.licenseKey ? { licenseKey: config.licenseKey } : {}),
|
|
2001
|
+
hidden: getFieldHiddenFunction("geoChecklist", config)
|
|
2002
|
+
})),
|
|
2003
|
+
fieldGroupMap
|
|
2004
|
+
)
|
|
2005
|
+
];
|
|
2006
|
+
}
|
|
2007
|
+
function buildMetaTagsPreviewField(config, fieldGroupMap) {
|
|
2008
|
+
if (config.metaTagsPreview === false) return [];
|
|
2009
|
+
return [
|
|
2010
|
+
withGroup(
|
|
2011
|
+
defineField3(__spreadProps(__spreadValues({
|
|
2012
|
+
name: "metaTagsPreview"
|
|
2013
|
+
}, getFieldInfo("metaTagsPreview", config.fieldOverrides)), {
|
|
2014
|
+
type: "string",
|
|
2015
|
+
readOnly: true,
|
|
2016
|
+
components: {
|
|
2017
|
+
input: MetaTagsPreview_default
|
|
2018
|
+
},
|
|
2019
|
+
hidden: getFieldHiddenFunction("metaTagsPreview", config)
|
|
2020
|
+
})),
|
|
2021
|
+
fieldGroupMap
|
|
2022
|
+
)
|
|
2023
|
+
];
|
|
2024
|
+
}
|
|
715
2025
|
function seoFieldsSchema(config = {}) {
|
|
716
2026
|
const groupsCfg = config.fieldGroups;
|
|
717
2027
|
const fieldGroupMap = (groupsCfg == null ? void 0 : groupsCfg.length) ? buildFieldGroupMap(groupsCfg) : void 0;
|
|
718
2028
|
const sanityGroups = (groupsCfg == null ? void 0 : groupsCfg.length) ? toSanityGroups(groupsCfg) : void 0;
|
|
719
|
-
return
|
|
2029
|
+
return defineType3(__spreadProps(__spreadValues({
|
|
720
2030
|
name: "seoFields",
|
|
721
2031
|
title: "SEO Fields",
|
|
722
2032
|
type: "object"
|
|
723
2033
|
}, sanityGroups ? { groups: sanityGroups } : {}), {
|
|
724
2034
|
fields: [
|
|
725
2035
|
withGroup(
|
|
726
|
-
|
|
2036
|
+
defineField3({
|
|
727
2037
|
name: "robots",
|
|
728
2038
|
title: "Robots Settings",
|
|
729
2039
|
type: "robots",
|
|
@@ -732,16 +2042,14 @@ function seoFieldsSchema(config = {}) {
|
|
|
732
2042
|
fieldGroupMap
|
|
733
2043
|
),
|
|
734
2044
|
// 👇 conditionally spread preview field
|
|
735
|
-
...
|
|
2045
|
+
...shouldShowPreviewField(config.seoPreview) ? [
|
|
736
2046
|
withGroup(
|
|
737
|
-
|
|
2047
|
+
defineField3({
|
|
738
2048
|
name: "preview",
|
|
739
2049
|
title: "SEO Preview",
|
|
740
2050
|
type: "string",
|
|
741
2051
|
components: { input: SeoPreviewWrapper },
|
|
742
|
-
options:
|
|
743
|
-
baseUrl: config.baseUrl || "https://www.example.com"
|
|
744
|
-
}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.prefix ? { prefix: config.seoPreview.prefix } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.titleSuffix ? { titleSuffix: config.seoPreview.titleSuffix } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.titleSuffixInheritColor ? { titleSuffixInheritColor: config.seoPreview.titleSuffixInheritColor } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.titleSuffixQuery ? { titleSuffixQuery: config.seoPreview.titleSuffixQuery } : {}),
|
|
2052
|
+
options: getPreviewFieldOptions(config),
|
|
745
2053
|
initialValue: "",
|
|
746
2054
|
readOnly: true
|
|
747
2055
|
}),
|
|
@@ -749,20 +2057,20 @@ function seoFieldsSchema(config = {}) {
|
|
|
749
2057
|
)
|
|
750
2058
|
] : [],
|
|
751
2059
|
withGroup(
|
|
752
|
-
|
|
2060
|
+
defineField3(__spreadProps(__spreadValues({
|
|
753
2061
|
name: "title"
|
|
754
2062
|
}, getFieldInfo("title", config.fieldOverrides)), {
|
|
755
2063
|
type: "string",
|
|
756
2064
|
components: {
|
|
757
2065
|
input: MetaTitle_default
|
|
758
2066
|
},
|
|
759
|
-
options:
|
|
2067
|
+
options: getTitleFieldOptions(config),
|
|
760
2068
|
hidden: getFieldHiddenFunction("title", config)
|
|
761
2069
|
})),
|
|
762
2070
|
fieldGroupMap
|
|
763
2071
|
),
|
|
764
2072
|
withGroup(
|
|
765
|
-
|
|
2073
|
+
defineField3(__spreadProps(__spreadValues({
|
|
766
2074
|
name: "description"
|
|
767
2075
|
}, getFieldInfo("description", config.fieldOverrides)), {
|
|
768
2076
|
type: "text",
|
|
@@ -770,12 +2078,13 @@ function seoFieldsSchema(config = {}) {
|
|
|
770
2078
|
components: {
|
|
771
2079
|
input: MetaDescription_default
|
|
772
2080
|
},
|
|
2081
|
+
options: getAiOption(config),
|
|
773
2082
|
hidden: getFieldHiddenFunction("description", config)
|
|
774
2083
|
})),
|
|
775
2084
|
fieldGroupMap
|
|
776
2085
|
),
|
|
777
2086
|
withGroup(
|
|
778
|
-
|
|
2087
|
+
defineField3(__spreadProps(__spreadValues({
|
|
779
2088
|
name: "metaImage"
|
|
780
2089
|
}, getFieldInfo("metaImage", config.fieldOverrides)), {
|
|
781
2090
|
type: "image",
|
|
@@ -790,7 +2099,7 @@ function seoFieldsSchema(config = {}) {
|
|
|
790
2099
|
fieldGroupMap
|
|
791
2100
|
),
|
|
792
2101
|
withGroup(
|
|
793
|
-
|
|
2102
|
+
defineField3(__spreadProps(__spreadValues({
|
|
794
2103
|
name: "metaAttributes"
|
|
795
2104
|
}, getFieldInfo("metaAttributes", config.fieldOverrides)), {
|
|
796
2105
|
type: "array",
|
|
@@ -800,19 +2109,21 @@ function seoFieldsSchema(config = {}) {
|
|
|
800
2109
|
fieldGroupMap
|
|
801
2110
|
),
|
|
802
2111
|
withGroup(
|
|
803
|
-
|
|
2112
|
+
defineField3(__spreadProps(__spreadValues({
|
|
804
2113
|
name: "keywords"
|
|
805
2114
|
}, getFieldInfo("keywords", config.fieldOverrides)), {
|
|
806
2115
|
title: "Keywords",
|
|
807
2116
|
type: "array",
|
|
808
2117
|
of: [{ type: "string" }],
|
|
809
2118
|
description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
|
|
2119
|
+
components: config.ai ? { input: KeywordsInput_default } : void 0,
|
|
2120
|
+
options: getAiOption(config),
|
|
810
2121
|
hidden: getFieldHiddenFunction("keywords", config)
|
|
811
2122
|
})),
|
|
812
2123
|
fieldGroupMap
|
|
813
2124
|
),
|
|
814
2125
|
withGroup(
|
|
815
|
-
|
|
2126
|
+
defineField3(__spreadProps(__spreadValues({
|
|
816
2127
|
name: "canonicalUrl"
|
|
817
2128
|
}, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
|
|
818
2129
|
title: "Canonical URL",
|
|
@@ -822,18 +2133,47 @@ function seoFieldsSchema(config = {}) {
|
|
|
822
2133
|
})),
|
|
823
2134
|
fieldGroupMap
|
|
824
2135
|
),
|
|
2136
|
+
withGroup(
|
|
2137
|
+
defineField3(__spreadProps(__spreadValues({
|
|
2138
|
+
name: "focusKeyword"
|
|
2139
|
+
}, getFieldInfo("focusKeyword", config.fieldOverrides)), {
|
|
2140
|
+
type: "string",
|
|
2141
|
+
components: {
|
|
2142
|
+
input: FocusKeywordInput_default
|
|
2143
|
+
},
|
|
2144
|
+
options: getAiOption(config),
|
|
2145
|
+
hidden: getFieldHiddenFunction("focusKeyword", config)
|
|
2146
|
+
})),
|
|
2147
|
+
fieldGroupMap
|
|
2148
|
+
),
|
|
2149
|
+
withGroup(
|
|
2150
|
+
defineField3(__spreadProps(__spreadValues({
|
|
2151
|
+
name: "hreflangs"
|
|
2152
|
+
}, getFieldInfo("hreflangs", config.fieldOverrides)), {
|
|
2153
|
+
type: "array",
|
|
2154
|
+
of: [{ type: "hreflangEntry" }],
|
|
2155
|
+
description: "Add alternate language/region versions. Include x-default for the fallback URL.",
|
|
2156
|
+
hidden: getFieldHiddenFunction("hreflangs", config)
|
|
2157
|
+
})),
|
|
2158
|
+
fieldGroupMap
|
|
2159
|
+
),
|
|
2160
|
+
...buildGeoChecklistField(config, fieldGroupMap),
|
|
2161
|
+
...buildMetaTagsPreviewField(config, fieldGroupMap),
|
|
825
2162
|
withGroup(openGraph(config), fieldGroupMap),
|
|
826
2163
|
withGroup(twitter(config), fieldGroupMap)
|
|
827
2164
|
]
|
|
828
2165
|
}));
|
|
829
2166
|
}
|
|
2167
|
+
|
|
2168
|
+
// src/schemas/types/baseMeta/index.ts
|
|
2169
|
+
import { defineField as defineField4, defineType as defineType4 } from "sanity";
|
|
830
2170
|
function baseMeta(config = {}) {
|
|
831
|
-
return
|
|
2171
|
+
return defineType4({
|
|
832
2172
|
name: "baseMeta",
|
|
833
2173
|
title: "Basic Meta Settings",
|
|
834
2174
|
type: "object",
|
|
835
2175
|
fields: [
|
|
836
|
-
|
|
2176
|
+
defineField4(__spreadProps(__spreadValues({
|
|
837
2177
|
name: "title"
|
|
838
2178
|
}, getFieldInfo("title", config.fieldOverrides)), {
|
|
839
2179
|
type: "string",
|
|
@@ -842,7 +2182,7 @@ function baseMeta(config = {}) {
|
|
|
842
2182
|
},
|
|
843
2183
|
hidden: getFieldHiddenFunction("title", config)
|
|
844
2184
|
})),
|
|
845
|
-
|
|
2185
|
+
defineField4(__spreadProps(__spreadValues({
|
|
846
2186
|
name: "description"
|
|
847
2187
|
}, getFieldInfo("description", config.fieldOverrides)), {
|
|
848
2188
|
type: "text",
|
|
@@ -852,7 +2192,7 @@ function baseMeta(config = {}) {
|
|
|
852
2192
|
},
|
|
853
2193
|
hidden: getFieldHiddenFunction("description", config)
|
|
854
2194
|
})),
|
|
855
|
-
|
|
2195
|
+
defineField4(__spreadProps(__spreadValues({
|
|
856
2196
|
name: "metaImage"
|
|
857
2197
|
}, getFieldInfo("metaImage", config.fieldOverrides)), {
|
|
858
2198
|
type: "image",
|
|
@@ -864,7 +2204,7 @@ function baseMeta(config = {}) {
|
|
|
864
2204
|
},
|
|
865
2205
|
hidden: getFieldHiddenFunction("metaImage", config)
|
|
866
2206
|
})),
|
|
867
|
-
|
|
2207
|
+
defineField4(__spreadProps(__spreadValues({
|
|
868
2208
|
name: "keywords"
|
|
869
2209
|
}, getFieldInfo("keywords", config.fieldOverrides)), {
|
|
870
2210
|
title: "Keywords",
|
|
@@ -873,7 +2213,7 @@ function baseMeta(config = {}) {
|
|
|
873
2213
|
description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
|
|
874
2214
|
hidden: getFieldHiddenFunction("keywords", config)
|
|
875
2215
|
})),
|
|
876
|
-
|
|
2216
|
+
defineField4(__spreadProps(__spreadValues({
|
|
877
2217
|
name: "canonicalUrl"
|
|
878
2218
|
}, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
|
|
879
2219
|
title: "Canonical URL",
|
|
@@ -881,7 +2221,7 @@ function baseMeta(config = {}) {
|
|
|
881
2221
|
description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page.",
|
|
882
2222
|
hidden: getFieldHiddenFunction("canonicalUrl", config)
|
|
883
2223
|
})),
|
|
884
|
-
|
|
2224
|
+
defineField4(__spreadProps(__spreadValues({
|
|
885
2225
|
name: "metaAttributes"
|
|
886
2226
|
}, getFieldInfo("metaAttributes", config.fieldOverrides)), {
|
|
887
2227
|
type: "array",
|
|
@@ -891,17 +2231,55 @@ function baseMeta(config = {}) {
|
|
|
891
2231
|
]
|
|
892
2232
|
});
|
|
893
2233
|
}
|
|
894
|
-
|
|
2234
|
+
|
|
2235
|
+
// src/schemas/types/hreflang/index.ts
|
|
2236
|
+
import { defineField as defineField5, defineType as defineType5 } from "sanity";
|
|
2237
|
+
var hreflang_default = defineType5({
|
|
2238
|
+
name: "hreflangEntry",
|
|
2239
|
+
title: "Hreflang Entry",
|
|
2240
|
+
type: "object",
|
|
2241
|
+
fields: [
|
|
2242
|
+
defineField5({
|
|
2243
|
+
name: "locale",
|
|
2244
|
+
title: "Language / Region",
|
|
2245
|
+
type: "string",
|
|
2246
|
+
description: "BCP 47 tag \u2014 e.g. en, fr-FR, x-default",
|
|
2247
|
+
validation: (Rule) => Rule.required().custom((value) => {
|
|
2248
|
+
if (!value) return "Required";
|
|
2249
|
+
if (!/^(x-default|[a-z]{2,3}(-[A-Z]{2,3})?)$/.test(value)) {
|
|
2250
|
+
return "Must be a valid BCP 47 tag (e.g. en, fr-FR, x-default)";
|
|
2251
|
+
}
|
|
2252
|
+
return true;
|
|
2253
|
+
})
|
|
2254
|
+
}),
|
|
2255
|
+
defineField5({
|
|
2256
|
+
name: "url",
|
|
2257
|
+
title: "URL",
|
|
2258
|
+
type: "url",
|
|
2259
|
+
validation: (Rule) => Rule.required().uri({ scheme: ["http", "https"] })
|
|
2260
|
+
})
|
|
2261
|
+
],
|
|
2262
|
+
preview: {
|
|
2263
|
+
select: {
|
|
2264
|
+
title: "locale",
|
|
2265
|
+
subtitle: "url"
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
// src/schemas/types/metaAttribute/index.ts
|
|
2271
|
+
import { defineField as defineField6, defineType as defineType6 } from "sanity";
|
|
2272
|
+
var metaAttribute_default = defineType6({
|
|
895
2273
|
name: "metaAttribute",
|
|
896
2274
|
title: "Meta Attribute",
|
|
897
2275
|
type: "object",
|
|
898
2276
|
fields: [
|
|
899
|
-
|
|
2277
|
+
defineField6({
|
|
900
2278
|
name: "key",
|
|
901
2279
|
title: "Attribute Name",
|
|
902
2280
|
type: "string"
|
|
903
2281
|
}),
|
|
904
|
-
|
|
2282
|
+
defineField6({
|
|
905
2283
|
name: "type",
|
|
906
2284
|
title: "Attribute Value Type",
|
|
907
2285
|
type: "string",
|
|
@@ -913,13 +2291,13 @@ var metaAttribute_default = defineType({
|
|
|
913
2291
|
},
|
|
914
2292
|
initialValue: "string"
|
|
915
2293
|
}),
|
|
916
|
-
|
|
2294
|
+
defineField6({
|
|
917
2295
|
name: "value",
|
|
918
2296
|
title: "Attribute Value",
|
|
919
2297
|
type: "string",
|
|
920
2298
|
hidden: ({ parent }) => (parent == null ? void 0 : parent.type) === "image"
|
|
921
2299
|
}),
|
|
922
|
-
|
|
2300
|
+
defineField6({
|
|
923
2301
|
name: "image",
|
|
924
2302
|
title: "Attribute Image Value",
|
|
925
2303
|
type: "image",
|
|
@@ -949,7 +2327,10 @@ var metaAttribute_default = defineType({
|
|
|
949
2327
|
}
|
|
950
2328
|
}
|
|
951
2329
|
});
|
|
952
|
-
|
|
2330
|
+
|
|
2331
|
+
// src/schemas/types/metaTag/index.ts
|
|
2332
|
+
import { defineType as defineType7 } from "sanity";
|
|
2333
|
+
var metaTag_default = defineType7({
|
|
953
2334
|
name: "metaTag",
|
|
954
2335
|
title: "Meta Tag",
|
|
955
2336
|
type: "object",
|
|
@@ -963,27 +2344,44 @@ var metaTag_default = defineType({
|
|
|
963
2344
|
}
|
|
964
2345
|
]
|
|
965
2346
|
});
|
|
966
|
-
|
|
2347
|
+
|
|
2348
|
+
// src/schemas/types/robots/index.ts
|
|
2349
|
+
import { defineField as defineField7, defineType as defineType8 } from "sanity";
|
|
2350
|
+
var robots_default = defineType8({
|
|
967
2351
|
name: "robots",
|
|
968
2352
|
title: "Robots Settings",
|
|
969
2353
|
type: "object",
|
|
970
2354
|
fields: [
|
|
971
|
-
|
|
2355
|
+
defineField7({
|
|
972
2356
|
name: "noIndex",
|
|
973
2357
|
title: "No Index",
|
|
974
2358
|
type: "boolean",
|
|
975
2359
|
initialValue: false,
|
|
976
2360
|
description: "Enable this to prevent search engines from indexing this page. The page will not appear in search results."
|
|
977
2361
|
}),
|
|
978
|
-
|
|
2362
|
+
defineField7({
|
|
979
2363
|
name: "noFollow",
|
|
980
2364
|
title: "No Follow",
|
|
981
2365
|
type: "boolean",
|
|
982
2366
|
initialValue: false,
|
|
983
2367
|
description: "Enable this to prevent search engines from following links on this page. Links will not pass SEO value."
|
|
2368
|
+
}),
|
|
2369
|
+
defineField7({
|
|
2370
|
+
name: "noTranslate",
|
|
2371
|
+
title: "No Translate",
|
|
2372
|
+
type: "boolean",
|
|
2373
|
+
initialValue: false,
|
|
2374
|
+
description: "Enable this to prevent search engines from offering a translated version of this page in search results."
|
|
2375
|
+
}),
|
|
2376
|
+
defineField7({
|
|
2377
|
+
name: "noImageIndex",
|
|
2378
|
+
title: "No Image Index",
|
|
2379
|
+
type: "boolean",
|
|
2380
|
+
initialValue: false,
|
|
2381
|
+
description: "Enable this to prevent images on this page from appearing in image search results."
|
|
984
2382
|
})
|
|
985
2383
|
],
|
|
986
|
-
description: "Select how search engines should index
|
|
2384
|
+
description: "Select how search engines should index, follow links, translate content, and index images on this page."
|
|
987
2385
|
});
|
|
988
2386
|
|
|
989
2387
|
// src/schemas/types/index.ts
|
|
@@ -999,13 +2397,14 @@ function types(config = {}) {
|
|
|
999
2397
|
// pass config here
|
|
1000
2398
|
metaAttribute_default,
|
|
1001
2399
|
metaTag_default,
|
|
1002
|
-
robots_default
|
|
2400
|
+
robots_default,
|
|
2401
|
+
hreflang_default
|
|
1003
2402
|
];
|
|
1004
2403
|
}
|
|
1005
2404
|
|
|
1006
2405
|
// 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;
|
|
2406
|
+
var resolveDashboardConfig = (healthDashboard, rootApiVersion, rootLicenseKey) => {
|
|
2407
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
1009
2408
|
const cfg = typeof healthDashboard === "object" ? healthDashboard : void 0;
|
|
1010
2409
|
const deprecationWarnings = [];
|
|
1011
2410
|
if ((cfg == null ? void 0 : cfg.apiVersion) !== void 0) {
|
|
@@ -1018,27 +2417,37 @@ var resolveDashboardConfig = (healthDashboard, rootApiVersion) => {
|
|
|
1018
2417
|
'[sanity-plugin-seofields] "healthDashboard.apiVersion" is deprecated. Move it to the root "apiVersion" option instead.'
|
|
1019
2418
|
);
|
|
1020
2419
|
}
|
|
2420
|
+
if ((cfg == null ? void 0 : cfg.licenseKey) !== void 0 && !rootLicenseKey) {
|
|
2421
|
+
deprecationWarnings.push({
|
|
2422
|
+
key: "healthDashboard.licenseKey \u2192 licenseKey",
|
|
2423
|
+
version: "v1.8.0",
|
|
2424
|
+
changelogUrl: "https://github.com/hardik-143/sanity-plugin-seofields/blob/main/CHANGELOG.md"
|
|
2425
|
+
});
|
|
2426
|
+
console.warn(
|
|
2427
|
+
'[sanity-plugin-seofields] "healthDashboard.licenseKey" is deprecated. Move it to the root "licenseKey" option instead.'
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
1021
2430
|
return {
|
|
1022
2431
|
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: (
|
|
2432
|
+
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",
|
|
2433
|
+
toolName: (_e = (_d = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _d.name) != null ? _e : "seo-health-dashboard",
|
|
2434
|
+
icon: (_f = cfg == null ? void 0 : cfg.content) == null ? void 0 : _f.icon,
|
|
2435
|
+
title: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.title,
|
|
2436
|
+
description: (_h = cfg == null ? void 0 : cfg.content) == null ? void 0 : _h.description,
|
|
1028
2437
|
showTypeColumn: cfg == null ? void 0 : cfg.showTypeColumn,
|
|
1029
2438
|
showDocumentId: cfg == null ? void 0 : cfg.showDocumentId,
|
|
1030
|
-
queryTypes: (
|
|
1031
|
-
queryRequireSeo: (
|
|
1032
|
-
queryGroq: (
|
|
1033
|
-
apiVersion: (
|
|
1034
|
-
licenseKey: cfg == null ? void 0 : cfg.licenseKey,
|
|
2439
|
+
queryTypes: (_i = cfg == null ? void 0 : cfg.query) == null ? void 0 : _i.types,
|
|
2440
|
+
queryRequireSeo: (_j = cfg == null ? void 0 : cfg.query) == null ? void 0 : _j.requireSeo,
|
|
2441
|
+
queryGroq: (_k = cfg == null ? void 0 : cfg.query) == null ? void 0 : _k.groq,
|
|
2442
|
+
apiVersion: (_l = cfg == null ? void 0 : cfg.apiVersion) != null ? _l : rootApiVersion,
|
|
2443
|
+
licenseKey: rootLicenseKey != null ? rootLicenseKey : cfg == null ? void 0 : cfg.licenseKey,
|
|
1035
2444
|
typeDisplayLabels: cfg == null ? void 0 : cfg.typeDisplayLabels,
|
|
1036
2445
|
typeColumnMode: cfg == null ? void 0 : cfg.typeColumnMode,
|
|
1037
2446
|
titleField: cfg == null ? void 0 : cfg.titleField,
|
|
1038
2447
|
getDocumentBadge: cfg == null ? void 0 : cfg.getDocumentBadge,
|
|
1039
|
-
loadingLicense: (
|
|
1040
|
-
loadingDocuments: (
|
|
1041
|
-
noDocuments: (
|
|
2448
|
+
loadingLicense: (_m = cfg == null ? void 0 : cfg.content) == null ? void 0 : _m.loadingLicense,
|
|
2449
|
+
loadingDocuments: (_n = cfg == null ? void 0 : cfg.content) == null ? void 0 : _n.loadingDocuments,
|
|
2450
|
+
noDocuments: (_o = cfg == null ? void 0 : cfg.content) == null ? void 0 : _o.noDocuments,
|
|
1042
2451
|
previewMode: cfg == null ? void 0 : cfg.previewMode,
|
|
1043
2452
|
structureTool: cfg == null ? void 0 : cfg.structureTool,
|
|
1044
2453
|
exportEnabled: (() => {
|
|
@@ -1053,18 +2462,19 @@ var resolveDashboardConfig = (healthDashboard, rootApiVersion) => {
|
|
|
1053
2462
|
if (typeof exportCfg === "object" && exportCfg.formats) return exportCfg.formats;
|
|
1054
2463
|
return ["csv", "json"];
|
|
1055
2464
|
})(),
|
|
1056
|
-
compactStats: (
|
|
2465
|
+
compactStats: (_p = cfg == null ? void 0 : cfg.compactStats) != null ? _p : false,
|
|
1057
2466
|
deprecationWarnings
|
|
1058
2467
|
};
|
|
1059
2468
|
};
|
|
1060
2469
|
var seofields = definePlugin((config = {}) => {
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
2470
|
+
const cfg = config;
|
|
2471
|
+
const { healthDashboard = true, licenseKey, publishGate } = cfg;
|
|
2472
|
+
const dash = resolveDashboardConfig(healthDashboard, cfg.apiVersion, licenseKey);
|
|
2473
|
+
const LazySeoHealthTool = lazy2(() => import("./SeoHealthTool-CWI3KB2V.js"));
|
|
2474
|
+
const BoundSeoHealthTool = () => createElement2(
|
|
2475
|
+
Suspense2,
|
|
1066
2476
|
{ fallback: null },
|
|
1067
|
-
|
|
2477
|
+
createElement2(LazySeoHealthTool, {
|
|
1068
2478
|
icon: dash.icon,
|
|
1069
2479
|
title: dash.title,
|
|
1070
2480
|
description: dash.description,
|
|
@@ -1090,12 +2500,23 @@ var seofields = definePlugin((config = {}) => {
|
|
|
1090
2500
|
_deprecationWarnings: dash.deprecationWarnings
|
|
1091
2501
|
})
|
|
1092
2502
|
);
|
|
1093
|
-
return __spreadValues({
|
|
2503
|
+
return __spreadValues(__spreadValues({
|
|
1094
2504
|
name: "sanity-plugin-seofields",
|
|
1095
2505
|
schema: {
|
|
1096
|
-
types: types(
|
|
2506
|
+
types: types(cfg)
|
|
2507
|
+
}
|
|
2508
|
+
}, publishGate && licenseKey && {
|
|
2509
|
+
document: {
|
|
2510
|
+
actions: (prev, context) => {
|
|
2511
|
+
var _a;
|
|
2512
|
+
if (((_a = publishGate.types) == null ? void 0 : _a.length) && !publishGate.types.includes(context.schemaType))
|
|
2513
|
+
return prev;
|
|
2514
|
+
return prev.map(
|
|
2515
|
+
(Action) => Action.action === "publish" ? createSeoPublishGateAction(Action, __spreadProps(__spreadValues({}, publishGate), { licenseKey })) : Action
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
1097
2518
|
}
|
|
1098
|
-
}, dash.enabled && {
|
|
2519
|
+
}), dash.enabled && {
|
|
1099
2520
|
tools: [
|
|
1100
2521
|
{
|
|
1101
2522
|
name: dash.toolName,
|
|
@@ -1107,28 +2528,187 @@ var seofields = definePlugin((config = {}) => {
|
|
|
1107
2528
|
});
|
|
1108
2529
|
});
|
|
1109
2530
|
var plugin_default = seofields;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
2531
|
+
|
|
2532
|
+
// src/components/SeoHealthPane.tsx
|
|
2533
|
+
import { createSeoHealthPane } from "seofields-pro";
|
|
2534
|
+
import { createSeoHealthPaneDefault } from "seofields-pro";
|
|
2535
|
+
|
|
2536
|
+
// src/utils/scoring.ts
|
|
2537
|
+
var getStatusCategory = (score) => {
|
|
2538
|
+
if (score >= 80) return "excellent";
|
|
2539
|
+
if (score >= 60) return "good";
|
|
2540
|
+
if (score >= 40) return "fair";
|
|
2541
|
+
if (score > 0) return "poor";
|
|
2542
|
+
return "missing";
|
|
2543
|
+
};
|
|
2544
|
+
var scoreMetaTitle = (title) => {
|
|
2545
|
+
const issues = [];
|
|
2546
|
+
let score = 0;
|
|
2547
|
+
if (title && title.length >= 50 && title.length <= 60) {
|
|
2548
|
+
score = 15;
|
|
2549
|
+
} else if (title && title.length > 0) {
|
|
2550
|
+
score = 10;
|
|
2551
|
+
if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
|
|
2552
|
+
if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
|
|
2553
|
+
} else {
|
|
2554
|
+
issues.push("Missing meta title");
|
|
2555
|
+
}
|
|
2556
|
+
return { score, issues };
|
|
2557
|
+
};
|
|
2558
|
+
var scoreMetaDescription = (description) => {
|
|
2559
|
+
const issues = [];
|
|
2560
|
+
let score = 0;
|
|
2561
|
+
if (description && description.length >= 120 && description.length <= 160) {
|
|
2562
|
+
score = 15;
|
|
2563
|
+
} else if (description && description.length > 0) {
|
|
2564
|
+
score = 10;
|
|
2565
|
+
if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
|
|
2566
|
+
if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
|
|
2567
|
+
} else {
|
|
2568
|
+
issues.push("Missing meta description");
|
|
2569
|
+
}
|
|
2570
|
+
return { score, issues };
|
|
2571
|
+
};
|
|
2572
|
+
var scoreOpenGraph = (openGraph2) => {
|
|
2573
|
+
const issues = [];
|
|
2574
|
+
let score = 0;
|
|
2575
|
+
if (openGraph2) {
|
|
2576
|
+
if (openGraph2.title) score += 6;
|
|
2577
|
+
else issues.push("Missing OG title");
|
|
2578
|
+
if (openGraph2.description) score += 6;
|
|
2579
|
+
else issues.push("Missing OG description");
|
|
2580
|
+
if (openGraph2.image) score += 6;
|
|
2581
|
+
else issues.push("Missing OG image");
|
|
2582
|
+
if (openGraph2.type) score += 7;
|
|
2583
|
+
else issues.push("Missing OG type");
|
|
2584
|
+
} else {
|
|
2585
|
+
issues.push("Open Graph not configured");
|
|
2586
|
+
}
|
|
2587
|
+
return { score, issues };
|
|
2588
|
+
};
|
|
2589
|
+
var scoreTwitterCard = (twitter2) => {
|
|
2590
|
+
const issues = [];
|
|
2591
|
+
let score = 0;
|
|
2592
|
+
if (twitter2) {
|
|
2593
|
+
if (twitter2.title) score += 5;
|
|
2594
|
+
else issues.push("Missing Twitter title");
|
|
2595
|
+
if (twitter2.description) score += 5;
|
|
2596
|
+
else issues.push("Missing Twitter description");
|
|
2597
|
+
if (twitter2.image) score += 5;
|
|
2598
|
+
else issues.push("Missing Twitter image");
|
|
2599
|
+
} else {
|
|
2600
|
+
issues.push("Twitter Card not configured");
|
|
2601
|
+
}
|
|
2602
|
+
return { score, issues };
|
|
2603
|
+
};
|
|
2604
|
+
var scoreKeywordArray = (keywords, title, description) => {
|
|
2605
|
+
if (!keywords || keywords.length === 0) {
|
|
2606
|
+
return { score: 0, issues: ["No keywords defined"], stuffed: false };
|
|
2607
|
+
}
|
|
2608
|
+
const issues = [];
|
|
2609
|
+
let score = 2;
|
|
2610
|
+
if (hasMatchingKeyword(title || "", keywords) || hasMatchingKeyword(description || "", keywords)) {
|
|
2611
|
+
score += 3;
|
|
2612
|
+
} else {
|
|
2613
|
+
issues.push("Keywords defined but not used in title or description");
|
|
2614
|
+
}
|
|
2615
|
+
const stuffed = hasKeywordOveruse(title || "", keywords) || hasKeywordOveruse(description || "", keywords);
|
|
2616
|
+
if (stuffed) issues.push("Keyword stuffing detected \u2014 reduce repeated keyword usage");
|
|
2617
|
+
return { score, issues, stuffed };
|
|
2618
|
+
};
|
|
2619
|
+
var scoreFocusKeyword = (focusKeyword, title, description) => {
|
|
2620
|
+
const placement = getFocusKeywordPlacement(focusKeyword, title, description);
|
|
2621
|
+
if (!placement.hasFocusKeyword) {
|
|
2622
|
+
return { score: 0, issues: ["No focus keyword defined"], stuffed: false };
|
|
2623
|
+
}
|
|
2624
|
+
const issues = [];
|
|
2625
|
+
let score = 1;
|
|
2626
|
+
if (placement.atStartOfTitle) {
|
|
2627
|
+
score += 4;
|
|
2628
|
+
} else if (placement.inTitle) {
|
|
2629
|
+
score += 2;
|
|
2630
|
+
issues.push("Focus keyword is in the title but not at the start");
|
|
2631
|
+
} else if (placement.inDescription) {
|
|
2632
|
+
score += 1;
|
|
2633
|
+
issues.push("Focus keyword missing from meta title");
|
|
2634
|
+
} else {
|
|
2635
|
+
issues.push("Focus keyword missing from meta title and description");
|
|
2636
|
+
}
|
|
2637
|
+
if (placement.isStuffed)
|
|
2638
|
+
issues.push("Focus keyword appears too many times in title \u2014 avoid stuffing");
|
|
2639
|
+
return { score, issues, stuffed: placement.isStuffed };
|
|
2640
|
+
};
|
|
2641
|
+
var scoreKeywordOptimization = (keywords, focusKeyword, title, description) => {
|
|
2642
|
+
const arrayResult = scoreKeywordArray(keywords, title, description);
|
|
2643
|
+
const focusResult = scoreFocusKeyword(focusKeyword, title, description);
|
|
2644
|
+
let score = arrayResult.score + focusResult.score;
|
|
2645
|
+
if (arrayResult.stuffed || focusResult.stuffed) score = Math.max(0, score - 2);
|
|
2646
|
+
return { score: Math.min(score, 10), issues: [...arrayResult.issues, ...focusResult.issues] };
|
|
2647
|
+
};
|
|
2648
|
+
var calculateHealthScore = (doc) => {
|
|
2649
|
+
if (!doc.seo) {
|
|
2650
|
+
return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
|
|
2651
|
+
}
|
|
2652
|
+
const seo = doc.seo;
|
|
2653
|
+
const title = seo.title;
|
|
2654
|
+
const description = seo.description;
|
|
2655
|
+
const keywords = seo.keywords;
|
|
2656
|
+
const focusKeyword = seo.focusKeyword;
|
|
2657
|
+
const robots = seo.robots;
|
|
2658
|
+
const openGraph2 = seo.openGraph;
|
|
2659
|
+
const twitter2 = seo.twitter;
|
|
2660
|
+
let score = 0;
|
|
2661
|
+
const issues = [];
|
|
2662
|
+
const titleResult = scoreMetaTitle(title);
|
|
2663
|
+
score += titleResult.score;
|
|
2664
|
+
issues.push(...titleResult.issues);
|
|
2665
|
+
const descResult = scoreMetaDescription(description);
|
|
2666
|
+
score += descResult.score;
|
|
2667
|
+
issues.push(...descResult.issues);
|
|
2668
|
+
if (seo.metaImage) score += 10;
|
|
2669
|
+
else issues.push("Missing meta image");
|
|
2670
|
+
const keywordResult = scoreKeywordOptimization(keywords, focusKeyword, title, description);
|
|
2671
|
+
score += keywordResult.score;
|
|
2672
|
+
issues.push(...keywordResult.issues);
|
|
2673
|
+
if (robots && !robots.noIndex) score += 5;
|
|
2674
|
+
else if (!robots) score += 5;
|
|
2675
|
+
const ogResult = scoreOpenGraph(openGraph2);
|
|
2676
|
+
score += ogResult.score;
|
|
2677
|
+
issues.push(...ogResult.issues);
|
|
2678
|
+
const twResult = scoreTwitterCard(twitter2);
|
|
2679
|
+
score += twResult.score;
|
|
2680
|
+
issues.push(...twResult.issues);
|
|
2681
|
+
const hasMetaImage = !!seo.metaImage;
|
|
2682
|
+
const hasOgImage = !!(openGraph2 && openGraph2.image);
|
|
2683
|
+
const hasTwitterImage = !!(twitter2 && twitter2.image);
|
|
2684
|
+
if (hasMetaImage && hasOgImage && hasTwitterImage) {
|
|
2685
|
+
score += 5;
|
|
2686
|
+
} else {
|
|
2687
|
+
const missingImages = [];
|
|
2688
|
+
if (!hasMetaImage) missingImages.push("meta image");
|
|
2689
|
+
if (!hasOgImage) missingImages.push("OG image");
|
|
2690
|
+
if (!hasTwitterImage) missingImages.push("Twitter image");
|
|
2691
|
+
issues.push(`Missing images for full score: ${missingImages.join(", ")}`);
|
|
2692
|
+
}
|
|
2693
|
+
const status = getStatusCategory(score);
|
|
2694
|
+
return { score, status, issues };
|
|
2695
|
+
};
|
|
1128
2696
|
|
|
1129
2697
|
// src/index.ts
|
|
1130
2698
|
var src_default = plugin_default;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
2699
|
+
export {
|
|
2700
|
+
SchemaOrgJsonLdPreview as SchemaOrgDocumentJsonLdPreview,
|
|
2701
|
+
SchemaOrgJsonLdPreview,
|
|
2702
|
+
types as allSchemas,
|
|
2703
|
+
baseMeta as baseMetaSchema,
|
|
2704
|
+
calculateHealthScore,
|
|
2705
|
+
createSeoHealthPane,
|
|
2706
|
+
src_default as default,
|
|
2707
|
+
metaAttribute_default as metaAttributeSchema,
|
|
2708
|
+
metaTag_default as metaTagSchema,
|
|
2709
|
+
openGraph as openGraphSchema,
|
|
2710
|
+
robots_default as robotsSchema,
|
|
2711
|
+
seoFieldsSchema,
|
|
2712
|
+
twitter as twitterSchema
|
|
2713
|
+
};
|
|
1134
2714
|
//# sourceMappingURL=index.js.map
|