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.
Files changed (111) hide show
  1. package/README.md +440 -29
  2. package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.cts +78 -0
  3. package/dist/SchemaOrgJsonLdPreview-DJLkIPq4.d.ts +78 -0
  4. package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
  5. package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
  6. package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
  7. package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
  8. package/dist/{SeoPreview-WYH7NYNM.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
  9. package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
  10. package/dist/{SeoPreview-KEGGQSPJ.js → SeoPreview-UNQBKTQO.js} +14 -11
  11. package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
  12. package/dist/chunk-3OK3S2F7.cjs +673 -0
  13. package/dist/chunk-3OK3S2F7.cjs.map +1 -0
  14. package/dist/{chunk-GWPOJSGW.cjs → chunk-5XTQRILL.cjs} +1022 -307
  15. package/dist/chunk-5XTQRILL.cjs.map +1 -0
  16. package/dist/{chunk-2NMEKWO5.js → chunk-7N4MLTMR.js} +8 -15
  17. package/dist/chunk-7N4MLTMR.js.map +1 -0
  18. package/dist/{chunk-S367Y35J.cjs → chunk-A57XNQC7.cjs} +9 -20
  19. package/dist/chunk-A57XNQC7.cjs.map +1 -0
  20. package/dist/chunk-BWLJDK5J.js +624 -0
  21. package/dist/chunk-BWLJDK5J.js.map +1 -0
  22. package/dist/chunk-FUUWBQE2.cjs +195 -0
  23. package/dist/chunk-FUUWBQE2.cjs.map +1 -0
  24. package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
  25. package/dist/chunk-HHO2AKAP.js.map +1 -0
  26. package/dist/chunk-KIEO6QG3.js +195 -0
  27. package/dist/chunk-KIEO6QG3.js.map +1 -0
  28. package/dist/chunk-R2U7JF7U.cjs +624 -0
  29. package/dist/chunk-R2U7JF7U.cjs.map +1 -0
  30. package/dist/{chunk-HRKRV2X7.js → chunk-XZKQWV2H.js} +974 -148
  31. package/dist/chunk-XZKQWV2H.js.map +1 -0
  32. package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
  33. package/dist/chunk-Y46ACXM4.cjs.map +1 -0
  34. package/dist/chunk-Z5AOUU4H.js +673 -0
  35. package/dist/chunk-Z5AOUU4H.js.map +1 -0
  36. package/dist/cli.js +27 -27
  37. package/dist/{component-DQ4KuQQD.d.ts → component-CjT1hvxh.d.ts} +3 -27
  38. package/dist/{component-D1sBm6ts.d.cts → component-WYh0z8as.d.cts} +3 -27
  39. package/dist/define-cli.cjs +2 -4
  40. package/dist/define-cli.cjs.map +1 -1
  41. package/dist/define-cli.js +4 -4
  42. package/dist/define-cli.js.map +1 -1
  43. package/dist/head.cjs +14 -0
  44. package/dist/head.cjs.map +1 -0
  45. package/dist/head.d.cts +254 -0
  46. package/dist/head.d.ts +254 -0
  47. package/dist/head.js +14 -0
  48. package/dist/head.js.map +1 -0
  49. package/dist/index.cjs +1797 -230
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +11 -598
  52. package/dist/index.d.ts +11 -598
  53. package/dist/index.js +1777 -197
  54. package/dist/index.js.map +1 -1
  55. package/dist/next.cjs +210 -447
  56. package/dist/next.cjs.map +1 -1
  57. package/dist/next.d.cts +8 -200
  58. package/dist/next.d.ts +8 -200
  59. package/dist/next.js +200 -124
  60. package/dist/next.js.map +1 -1
  61. package/dist/plugin-DQdThyxG.d.ts +449 -0
  62. package/dist/plugin-DoGeZP79.d.cts +449 -0
  63. package/dist/schema/next.cjs +173 -319
  64. package/dist/schema/next.cjs.map +1 -1
  65. package/dist/schema/next.d.cts +4 -4
  66. package/dist/schema/next.d.ts +4 -4
  67. package/dist/schema/next.js +172 -8
  68. package/dist/schema/next.js.map +1 -1
  69. package/dist/schema.cjs +390 -489
  70. package/dist/schema.cjs.map +1 -1
  71. package/dist/schema.d.cts +26 -16
  72. package/dist/schema.d.ts +26 -16
  73. package/dist/schema.js +236 -13
  74. package/dist/schema.js.map +1 -1
  75. package/dist/server.cjs +149 -0
  76. package/dist/server.cjs.map +1 -0
  77. package/dist/server.d.cts +102 -0
  78. package/dist/server.d.ts +102 -0
  79. package/dist/server.js +149 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/{types-CeBULNFF.d.ts → types-BAdN1RMu.d.ts} +2 -2
  82. package/dist/{types-_wBLg6RV.d.cts → types-C5i3KMgs.d.cts} +2 -2
  83. package/dist/{types-9_wbCIi9.d.ts → types-DP-DiW6f.d.cts} +29 -2
  84. package/dist/{types-Bb49oRBV.d.cts → types-DRLUGLtX.d.ts} +29 -2
  85. package/dist/{types-B91ena4g.d.cts → types-DxlPXihz.d.cts} +8 -1
  86. package/dist/{types-B91ena4g.d.ts → types-DxlPXihz.d.ts} +8 -1
  87. package/package.json +25 -3
  88. package/dist/SeoHealthDashboard-IP5BJ322.cjs +0 -10
  89. package/dist/SeoHealthDashboard-IP5BJ322.cjs.map +0 -1
  90. package/dist/SeoHealthDashboard-PTUEVYZL.js +0 -4
  91. package/dist/SeoHealthDashboard-PTUEVYZL.js.map +0 -1
  92. package/dist/SeoHealthTool-CZZRMWQK.cjs +0 -14
  93. package/dist/SeoHealthTool-CZZRMWQK.cjs.map +0 -1
  94. package/dist/SeoHealthTool-IULT4G25.js +0 -12
  95. package/dist/SeoHealthTool-IULT4G25.js.map +0 -1
  96. package/dist/SeoPreview-KEGGQSPJ.js.map +0 -1
  97. package/dist/SeoPreview-WYH7NYNM.cjs.map +0 -1
  98. package/dist/chunk-2NMEKWO5.js.map +0 -1
  99. package/dist/chunk-5YRH47VQ.cjs +0 -2167
  100. package/dist/chunk-5YRH47VQ.cjs.map +0 -1
  101. package/dist/chunk-ARELX3GK.cjs +0 -689
  102. package/dist/chunk-ARELX3GK.cjs.map +0 -1
  103. package/dist/chunk-AW7OKBQQ.js +0 -2161
  104. package/dist/chunk-AW7OKBQQ.js.map +0 -1
  105. package/dist/chunk-FONJYVF2.js +0 -610
  106. package/dist/chunk-FONJYVF2.js.map +0 -1
  107. package/dist/chunk-GWPOJSGW.cjs.map +0 -1
  108. package/dist/chunk-HDZZQCH7.js.map +0 -1
  109. package/dist/chunk-HRKRV2X7.js.map +0 -1
  110. package/dist/chunk-S367Y35J.cjs.map +0 -1
  111. package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,45 +1,922 @@
1
- import { getMetaTitleValidationMessages, getMetaDescriptionValidationMessages, getMetaImageValidation, getOgTitleValidation, getOgDescriptionValidation, getOgImageValidation, getOgImageUrlValidation, getTwitterTitleValidation, getTwitterDescriptionValidation, getTwitterImageValidation, getTwitterImageUrlValidation } from './chunk-HDZZQCH7.js';
2
- import { __spreadValues, __spreadProps, __objRest } from './chunk-2NMEKWO5.js';
3
- import { lazy, createElement, Suspense, useMemo, useState, useEffect } from 'react';
4
- import { defineType, defineField, definePlugin, useFormValue, useClient } from 'sanity';
5
- import { Stack, Text } from '@sanity/ui';
6
- import { jsxs, jsx } from 'react/jsx-runtime';
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 parent = useFormValue([path[0]]);
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 = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
13
- const feedbackItems = useMemo(
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
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
855
+ const readability = useMemo3(() => analyzeReadability(value || ""), [value]);
856
+ return /* @__PURE__ */ jsxs6(Stack4, { space: 3, children: [
18
857
  renderDefault(props),
19
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
20
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
910
+ const seoParent = useFormValue6([path[0]]);
34
911
  const hasImage = !!(value == null ? void 0 : value.asset);
35
- const feedbackItems = useMemo(
912
+ const feedbackItems = useMemo4(
36
913
  () => getMetaImageValidation(hasImage, seoParent),
37
914
  [hasImage, seoParent]
38
915
  );
39
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
916
+ return /* @__PURE__ */ jsxs7(Stack5, { space: 3, children: [
40
917
  renderDefault(props),
41
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
42
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
1147
+ const parent = useFormValue8([path[0]]);
63
1148
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
64
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
65
- const rootDoc = (_a = useFormValue([])) != null ? _a : {};
66
- const client = useClient({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
67
- const [groqTitleSuffix, setGroqTitleSuffix] = useState("");
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
- useEffect(() => {
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 resolvedSuffix = useMemo(() => {
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 = useMemo(
1188
+ const feedbackItems = useMemo6(
88
1189
  () => getMetaTitleValidationMessages(value || "", keywords, isParentseoField, suffixLength),
89
1190
  [value, keywords, isParentseoField, suffixLength]
90
1191
  );
91
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1192
+ return /* @__PURE__ */ jsxs9(Stack7, { space: 3, children: [
92
1193
  renderDefault(props),
93
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
94
- /* @__PURE__ */ jsx(
95
- "div",
96
- {
97
- style: {
98
- minWidth: 10,
99
- height: 10,
100
- borderRadius: "50%",
101
- backgroundColor: item.color
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
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
106
- ] }, item.text)) })
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 and follow links on this page."
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 parent = useFormValue([path[0]]);
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 = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
238
- const feedbackItems = useMemo(
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__ */ jsxs(Stack, { space: 3, children: [
1398
+ return /* @__PURE__ */ jsxs10(Stack8, { space: 3, children: [
243
1399
  renderDefault(props),
244
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
245
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
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 = useMemo(
1438
+ const feedbackItems = useMemo8(
268
1439
  () => getOgImageValidation(hasImage, altText, seoParent),
269
1440
  [hasImage, altText, seoParent]
270
1441
  );
271
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1442
+ return /* @__PURE__ */ jsxs11(Stack9, { space: 3, children: [
272
1443
  renderDefault(props),
273
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
274
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
293
- const feedbackItems = useMemo(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
294
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
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__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
297
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 parent = useFormValue([path[0]]);
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 = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
318
- const feedbackItems = useMemo(
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__ */ jsxs(Stack, { space: 3, children: [
1506
+ return /* @__PURE__ */ jsxs13(Stack11, { space: 3, children: [
323
1507
  renderDefault(props),
324
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
325
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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
- // Can also wrap with a string input + preview
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
- // Can also wrap with a text area + preview
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 parent = useFormValue([path[0]]);
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 = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
462
- const feedbackItems = useMemo(
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__ */ jsxs(Stack, { space: 3, children: [
1669
+ return /* @__PURE__ */ jsxs14(Stack12, { space: 3, children: [
467
1670
  renderDefault(props),
468
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
469
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
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 = useMemo(
1709
+ const feedbackItems = useMemo12(
492
1710
  () => getTwitterImageValidation(hasImage, altText, seoParent),
493
1711
  [hasImage, altText, seoParent]
494
1712
  );
495
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1713
+ return /* @__PURE__ */ jsxs15(Stack13, { space: 3, children: [
496
1714
  renderDefault(props),
497
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
498
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 = useFormValue([path[0]]);
517
- const feedbackItems = useMemo(
1740
+ const seoParent = useFormValue15([path[0]]);
1741
+ const feedbackItems = useMemo13(
518
1742
  () => getTwitterImageUrlValidation(value, seoParent),
519
1743
  [value, seoParent]
520
1744
  );
521
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1745
+ return /* @__PURE__ */ jsxs16(Stack14, { space: 3, children: [
522
1746
  renderDefault(props),
523
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
524
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 parent = useFormValue([path[0]]);
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 = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
545
- const feedbackItems = useMemo(
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__ */ jsxs(Stack, { space: 3, children: [
1780
+ return /* @__PURE__ */ jsxs17(Stack15, { space: 3, children: [
550
1781
  renderDefault(props),
551
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
552
- /* @__PURE__ */ jsx(
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__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
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 defineType({
1811
+ return defineType2({
572
1812
  name: "twitter",
573
1813
  title: "X (Formerly Twitter)",
574
1814
  type: "object",
575
1815
  fields: [
576
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField({
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
- defineField(__spreadProps(__spreadValues({
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('./SeoPreview-KEGGQSPJ.js'));
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 defineType(__spreadProps(__spreadValues({
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
- defineField({
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
- ...typeof config.seoPreview === "boolean" && config.seoPreview || typeof config.seoPreview === "object" && !isEmpty(config.seoPreview) ? [
2045
+ ...shouldShowPreviewField(config.seoPreview) ? [
736
2046
  withGroup(
737
- defineField({
2047
+ defineField3({
738
2048
  name: "preview",
739
2049
  title: "SEO Preview",
740
2050
  type: "string",
741
2051
  components: { input: SeoPreviewWrapper },
742
- options: __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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: __spreadValues(__spreadValues(__spreadValues({}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.titleSuffix ? { titleSuffix: config.seoPreview.titleSuffix } : {}), typeof config.seoPreview === "object" && config.seoPreview && config.seoPreview.titleSuffixQuery ? { titleSuffixQuery: config.seoPreview.titleSuffixQuery } : {}),
2067
+ options: getTitleFieldOptions(config),
760
2068
  hidden: getFieldHiddenFunction("title", config)
761
2069
  })),
762
2070
  fieldGroupMap
763
2071
  ),
764
2072
  withGroup(
765
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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 defineType({
2171
+ return defineType4({
832
2172
  name: "baseMeta",
833
2173
  title: "Basic Meta Settings",
834
2174
  type: "object",
835
2175
  fields: [
836
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- defineField(__spreadProps(__spreadValues({
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
- var metaAttribute_default = defineType({
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
- defineField({
2277
+ defineField6({
900
2278
  name: "key",
901
2279
  title: "Attribute Name",
902
2280
  type: "string"
903
2281
  }),
904
- defineField({
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
- defineField({
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
- defineField({
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
- var metaTag_default = defineType({
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
- var robots_default = defineType({
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
- defineField({
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
- defineField({
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 and follow links on this page."
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: (_d = (_c = cfg == null ? void 0 : cfg.tool) == null ? void 0 : _c.name) != null ? _d : "seo-health-dashboard",
1025
- icon: (_e = cfg == null ? void 0 : cfg.content) == null ? void 0 : _e.icon,
1026
- title: (_f = cfg == null ? void 0 : cfg.content) == null ? void 0 : _f.title,
1027
- description: (_g = cfg == null ? void 0 : cfg.content) == null ? void 0 : _g.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: (_h = cfg == null ? void 0 : cfg.query) == null ? void 0 : _h.types,
1031
- queryRequireSeo: (_i = cfg == null ? void 0 : cfg.query) == null ? void 0 : _i.requireSeo,
1032
- queryGroq: (_j = cfg == null ? void 0 : cfg.query) == null ? void 0 : _j.groq,
1033
- apiVersion: (_k = cfg == null ? void 0 : cfg.apiVersion) != null ? _k : rootApiVersion,
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: (_l = cfg == null ? void 0 : cfg.content) == null ? void 0 : _l.loadingLicense,
1040
- loadingDocuments: (_m = cfg == null ? void 0 : cfg.content) == null ? void 0 : _m.loadingDocuments,
1041
- noDocuments: (_n = cfg == null ? void 0 : cfg.content) == null ? void 0 : _n.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: (_o = cfg == null ? void 0 : cfg.compactStats) != null ? _o : false,
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 { healthDashboard = true } = config;
1062
- const dash = resolveDashboardConfig(healthDashboard, config.apiVersion);
1063
- const LazySeoHealthTool = lazy(() => import('./SeoHealthTool-IULT4G25.js'));
1064
- const BoundSeoHealthTool = () => createElement(
1065
- Suspense,
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
- createElement(LazySeoHealthTool, {
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(config)
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
- var LazySeoHealthDashboard = lazy(() => import('./SeoHealthDashboard-PTUEVYZL.js'));
1111
- function createSeoHealthPane(optionsOrS, optionsWhenS) {
1112
- const S = optionsOrS;
1113
- const _a = optionsWhenS != null ? optionsWhenS : {}, { query, openInPane = true, title: paneTitle } = _a, rest = __objRest(_a, ["query", "openInPane", "title"]);
1114
- const SeoHealthPane = () => /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
1115
- LazySeoHealthDashboard,
1116
- __spreadValues({
1117
- customQuery: query,
1118
- openInPane,
1119
- title: paneTitle
1120
- }, rest)
1121
- ) });
1122
- SeoHealthPane.displayName = "SeoHealthPane";
1123
- return S.component(SeoHealthPane).title(paneTitle != null ? paneTitle : "SEO Health").child((docId, { params }) => {
1124
- const builder = S.document().documentId(docId);
1125
- return (params == null ? void 0 : params.type) ? builder.schemaType(params.type) : builder;
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
- export { types as allSchemas, baseMeta as baseMetaSchema, createSeoHealthPane, src_default as default, metaAttribute_default as metaAttributeSchema, metaTag_default as metaTagSchema, openGraph as openGraphSchema, robots_default as robotsSchema, seoFieldsSchema, twitter as twitterSchema };
1133
- //# sourceMappingURL=index.js.map
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