sanity-plugin-seofields 1.8.0 → 1.10.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 (119) hide show
  1. package/README.md +508 -26
  2. package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
  3. package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
  4. package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
  5. package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
  6. package/dist/{SeoPreview-3EXR6FD4.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
  7. package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
  8. package/dist/{SeoPreview-4PPAF4NO.js → SeoPreview-UNQBKTQO.js} +14 -11
  9. package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
  10. package/dist/{chunk-B5IVI5LP.cjs → chunk-5XTQRILL.cjs} +286 -236
  11. package/dist/chunk-5XTQRILL.cjs.map +1 -0
  12. package/dist/{chunk-254YHUN3.cjs → chunk-7N4MLTMR.js} +8 -6
  13. package/dist/chunk-7N4MLTMR.js.map +1 -0
  14. package/dist/{chunk-DDAAVRWG.js → chunk-A57XNQC7.cjs} +9 -4
  15. package/dist/chunk-A57XNQC7.cjs.map +1 -0
  16. package/dist/chunk-BWLJDK5J.js +624 -0
  17. package/dist/chunk-BWLJDK5J.js.map +1 -0
  18. package/dist/chunk-CRCXC45D.js +56 -0
  19. package/dist/chunk-CRCXC45D.js.map +1 -0
  20. package/dist/chunk-EUG4MNRC.cjs +713 -0
  21. package/dist/chunk-EUG4MNRC.cjs.map +1 -0
  22. package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
  23. package/dist/chunk-HHO2AKAP.js.map +1 -0
  24. package/dist/chunk-NLEB47UV.js +713 -0
  25. package/dist/chunk-NLEB47UV.js.map +1 -0
  26. package/dist/chunk-OULUDJPI.cjs +254 -0
  27. package/dist/chunk-OULUDJPI.cjs.map +1 -0
  28. package/dist/chunk-R2U7JF7U.cjs +624 -0
  29. package/dist/chunk-R2U7JF7U.cjs.map +1 -0
  30. package/dist/chunk-XD4HKLEA.js +254 -0
  31. package/dist/chunk-XD4HKLEA.js.map +1 -0
  32. package/dist/chunk-XYAZJ3WX.cjs +56 -0
  33. package/dist/chunk-XYAZJ3WX.cjs.map +1 -0
  34. package/dist/{chunk-VR44E62I.js → chunk-XZKQWV2H.js} +204 -33
  35. package/dist/chunk-XZKQWV2H.js.map +1 -0
  36. package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
  37. package/dist/chunk-Y46ACXM4.cjs.map +1 -0
  38. package/dist/cli.js +27 -27
  39. package/dist/{component-DEXwtemT.d.ts → component-CjT1hvxh.d.ts} +2 -2
  40. package/dist/{component-BzI-LHPw.d.cts → component-WYh0z8as.d.cts} +2 -2
  41. package/dist/define-cli.cjs +2 -4
  42. package/dist/define-cli.cjs.map +1 -1
  43. package/dist/define-cli.js +4 -4
  44. package/dist/define-cli.js.map +1 -1
  45. package/dist/head.cjs +22 -0
  46. package/dist/head.cjs.map +1 -0
  47. package/dist/head.d.cts +314 -0
  48. package/dist/head.d.ts +314 -0
  49. package/dist/head.js +22 -0
  50. package/dist/head.js.map +1 -0
  51. package/dist/hreflang-_VbSuWYD.d.cts +29 -0
  52. package/dist/hreflang-_VbSuWYD.d.ts +29 -0
  53. package/dist/index.cjs +1072 -368
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +7 -374
  56. package/dist/index.d.ts +7 -374
  57. package/dist/index.js +1015 -288
  58. package/dist/index.js.map +1 -1
  59. package/dist/next.cjs +212 -455
  60. package/dist/next.cjs.map +1 -1
  61. package/dist/next.d.cts +9 -206
  62. package/dist/next.d.ts +9 -206
  63. package/dist/next.js +189 -111
  64. package/dist/next.js.map +1 -1
  65. package/dist/plugin-HFuWZsJu.d.cts +516 -0
  66. package/dist/plugin-oQUCim56.d.ts +516 -0
  67. package/dist/schema/next.cjs +173 -327
  68. package/dist/schema/next.cjs.map +1 -1
  69. package/dist/schema/next.d.cts +4 -4
  70. package/dist/schema/next.d.ts +4 -4
  71. package/dist/schema/next.js +172 -8
  72. package/dist/schema/next.js.map +1 -1
  73. package/dist/schema.cjs +376 -517
  74. package/dist/schema.cjs.map +1 -1
  75. package/dist/schema.d.cts +5 -5
  76. package/dist/schema.d.ts +5 -5
  77. package/dist/schema.js +216 -11
  78. package/dist/schema.js.map +1 -1
  79. package/dist/server.cjs +149 -0
  80. package/dist/server.cjs.map +1 -0
  81. package/dist/server.d.cts +100 -0
  82. package/dist/server.d.ts +100 -0
  83. package/dist/server.js +149 -0
  84. package/dist/server.js.map +1 -0
  85. package/dist/{types-CW8qFdAn.d.ts → types-BAdN1RMu.d.ts} +2 -2
  86. package/dist/{types-B_vjMPtM.d.cts → types-C5i3KMgs.d.cts} +2 -2
  87. package/dist/{types-BXSCbl6p.d.cts → types-DP-DiW6f.d.cts} +2 -2
  88. package/dist/{types-C19cQx5D.d.ts → types-DRLUGLtX.d.ts} +2 -2
  89. package/dist/{types-yVmQfby9.d.cts → types-DxlPXihz.d.cts} +1 -1
  90. package/dist/{types-yVmQfby9.d.ts → types-DxlPXihz.d.ts} +1 -1
  91. package/package.json +25 -4
  92. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +0 -4
  93. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +0 -1
  94. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +0 -10
  95. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +0 -1
  96. package/dist/SeoHealthTool-2KKQJLJL.cjs +0 -11
  97. package/dist/SeoHealthTool-2KKQJLJL.cjs.map +0 -1
  98. package/dist/SeoHealthTool-3CUKGADZ.js +0 -5
  99. package/dist/SeoHealthTool-3CUKGADZ.js.map +0 -1
  100. package/dist/SeoPreview-3EXR6FD4.cjs.map +0 -1
  101. package/dist/SeoPreview-4PPAF4NO.js.map +0 -1
  102. package/dist/chunk-254YHUN3.cjs.map +0 -1
  103. package/dist/chunk-27XSYENH.js +0 -2215
  104. package/dist/chunk-27XSYENH.js.map +0 -1
  105. package/dist/chunk-B5IVI5LP.cjs.map +0 -1
  106. package/dist/chunk-BNXMFD3T.cjs +0 -431
  107. package/dist/chunk-BNXMFD3T.cjs.map +0 -1
  108. package/dist/chunk-DDAAVRWG.js.map +0 -1
  109. package/dist/chunk-HDZZQCH7.js.map +0 -1
  110. package/dist/chunk-P6IOWIO2.cjs +0 -480
  111. package/dist/chunk-P6IOWIO2.cjs.map +0 -1
  112. package/dist/chunk-UOCZFYYP.js +0 -407
  113. package/dist/chunk-UOCZFYYP.js.map +0 -1
  114. package/dist/chunk-VR44E62I.js.map +0 -1
  115. package/dist/chunk-WKXHX3GO.js +0 -424
  116. package/dist/chunk-WKXHX3GO.js.map +0 -1
  117. package/dist/chunk-XZDLJ2N6.cjs +0 -2224
  118. package/dist/chunk-XZDLJ2N6.cjs.map +0 -1
  119. package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,59 @@
1
- import { tokenizeHtml } from './chunk-VR44E62I.js';
2
- export { SchemaOrgJsonLdPreview as SchemaOrgDocumentJsonLdPreview, SchemaOrgJsonLdPreview } from './chunk-VR44E62I.js';
3
- import { getMetaTitleValidationMessages, getMetaDescriptionValidationMessages, getMetaImageValidation, getOgTitleValidation, getOgDescriptionValidation, getOgImageValidation, getOgImageUrlValidation, getTwitterTitleValidation, getTwitterDescriptionValidation, getTwitterImageValidation, getTwitterImageUrlValidation } from './chunk-HDZZQCH7.js';
4
- import { createSeoPublishGateAction, buildGEOProChecks, GEOChecklistProChecks, GEOLockedTeaser } from './chunk-WKXHX3GO.js';
5
- export { createSeoHealthPane } from './chunk-WKXHX3GO.js';
6
- import './chunk-27XSYENH.js';
7
- import { __spreadValues, __spreadProps } from './chunk-DDAAVRWG.js';
8
- import { lazy, createElement, Suspense, useMemo, useState, useEffect } from 'react';
9
- import { defineType, defineField, definePlugin, useFormValue, useClient, useColorScheme } from 'sanity';
10
- import { jsxs, jsx } from 'react/jsx-runtime';
11
- import { Stack, Text, Card, Flex, Button, Box } from '@sanity/ui';
1
+ import {
2
+ buildHreflangs
3
+ } from "./chunk-CRCXC45D.js";
4
+ import {
5
+ analyzeReadability,
6
+ generateSeoText,
7
+ pickTestOutput
8
+ } from "./chunk-NLEB47UV.js";
9
+ import {
10
+ SchemaOrgJsonLdPreview,
11
+ tokenizeHtml
12
+ } from "./chunk-XZKQWV2H.js";
13
+ import {
14
+ getFocusKeywordPlacement,
15
+ getMetaDescriptionValidationMessages,
16
+ getMetaImageValidation,
17
+ getMetaTitleValidationMessages,
18
+ getOgDescriptionValidation,
19
+ getOgImageUrlValidation,
20
+ getOgImageValidation,
21
+ getOgTitleValidation,
22
+ getTwitterDescriptionValidation,
23
+ getTwitterImageUrlValidation,
24
+ getTwitterImageValidation,
25
+ getTwitterTitleValidation,
26
+ hasKeywordOveruse,
27
+ hasMatchingKeyword
28
+ } from "./chunk-HHO2AKAP.js";
29
+ import {
30
+ __spreadProps,
31
+ __spreadValues
32
+ } from "./chunk-7N4MLTMR.js";
12
33
 
34
+ // src/plugin.ts
35
+ import { createElement as createElement2, lazy as lazy2, Suspense as Suspense2 } from "react";
36
+ import { definePlugin } from "sanity";
37
+
38
+ // src/actions/SeoPublishGateAction.tsx
39
+ import { createSeoPublishGateAction } from "seofields-pro";
40
+
41
+ // src/schemas/index.ts
42
+ import { createElement, lazy, Suspense } from "react";
43
+ import {
44
+ defineField as defineField3,
45
+ defineType as defineType3
46
+ } from "sanity";
47
+
48
+ // src/components/geo/GEOChecklist.tsx
49
+ import { useMemo, useState } from "react";
50
+ import { useFormValue } from "sanity";
51
+ import {
52
+ buildGEOProChecks,
53
+ GEOChecklistProChecks,
54
+ GEOLockedTeaser
55
+ } from "seofields-pro";
56
+ import { jsx, jsxs } from "react/jsx-runtime";
13
57
  var GEOChecklist = (props) => {
14
58
  var _a;
15
59
  const { path, schemaType } = props;
@@ -339,23 +383,174 @@ function CheckRow({ check }) {
339
383
  );
340
384
  }
341
385
  var GEOChecklist_default = GEOChecklist;
386
+
387
+ // src/components/meta/FocusKeywordInput.tsx
388
+ import { Stack, Text as Text2 } from "@sanity/ui";
389
+ import { useMemo as useMemo2 } from "react";
390
+ import { PatchEvent, set, useFormValue as useFormValue3 } from "sanity";
391
+
392
+ // src/components/ai/SeoGenButton.tsx
393
+ import { SparklesIcon } from "@sanity/icons";
394
+ import { Button, Card, Flex, Text } from "@sanity/ui";
395
+ import { useCallback, useEffect, useRef, useState as useState2 } from "react";
396
+ import { useFormValue as useFormValue2, useWorkspace } from "sanity";
397
+
398
+ // src/utils/extractDocText.ts
399
+ function extractBlockText(block) {
400
+ if (block._type === "block" && Array.isArray(block.children)) {
401
+ return block.children.map((s) => {
402
+ var _a;
403
+ return (_a = s.text) != null ? _a : "";
404
+ }).join("");
405
+ }
406
+ const parts = [];
407
+ for (const key of Object.keys(block)) {
408
+ if (key === "_type" || key === "_key") continue;
409
+ const val = block[key];
410
+ if (typeof val === "string" && val.trim()) parts.push(val.trim());
411
+ else if (Array.isArray(val)) parts.push(extractArrayText(val));
412
+ }
413
+ return parts.filter(Boolean).join(" ");
414
+ }
415
+ function extractArrayText(arr) {
416
+ return arr.map((b) => extractBlockText(b)).filter(Boolean).join("\n");
417
+ }
418
+ function extractFieldText(value) {
419
+ if (typeof value === "string") return value;
420
+ if (!Array.isArray(value)) return "";
421
+ return extractArrayText(value).replace(/\n{3,}/g, "\n\n").trim();
422
+ }
423
+ function extractDocText(doc, bodyField) {
424
+ const fields = [bodyField].flat();
425
+ const text = fields.map((f) => extractFieldText(doc[f])).filter(Boolean).join("\n\n");
426
+ return text.slice(0, 4e3);
427
+ }
428
+ function resolveContentFields(content, docType) {
429
+ var _a;
430
+ if (!content || typeof content === "string" || Array.isArray(content)) return content != null ? content : "body";
431
+ const key = docType && docType in content ? docType : "default";
432
+ return (_a = content[key]) != null ? _a : "body";
433
+ }
434
+
435
+ // src/components/ai/SeoGenButton.tsx
436
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
437
+ function SeoGenButton({
438
+ field,
439
+ ai,
440
+ seoFieldPath,
441
+ onGenerate
442
+ }) {
443
+ var _a, _b, _c, _d, _e, _f, _g;
444
+ const [state, setState] = useState2("idle");
445
+ const [errorMsg, setErrorMsg] = useState2(null);
446
+ const abortRef = useRef(null);
447
+ const cooldownRef = useRef(null);
448
+ const rootDoc = useFormValue2([]);
449
+ const workspace = useWorkspace();
450
+ const seoObj = useFormValue2([seoFieldPath]);
451
+ const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
452
+ const keywords = (_b = seoObj == null ? void 0 : seoObj.keywords) != null ? _b : [];
453
+ const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
454
+ const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
455
+ const meta = field === "focusKeyword" ? {
456
+ title: (_c = seoObj == null ? void 0 : seoObj.title) != null ? _c : void 0,
457
+ description: (_d = seoObj == null ? void 0 : seoObj.description) != null ? _d : void 0,
458
+ slug: (_f = (_e = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _e.current) != null ? _f : void 0
459
+ } : void 0;
460
+ const handleGenerate = useCallback(async () => {
461
+ var _a2;
462
+ if (!ai) return;
463
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
464
+ if (cooldownRef.current) clearTimeout(cooldownRef.current);
465
+ setState("loading");
466
+ setErrorMsg(null);
467
+ try {
468
+ let result;
469
+ if (ai.testMode) {
470
+ await new Promise((r) => setTimeout(r, 400));
471
+ result = pickTestOutput(field);
472
+ } else {
473
+ const controller = new AbortController();
474
+ abortRef.current = controller;
475
+ result = await generateSeoText(field, content, focusKeyword, keywords, {
476
+ config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
477
+ signal: controller.signal,
478
+ meta
479
+ });
480
+ }
481
+ onGenerate(result);
482
+ setState("cooldown");
483
+ cooldownRef.current = setTimeout(() => setState("idle"), 2e3);
484
+ } catch (err) {
485
+ if (err.name === "AbortError") {
486
+ setState("idle");
487
+ return;
488
+ }
489
+ setErrorMsg(err instanceof Error ? err.message : "Generation failed.");
490
+ setState("error");
491
+ }
492
+ }, [ai, field, content, focusKeyword, keywords, meta, onGenerate, workspace.projectId]);
493
+ useEffect(() => {
494
+ return () => {
495
+ var _a2;
496
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
497
+ if (cooldownRef.current) clearTimeout(cooldownRef.current);
498
+ };
499
+ }, []);
500
+ const isConfigured = Boolean(
501
+ (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"
502
+ );
503
+ if (!isConfigured) return null;
504
+ const isLoading = state === "loading";
505
+ const isTestMode = Boolean(ai == null ? void 0 : ai.testMode);
506
+ let buttonText = "Generate with AI";
507
+ if (isLoading) buttonText = "Generating\u2026";
508
+ else if (isTestMode) buttonText = "Generate (test)";
509
+ const isFullWidth = ((_g = ai == null ? void 0 : ai.buttonWidth) != null ? _g : "full") === "full";
510
+ return /* @__PURE__ */ jsxs2(Flex, { direction: "column", gap: 1, align: isFullWidth ? "stretch" : "flex-start", children: [
511
+ /* @__PURE__ */ jsx2(
512
+ Button,
513
+ {
514
+ mode: "ghost",
515
+ tone: "primary",
516
+ paddingX: isFullWidth ? 2 : 3,
517
+ paddingY: 3,
518
+ fontSize: 1,
519
+ icon: SparklesIcon,
520
+ text: buttonText,
521
+ onClick: handleGenerate,
522
+ disabled: isLoading || state === "cooldown"
523
+ }
524
+ ),
525
+ !content && /* @__PURE__ */ jsxs2(Text, { size: 0, muted: true, children: [
526
+ "(Set ",
527
+ /* @__PURE__ */ jsx2("code", { children: "ai.content" }),
528
+ " in plugin config for better results)"
529
+ ] }),
530
+ state === "error" && errorMsg && /* @__PURE__ */ jsx2(Card, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx2(Text, { size: 1, children: errorMsg }) })
531
+ ] });
532
+ }
533
+
534
+ // src/components/meta/FocusKeywordInput.tsx
535
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
342
536
  var FocusKeywordInput = (props) => {
343
537
  var _a;
344
- const { value, renderDefault, path } = props;
345
- const parent = useFormValue([path[0]]);
346
- const rootDoc = useFormValue([]);
347
- const feedbackItems = useMemo(() => {
348
- var _a2, _b;
538
+ const { value, renderDefault, path, schemaType, onChange } = props;
539
+ const { options } = schemaType;
540
+ const parent = useFormValue3([path[0]]);
541
+ const rootDoc = useFormValue3([]);
542
+ const feedbackItems = useMemo2(() => {
543
+ var _a2;
349
544
  if (!value) return [];
350
- const kw = value.toLowerCase();
351
- const title = ((parent == null ? void 0 : parent.title) || "").toLowerCase();
352
- const description = ((parent == null ? void 0 : parent.description) || "").toLowerCase();
545
+ const title = (parent == null ? void 0 : parent.title) || "";
546
+ const description = (parent == null ? void 0 : parent.description) || "";
353
547
  const slug = ((_a2 = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a2.current) || "";
548
+ const placement = getFocusKeywordPlacement(value, title, description);
354
549
  const items = [];
355
550
  if (title) {
356
- if (title.startsWith(kw)) {
551
+ if (placement.atStartOfTitle) {
357
552
  items.push({ text: "Keyword at start of title \u2014 excellent", color: "green" });
358
- } else if (title.includes(kw)) {
553
+ } else if (placement.inTitle) {
359
554
  items.push({
360
555
  text: "Keyword in title \u2014 move it to the start for more prominence",
361
556
  color: "orange"
@@ -365,14 +560,14 @@ var FocusKeywordInput = (props) => {
365
560
  }
366
561
  }
367
562
  if (description) {
368
- if (description.includes(kw)) {
563
+ if (placement.inDescription) {
369
564
  items.push({ text: "Keyword in meta description", color: "green" });
370
565
  } else {
371
566
  items.push({ text: "Not in description \u2014 include it naturally", color: "orange" });
372
567
  }
373
568
  }
374
569
  if (slug) {
375
- const kwSlug = kw.replace(/\s+/g, "-");
570
+ const kwSlug = value.toLowerCase().replace(/\s+/g, "-");
376
571
  if (slug.includes(kwSlug)) {
377
572
  items.push({ text: "Keyword in URL slug", color: "green" });
378
573
  } else {
@@ -382,8 +577,7 @@ var FocusKeywordInput = (props) => {
382
577
  });
383
578
  }
384
579
  }
385
- const safeKw = kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
386
- if (((_b = title.match(new RegExp(safeKw, "g"))) != null ? _b : []).length > 2) {
580
+ if (placement.isStuffed) {
387
581
  items.push({
388
582
  text: "Keyword appears too many times in title \u2014 avoid stuffing",
389
583
  color: "red"
@@ -391,10 +585,19 @@ var FocusKeywordInput = (props) => {
391
585
  }
392
586
  return items;
393
587
  }, [value, parent == null ? void 0 : parent.title, parent == null ? void 0 : parent.description, (_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current]);
394
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
588
+ return /* @__PURE__ */ jsxs3(Stack, { space: 3, children: [
395
589
  renderDefault(props),
396
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
397
- /* @__PURE__ */ jsx(
590
+ /* @__PURE__ */ jsx3(
591
+ SeoGenButton,
592
+ {
593
+ field: "focusKeyword",
594
+ ai: options == null ? void 0 : options.ai,
595
+ seoFieldPath: String(path[0]),
596
+ onGenerate: (v) => onChange(PatchEvent.from(set(v)))
597
+ }
598
+ ),
599
+ /* @__PURE__ */ jsx3(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
600
+ /* @__PURE__ */ jsx3(
398
601
  "div",
399
602
  {
400
603
  style: {
@@ -405,87 +608,357 @@ var FocusKeywordInput = (props) => {
405
608
  }
406
609
  }
407
610
  ),
408
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
611
+ /* @__PURE__ */ jsx3(Text2, { weight: "bold", muted: true, size: 14, children: item.text })
409
612
  ] }, item.text)) })
410
613
  ] });
411
614
  };
412
615
  var FocusKeywordInput_default = FocusKeywordInput;
413
616
 
414
- // src/utils/readability.ts
415
- function countSyllables(word) {
617
+ // src/components/meta/HreflangInput.tsx
618
+ import { TranslateIcon } from "@sanity/icons";
619
+ import { Button as Button2, Card as Card2, Flex as Flex2, Stack as Stack2, Text as Text3 } from "@sanity/ui";
620
+ import { useCallback as useCallback2, useState as useState3 } from "react";
621
+ import { PatchEvent as PatchEvent2, set as set2, useClient, useFormValue as useFormValue4 } from "sanity";
622
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
623
+ var DEFAULT_LOCALE_FIELD = "language";
624
+ var DEFAULT_API_VERSION = "2024-01-01";
625
+ function safeField(name) {
626
+ return name && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : DEFAULT_LOCALE_FIELD;
627
+ }
628
+ var HreflangInput = (props) => {
629
+ const { schemaType, onChange, renderDefault } = props;
630
+ const { options } = schemaType;
631
+ const { hreflang, baseUrl, apiVersion } = options != null ? options : {};
632
+ const rootDoc = useFormValue4([]);
633
+ const client = useClient({ apiVersion: apiVersion != null ? apiVersion : DEFAULT_API_VERSION });
634
+ const [state, setState] = useState3("idle");
635
+ const [message, setMessage] = useState3(null);
636
+ const localeField = safeField(hreflang == null ? void 0 : hreflang.localeField);
637
+ const handleSync = useCallback2(async () => {
638
+ var _a;
639
+ const rawId = rootDoc == null ? void 0 : rootDoc._id;
640
+ if (!rawId) {
641
+ setState("error");
642
+ setMessage("Save the document once before syncing translations.");
643
+ return;
644
+ }
645
+ setState("loading");
646
+ setMessage(null);
647
+ try {
648
+ const publishedId = rawId.replace(/^drafts\./, "");
649
+ const query = `*[_type == "translation.metadata" && references($id)][0].translations[].value->{
650
+ "language": ${localeField},
651
+ "slug": slug.current
652
+ }`;
653
+ const translations = (_a = await client.fetch(query, { id: publishedId })) != null ? _a : [];
654
+ const entries = buildHreflangs(translations, {
655
+ baseUrl,
656
+ resolvePath: hreflang == null ? void 0 : hreflang.resolvePath
657
+ });
658
+ if (entries.length === 0) {
659
+ setState("error");
660
+ setMessage("No translations with a resolvable URL were found for this document.");
661
+ return;
662
+ }
663
+ const items = entries.map((e) => ({
664
+ _type: "hreflangEntry",
665
+ _key: e.locale,
666
+ locale: e.locale,
667
+ url: e.url
668
+ }));
669
+ onChange(PatchEvent2.from(set2(items)));
670
+ setState("done");
671
+ setMessage(
672
+ `Synced ${items.length} alternate${items.length === 1 ? "" : "s"} from translations.`
673
+ );
674
+ } catch (err) {
675
+ setState("error");
676
+ setMessage(err instanceof Error ? err.message : "Failed to sync translations.");
677
+ }
678
+ }, [rootDoc == null ? void 0 : rootDoc._id, client, localeField, baseUrl, hreflang, onChange]);
679
+ return /* @__PURE__ */ jsxs4(Stack2, { space: 3, children: [
680
+ /* @__PURE__ */ jsx4(Flex2, { children: /* @__PURE__ */ jsx4(
681
+ Button2,
682
+ {
683
+ mode: "ghost",
684
+ tone: "primary",
685
+ fontSize: 1,
686
+ icon: TranslateIcon,
687
+ text: state === "loading" ? "Syncing\u2026" : "Sync from translations",
688
+ onClick: handleSync,
689
+ disabled: state === "loading"
690
+ }
691
+ ) }),
692
+ message && /* @__PURE__ */ jsx4(Card2, { padding: 2, radius: 2, tone: state === "error" ? "critical" : "positive", children: /* @__PURE__ */ jsx4(Text3, { size: 1, children: message }) }),
693
+ renderDefault(props)
694
+ ] });
695
+ };
696
+ var HreflangInput_default = HreflangInput;
697
+
698
+ // src/components/meta/KeywordsInput.tsx
699
+ import { Stack as Stack4 } from "@sanity/ui";
700
+ import { PatchEvent as PatchEvent3, set as set3 } from "sanity";
701
+
702
+ // src/components/ai/KeywordSuggestions.tsx
703
+ import { SparklesIcon as SparklesIcon2 } from "@sanity/icons";
704
+ import { Badge, Box, Button as Button3, Card as Card3, Flex as Flex3, Spinner, Stack as Stack3, Text as Text4 } from "@sanity/ui";
705
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState4 } from "react";
706
+ import { useFormValue as useFormValue5, useWorkspace as useWorkspace2 } from "sanity";
707
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
708
+ function KeywordSuggestions({
709
+ ai,
710
+ seoFieldPath,
711
+ existing,
712
+ onAdd,
713
+ onAddAll
714
+ }) {
416
715
  var _a;
417
- const w = word.toLowerCase().replace(/[^a-z]/g, "");
418
- if (w.length <= 3) return 1;
419
- const groups = (_a = w.match(/[aeiouy]+/g)) != null ? _a : [];
420
- let count = groups.length;
421
- if (w.endsWith("e") && w.length > 1 && !/[aeiouy]/.test(w[w.length - 2])) {
422
- count = Math.max(1, count - 1);
716
+ const [panelState, setPanelState] = useState4("idle");
717
+ const [suggestions, setSuggestions] = useState4([]);
718
+ const [errorMsg, setErrorMsg] = useState4(null);
719
+ const abortRef = useRef2(null);
720
+ const rootDoc = useFormValue5([]);
721
+ const workspace = useWorkspace2();
722
+ const seoObj = useFormValue5([seoFieldPath]);
723
+ const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
724
+ const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
725
+ const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
726
+ const handleSuggest = useCallback3(async () => {
727
+ var _a2;
728
+ if (!ai) return;
729
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
730
+ abortRef.current = new AbortController();
731
+ setPanelState("loading");
732
+ setErrorMsg(null);
733
+ setSuggestions([]);
734
+ try {
735
+ let csv;
736
+ if (ai.testMode) {
737
+ await new Promise((r) => setTimeout(r, 400));
738
+ csv = pickTestOutput("keywords");
739
+ } else {
740
+ csv = await generateSeoText("keywords", content, focusKeyword, existing, {
741
+ config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
742
+ signal: abortRef.current.signal
743
+ });
744
+ }
745
+ const parsed = csv.split(",").map((k) => k.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
746
+ if (parsed.length === 0) {
747
+ setErrorMsg("No keywords returned. Try again.");
748
+ setPanelState("error");
749
+ return;
750
+ }
751
+ setSuggestions(parsed);
752
+ setPanelState("suggestions");
753
+ } catch (err) {
754
+ if (err.name === "AbortError") {
755
+ setPanelState("idle");
756
+ return;
757
+ }
758
+ setErrorMsg(err instanceof Error ? err.message : "Keyword generation failed.");
759
+ setPanelState("error");
760
+ }
761
+ }, [ai, content, focusKeyword, existing, workspace.projectId]);
762
+ const handleAdd = useCallback3(
763
+ (kw) => {
764
+ onAdd(kw);
765
+ setSuggestions((prev) => prev.filter((s) => s !== kw));
766
+ if (suggestions.length <= 1) setPanelState("idle");
767
+ },
768
+ [onAdd, suggestions.length]
769
+ );
770
+ const handleAddAll = useCallback3(() => {
771
+ const toAdd = suggestions.filter((kw) => !existing.includes(kw));
772
+ if (toAdd.length > 0) onAddAll(toAdd);
773
+ setSuggestions([]);
774
+ setPanelState("idle");
775
+ }, [suggestions, existing, onAddAll]);
776
+ const handleDismiss = useCallback3(() => {
777
+ var _a2;
778
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
779
+ setSuggestions([]);
780
+ setPanelState("idle");
781
+ setErrorMsg(null);
782
+ }, []);
783
+ useEffect2(() => {
784
+ return () => {
785
+ var _a2;
786
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
787
+ };
788
+ }, []);
789
+ const isConfigured = Boolean(
790
+ (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"
791
+ );
792
+ if (!isConfigured) return null;
793
+ if (panelState === "idle" || panelState === "error") {
794
+ return /* @__PURE__ */ jsxs5(Stack3, { space: 2, children: [
795
+ /* @__PURE__ */ jsx5(
796
+ Button3,
797
+ {
798
+ mode: "ghost",
799
+ tone: "primary",
800
+ paddingX: 2,
801
+ paddingY: 3,
802
+ fontSize: 1,
803
+ icon: SparklesIcon2,
804
+ text: (ai == null ? void 0 : ai.testMode) ? "Suggest Keywords (test)" : "Suggest Keywords with AI",
805
+ onClick: handleSuggest
806
+ }
807
+ ),
808
+ panelState === "error" && errorMsg && /* @__PURE__ */ jsx5(Card3, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx5(Text4, { size: 1, children: errorMsg }) })
809
+ ] });
423
810
  }
424
- return Math.max(1, count);
425
- }
426
- function countSentences(text) {
427
- const matches = text.match(/[.!?]+/g);
428
- return matches ? matches.length : 1;
429
- }
430
- function analyzeReadability(text) {
431
- const wordList = text.trim().split(/\s+/).filter(Boolean);
432
- const words = wordList.length;
433
- if (words < 10) return null;
434
- const sentences = countSentences(text);
435
- const syllables = wordList.reduce((acc, word) => acc + countSyllables(word), 0);
436
- const rawScore = 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables / words);
437
- const score = Math.round(rawScore * 10) / 10;
438
- const rawGrade = 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59;
439
- const gradeLevel = Math.round(rawGrade * 10) / 10;
440
- const avgWordsPerSentence = Math.round(words / sentences * 10) / 10;
441
- let label;
442
- let color;
443
- if (score >= 70) {
444
- label = "Easy to read";
445
- color = "green";
446
- } else if (score >= 50) {
447
- label = "Fairly easy";
448
- color = "green";
449
- } else if (score >= 30) {
450
- label = "Moderately difficult";
451
- color = "orange";
452
- } else {
453
- label = "Difficult to read";
454
- color = "red";
811
+ if (panelState === "loading") {
812
+ return /* @__PURE__ */ jsxs5(Flex3, { align: "center", gap: 2, paddingY: 2, children: [
813
+ /* @__PURE__ */ jsx5(Spinner, { muted: true }),
814
+ /* @__PURE__ */ jsx5(Text4, { size: 1, muted: true, children: "Finding keywords\u2026" })
815
+ ] });
455
816
  }
456
- return {
457
- score,
458
- gradeLevel,
459
- avgWordsPerSentence,
460
- words,
461
- sentences,
462
- label,
463
- color
464
- };
817
+ const pendingSuggestions = suggestions.filter((kw) => !existing.includes(kw));
818
+ const alreadyAdded = suggestions.filter((kw) => existing.includes(kw));
819
+ return /* @__PURE__ */ jsx5(Card3, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ jsxs5(Stack3, { space: 3, children: [
820
+ /* @__PURE__ */ jsxs5(Flex3, { align: "center", justify: "space-between", children: [
821
+ /* @__PURE__ */ jsx5(Text4, { size: 1, weight: "semibold", muted: true, children: "AI keyword suggestions" }),
822
+ /* @__PURE__ */ jsxs5(Flex3, { gap: 2, children: [
823
+ pendingSuggestions.length > 1 && /* @__PURE__ */ jsx5(
824
+ Button3,
825
+ {
826
+ mode: "ghost",
827
+ tone: "primary",
828
+ paddingX: 2,
829
+ paddingY: 2,
830
+ fontSize: 1,
831
+ text: "Add all",
832
+ onClick: handleAddAll
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsx5(
836
+ Button3,
837
+ {
838
+ mode: "ghost",
839
+ tone: "default",
840
+ paddingX: 2,
841
+ paddingY: 2,
842
+ fontSize: 1,
843
+ text: "Dismiss",
844
+ onClick: handleDismiss
845
+ }
846
+ )
847
+ ] })
848
+ ] }),
849
+ pendingSuggestions.length > 0 && /* @__PURE__ */ jsxs5(Box, { children: [
850
+ /* @__PURE__ */ jsx5(Text4, { size: 0, muted: true, style: { marginBottom: 8 }, children: "Click to add:" }),
851
+ /* @__PURE__ */ jsx5(Flex3, { gap: 2, wrap: "wrap", style: { marginTop: 6 }, children: pendingSuggestions.map((kw) => /* @__PURE__ */ jsxs5(
852
+ "button",
853
+ {
854
+ type: "button",
855
+ onClick: () => handleAdd(kw),
856
+ style: {
857
+ padding: "4px 10px",
858
+ borderRadius: 20,
859
+ border: "1px solid var(--card-border-color)",
860
+ background: "transparent",
861
+ color: "var(--card-fg-color)",
862
+ cursor: "pointer",
863
+ fontSize: 12,
864
+ lineHeight: 1.4,
865
+ transition: "background 0.15s"
866
+ },
867
+ onMouseEnter: (e) => {
868
+ ;
869
+ e.currentTarget.style.background = "var(--card-border-color)";
870
+ },
871
+ onMouseLeave: (e) => {
872
+ ;
873
+ e.currentTarget.style.background = "transparent";
874
+ },
875
+ children: [
876
+ "+ ",
877
+ kw
878
+ ]
879
+ },
880
+ kw
881
+ )) })
882
+ ] }),
883
+ alreadyAdded.length > 0 && /* @__PURE__ */ jsxs5(Box, { children: [
884
+ /* @__PURE__ */ jsx5(Text4, { size: 0, muted: true, style: { marginBottom: 6 }, children: "Already added:" }),
885
+ /* @__PURE__ */ jsx5(Flex3, { gap: 2, wrap: "wrap", style: { marginTop: 4 }, children: alreadyAdded.map((kw) => /* @__PURE__ */ jsxs5(Badge, { tone: "positive", radius: 5, fontSize: 0, padding: 2, children: [
886
+ "\u2713 ",
887
+ kw
888
+ ] }, kw)) })
889
+ ] }),
890
+ pendingSuggestions.length === 0 && /* @__PURE__ */ jsx5(Text4, { size: 1, muted: true, children: "All suggested keywords added." })
891
+ ] }) });
465
892
  }
893
+
894
+ // src/components/meta/KeywordsInput.tsx
895
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
896
+ var KeywordsInput = (props) => {
897
+ const { value, renderDefault, path, schemaType, onChange } = props;
898
+ const { options } = schemaType;
899
+ const existing = value != null ? value : [];
900
+ const handleAdd = (kw) => {
901
+ if (existing.includes(kw)) return;
902
+ onChange(PatchEvent3.from(set3([...existing, kw])));
903
+ };
904
+ const handleAddAll = (keywords) => {
905
+ const merged = [.../* @__PURE__ */ new Set([...existing, ...keywords])];
906
+ onChange(PatchEvent3.from(set3(merged)));
907
+ };
908
+ return /* @__PURE__ */ jsxs6(Stack4, { space: 3, children: [
909
+ renderDefault(props),
910
+ /* @__PURE__ */ jsx6(
911
+ KeywordSuggestions,
912
+ {
913
+ ai: options == null ? void 0 : options.ai,
914
+ seoFieldPath: String(path[0]),
915
+ existing,
916
+ onAdd: handleAdd,
917
+ onAddAll: handleAddAll
918
+ }
919
+ )
920
+ ] });
921
+ };
922
+ var KeywordsInput_default = KeywordsInput;
923
+
924
+ // src/components/meta/MetaDescription.tsx
925
+ import { Stack as Stack5, Text as Text5 } from "@sanity/ui";
926
+ import { useMemo as useMemo3 } from "react";
927
+ import { PatchEvent as PatchEvent4, set as set4, useFormValue as useFormValue6 } from "sanity";
928
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
466
929
  var MetaDescription = (props) => {
467
- const { value, renderDefault, path } = props;
468
- const parent = useFormValue([path[0]]);
930
+ const { value, renderDefault, path, schemaType, onChange } = props;
931
+ const { options } = schemaType;
932
+ const parent = useFormValue6([path[0]]);
469
933
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
470
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
471
- const feedbackItems = useMemo(
934
+ const keywords = useMemo3(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
935
+ const feedbackItems = useMemo3(
472
936
  () => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
473
937
  [value, keywords, isParentseoField]
474
938
  );
475
- const readability = useMemo(() => analyzeReadability(value || ""), [value]);
476
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
939
+ const readability = useMemo3(() => analyzeReadability(value || ""), [value]);
940
+ return /* @__PURE__ */ jsxs7(Stack5, { space: 3, children: [
477
941
  renderDefault(props),
478
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
479
- /* @__PURE__ */ jsx(
942
+ /* @__PURE__ */ jsx7(
943
+ SeoGenButton,
944
+ {
945
+ field: "description",
946
+ ai: options == null ? void 0 : options.ai,
947
+ seoFieldPath: String(path[0]),
948
+ onGenerate: (v) => onChange(PatchEvent4.from(set4(v)))
949
+ }
950
+ ),
951
+ /* @__PURE__ */ jsx7(Stack5, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
952
+ /* @__PURE__ */ jsx7(
480
953
  "div",
481
954
  {
482
955
  style: { width: 10, height: 10, borderRadius: "50%", backgroundColor: item.color }
483
956
  }
484
957
  ),
485
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
958
+ /* @__PURE__ */ jsx7(Text5, { weight: "bold", muted: true, size: 14, children: item.text })
486
959
  ] }, item.text)) }),
487
- readability && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
488
- /* @__PURE__ */ jsx(
960
+ readability && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
961
+ /* @__PURE__ */ jsx7(
489
962
  "div",
490
963
  {
491
964
  style: {
@@ -496,7 +969,7 @@ var MetaDescription = (props) => {
496
969
  }
497
970
  }
498
971
  ),
499
- /* @__PURE__ */ jsxs(Text, { weight: "bold", muted: true, size: 14, children: [
972
+ /* @__PURE__ */ jsxs7(Text5, { weight: "bold", muted: true, size: 14, children: [
500
973
  "Readability: ",
501
974
  readability.label,
502
975
  " (Flesch ",
@@ -510,18 +983,24 @@ var MetaDescription = (props) => {
510
983
  ] });
511
984
  };
512
985
  var MetaDescription_default = MetaDescription;
986
+
987
+ // src/components/meta/MetaImage.tsx
988
+ import { Stack as Stack6, Text as Text6 } from "@sanity/ui";
989
+ import { useMemo as useMemo4 } from "react";
990
+ import { useFormValue as useFormValue7 } from "sanity";
991
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
513
992
  var MetaImage = (props) => {
514
993
  const { value, renderDefault, path } = props;
515
- const seoParent = useFormValue([path[0]]);
994
+ const seoParent = useFormValue7([path[0]]);
516
995
  const hasImage = !!(value == null ? void 0 : value.asset);
517
- const feedbackItems = useMemo(
996
+ const feedbackItems = useMemo4(
518
997
  () => getMetaImageValidation(hasImage, seoParent),
519
998
  [hasImage, seoParent]
520
999
  );
521
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1000
+ return /* @__PURE__ */ jsxs8(Stack6, { space: 3, children: [
522
1001
  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(
1002
+ /* @__PURE__ */ jsx8(Stack6, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1003
+ /* @__PURE__ */ jsx8(
525
1004
  "div",
526
1005
  {
527
1006
  style: {
@@ -532,11 +1011,17 @@ var MetaImage = (props) => {
532
1011
  }
533
1012
  }
534
1013
  ),
535
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1014
+ /* @__PURE__ */ jsx8(Text6, { weight: "bold", muted: true, size: 14, children: item.text })
536
1015
  ] }, item.text)) })
537
1016
  ] });
538
1017
  };
539
1018
  var MetaImage_default = MetaImage;
1019
+
1020
+ // src/components/meta/MetaTagsPreview.tsx
1021
+ import { Box as Box2, Button as Button4, Card as Card4, Flex as Flex4, Stack as Stack7, Text as Text7 } from "@sanity/ui";
1022
+ import { useMemo as useMemo5, useState as useState5 } from "react";
1023
+ import { useClient as useClient2, useColorScheme, useFormValue as useFormValue8 } from "sanity";
1024
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
540
1025
  function resolveImageUrl(asset, projectId, dataset) {
541
1026
  const ref = asset == null ? void 0 : asset._ref;
542
1027
  if (!ref) return null;
@@ -548,13 +1033,13 @@ function resolveImageUrl(asset, projectId, dataset) {
548
1033
  }
549
1034
  var MetaTagsPreview = (props) => {
550
1035
  const { path } = props;
551
- const [expanded, setExpanded] = useState(false);
552
- const [copyLabel, setCopyLabel] = useState("Copy");
1036
+ const [expanded, setExpanded] = useState5(false);
1037
+ const [copyLabel, setCopyLabel] = useState5("Copy");
553
1038
  const { scheme } = useColorScheme();
554
- const seoFields = useFormValue([path[0]]);
555
- const client = useClient({ apiVersion: "2024-01-01" });
1039
+ const seoFields = useFormValue8([path[0]]);
1040
+ const client = useClient2({ apiVersion: "2024-01-01" });
556
1041
  const { projectId = "", dataset = "" } = client.config();
557
- const rawHtml = useMemo(() => {
1042
+ const rawHtml = useMemo5(() => {
558
1043
  var _a, _b, _c, _d, _e, _f, _g;
559
1044
  const lines = [];
560
1045
  lines.push('<meta charset="UTF-8" />');
@@ -610,8 +1095,8 @@ var MetaTagsPreview = (props) => {
610
1095
  }
611
1096
  return lines.join("\n");
612
1097
  }, [seoFields, projectId, dataset]);
613
- const tagCount = useMemo(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
614
- const tokens = useMemo(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
1098
+ const tagCount = useMemo5(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
1099
+ const tokens = useMemo5(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
615
1100
  const handleCopy = () => {
616
1101
  navigator.clipboard.writeText(rawHtml).then(() => {
617
1102
  setCopyLabel("Copied!");
@@ -624,16 +1109,16 @@ var MetaTagsPreview = (props) => {
624
1109
  const headerBg = isDark ? "#21262d" : "#f0f2f4";
625
1110
  const headerBorder = isDark ? "#30363d" : "#d0d7de";
626
1111
  const headerText = isDark ? "#e6edf3" : "#24292f";
627
- return /* @__PURE__ */ jsx(Card, { padding: 3, radius: 2, tone: "default", border: true, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
628
- /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
629
- /* @__PURE__ */ jsx(Text, { weight: "semibold", size: 1, children: "Meta Tags Preview" }),
630
- /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
631
- /* @__PURE__ */ jsxs(Text, { size: 0, muted: true, children: [
1112
+ return /* @__PURE__ */ jsx9(Card4, { padding: 3, radius: 2, tone: "default", border: true, children: /* @__PURE__ */ jsxs9(Stack7, { space: 3, children: [
1113
+ /* @__PURE__ */ jsxs9(Flex4, { align: "center", justify: "space-between", gap: 3, children: [
1114
+ /* @__PURE__ */ jsx9(Text7, { weight: "semibold", size: 1, children: "Meta Tags Preview" }),
1115
+ /* @__PURE__ */ jsxs9(Flex4, { gap: 2, align: "center", children: [
1116
+ /* @__PURE__ */ jsxs9(Text7, { size: 0, muted: true, children: [
632
1117
  tagCount,
633
1118
  " tags generated"
634
1119
  ] }),
635
- /* @__PURE__ */ jsx(
636
- Button,
1120
+ /* @__PURE__ */ jsx9(
1121
+ Button4,
637
1122
  {
638
1123
  text: copyLabel,
639
1124
  tone: "default",
@@ -645,7 +1130,7 @@ var MetaTagsPreview = (props) => {
645
1130
  )
646
1131
  ] })
647
1132
  ] }),
648
- /* @__PURE__ */ jsxs(
1133
+ /* @__PURE__ */ jsxs9(
649
1134
  "button",
650
1135
  {
651
1136
  type: "button",
@@ -666,12 +1151,12 @@ var MetaTagsPreview = (props) => {
666
1151
  color: headerText
667
1152
  },
668
1153
  children: [
669
- /* @__PURE__ */ jsx("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
1154
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
670
1155
  expanded ? "Hide tags" : "Show tags"
671
1156
  ]
672
1157
  }
673
1158
  ),
674
- expanded && /* @__PURE__ */ jsxs(
1159
+ expanded && /* @__PURE__ */ jsxs9(
675
1160
  "div",
676
1161
  {
677
1162
  style: {
@@ -680,7 +1165,7 @@ var MetaTagsPreview = (props) => {
680
1165
  overflow: "hidden"
681
1166
  },
682
1167
  children: [
683
- /* @__PURE__ */ jsxs(
1168
+ /* @__PURE__ */ jsxs9(
684
1169
  "div",
685
1170
  {
686
1171
  style: {
@@ -692,7 +1177,7 @@ var MetaTagsPreview = (props) => {
692
1177
  gap: 6
693
1178
  },
694
1179
  children: [
695
- /* @__PURE__ */ jsx(
1180
+ /* @__PURE__ */ jsx9(
696
1181
  "span",
697
1182
  {
698
1183
  style: {
@@ -704,12 +1189,12 @@ var MetaTagsPreview = (props) => {
704
1189
  }
705
1190
  }
706
1191
  ),
707
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
1192
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
708
1193
  ]
709
1194
  }
710
1195
  ),
711
- /* @__PURE__ */ jsx(
712
- Box,
1196
+ /* @__PURE__ */ jsx9(
1197
+ Box2,
713
1198
  {
714
1199
  as: "pre",
715
1200
  padding: 3,
@@ -724,7 +1209,7 @@ var MetaTagsPreview = (props) => {
724
1209
  wordBreak: "break-word",
725
1210
  backgroundColor: codeBg
726
1211
  },
727
- children: tokens.map((token, idx) => /* @__PURE__ */ jsx("span", { style: { color: token.color }, children: token.text }, idx))
1212
+ children: tokens.map((token, idx) => /* @__PURE__ */ jsx9("span", { style: { color: token.color }, children: token.text }, idx))
728
1213
  }
729
1214
  )
730
1215
  ]
@@ -733,20 +1218,26 @@ var MetaTagsPreview = (props) => {
733
1218
  ] }) });
734
1219
  };
735
1220
  var MetaTagsPreview_default = MetaTagsPreview;
1221
+
1222
+ // src/components/meta/MetaTitle.tsx
1223
+ import { Stack as Stack8, Text as Text8 } from "@sanity/ui";
1224
+ import { useEffect as useEffect3, useMemo as useMemo6, useState as useState6 } from "react";
1225
+ import { PatchEvent as PatchEvent5, set as set5, useClient as useClient3, useFormValue as useFormValue9 } from "sanity";
1226
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
736
1227
  var MetaTitle = (props) => {
737
1228
  var _a, _b, _c;
738
- const { value, renderDefault, path, schemaType } = props;
1229
+ const { value, renderDefault, path, schemaType, onChange } = props;
739
1230
  const { options } = schemaType;
740
- const parent = useFormValue([path[0]]);
1231
+ const parent = useFormValue9([path[0]]);
741
1232
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
742
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
743
- const rootDoc = (_a = useFormValue([])) != null ? _a : {};
744
- const client = useClient({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
745
- const [groqTitleSuffix, setGroqTitleSuffix] = useState("");
746
- const [isDuplicateTitle, setIsDuplicateTitle] = useState(false);
1233
+ const keywords = useMemo6(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1234
+ const rootDoc = (_a = useFormValue9([])) != null ? _a : {};
1235
+ const client = useClient3({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
1236
+ const [groqTitleSuffix, setGroqTitleSuffix] = useState6("");
1237
+ const [isDuplicateTitle, setIsDuplicateTitle] = useState6(false);
747
1238
  const titleSuffixQuery = options == null ? void 0 : options.titleSuffixQuery;
748
1239
  const titleSuffixOption = options == null ? void 0 : options.titleSuffix;
749
- useEffect(() => {
1240
+ useEffect3(() => {
750
1241
  if (!titleSuffixQuery) return;
751
1242
  client.fetch(titleSuffixQuery).then((result) => {
752
1243
  setGroqTitleSuffix(result === null || result === void 0 ? "" : String(result));
@@ -755,7 +1246,7 @@ var MetaTitle = (props) => {
755
1246
  });
756
1247
  }, [titleSuffixQuery, client]);
757
1248
  const currentDocId = (_c = rootDoc._id) != null ? _c : "";
758
- useEffect(() => {
1249
+ useEffect3(() => {
759
1250
  if (!(value == null ? void 0 : value.trim()) || !currentDocId) {
760
1251
  setIsDuplicateTitle(false);
761
1252
  return () => {
@@ -769,7 +1260,7 @@ var MetaTitle = (props) => {
769
1260
  }, 600);
770
1261
  return () => clearTimeout(timer);
771
1262
  }, [value, client, currentDocId]);
772
- const resolvedSuffix = useMemo(() => {
1263
+ const resolvedSuffix = useMemo6(() => {
773
1264
  if (titleSuffixQuery) return groqTitleSuffix;
774
1265
  if (!titleSuffixOption) return "";
775
1266
  if (typeof titleSuffixOption === "function") {
@@ -778,15 +1269,24 @@ var MetaTitle = (props) => {
778
1269
  return titleSuffixOption;
779
1270
  }, [titleSuffixQuery, groqTitleSuffix, titleSuffixOption, rootDoc]);
780
1271
  const suffixLength = resolvedSuffix ? resolvedSuffix.length + 3 : 0;
781
- const feedbackItems = useMemo(
1272
+ const feedbackItems = useMemo6(
782
1273
  () => getMetaTitleValidationMessages(value || "", keywords, isParentseoField, suffixLength),
783
1274
  [value, keywords, isParentseoField, suffixLength]
784
1275
  );
785
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1276
+ return /* @__PURE__ */ jsxs10(Stack8, { space: 3, children: [
786
1277
  renderDefault(props),
787
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
788
- feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
789
- /* @__PURE__ */ jsx(
1278
+ /* @__PURE__ */ jsx10(
1279
+ SeoGenButton,
1280
+ {
1281
+ field: "title",
1282
+ ai: options == null ? void 0 : options.ai,
1283
+ seoFieldPath: String(path[0]),
1284
+ onGenerate: (v) => onChange(PatchEvent5.from(set5(v)))
1285
+ }
1286
+ ),
1287
+ /* @__PURE__ */ jsxs10(Stack8, { space: 2, children: [
1288
+ feedbackItems.map((item) => /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1289
+ /* @__PURE__ */ jsx10(
790
1290
  "div",
791
1291
  {
792
1292
  style: {
@@ -797,10 +1297,10 @@ var MetaTitle = (props) => {
797
1297
  }
798
1298
  }
799
1299
  ),
800
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1300
+ /* @__PURE__ */ jsx10(Text8, { weight: "bold", muted: true, size: 14, children: item.text })
801
1301
  ] }, item.text)),
802
- isDuplicateTitle && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
803
- /* @__PURE__ */ jsx(
1302
+ isDuplicateTitle && /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1303
+ /* @__PURE__ */ jsx10(
804
1304
  "div",
805
1305
  {
806
1306
  style: {
@@ -811,7 +1311,7 @@ var MetaTitle = (props) => {
811
1311
  }
812
1312
  }
813
1313
  ),
814
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
1314
+ /* @__PURE__ */ jsx10(Text8, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
815
1315
  ] })
816
1316
  ] })
817
1317
  ] });
@@ -819,6 +1319,10 @@ var MetaTitle = (props) => {
819
1319
  var MetaTitle_default = MetaTitle;
820
1320
 
821
1321
  // src/utils/fieldsUtils.ts
1322
+ function withLicense(ai, licenseKey) {
1323
+ if (!ai) return void 0;
1324
+ return licenseKey ? __spreadProps(__spreadValues({}, ai), { _licenseKey: licenseKey }) : ai;
1325
+ }
822
1326
  var DEFAULT_FIELD_INFO = {
823
1327
  title: {
824
1328
  title: "Meta Title",
@@ -956,19 +1460,38 @@ var getFieldHiddenFunction = (fieldName, config) => {
956
1460
  var isEmpty = (value) => {
957
1461
  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;
958
1462
  };
1463
+
1464
+ // src/schemas/types/openGraph/index.ts
1465
+ import { defineField, defineType } from "sanity";
1466
+
1467
+ // src/components/openGraph/OgDescription.tsx
1468
+ import { Stack as Stack9, Text as Text9 } from "@sanity/ui";
1469
+ import { useMemo as useMemo7 } from "react";
1470
+ import { PatchEvent as PatchEvent6, set as set6, useFormValue as useFormValue10 } from "sanity";
1471
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
959
1472
  var OgDescription = (props) => {
960
- const { value, renderDefault, path } = props;
961
- const parent = useFormValue([path[0]]);
1473
+ const { value, renderDefault, path, schemaType, onChange } = props;
1474
+ const { options } = schemaType;
1475
+ const parent = useFormValue10([path[0]]);
962
1476
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
963
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
964
- const feedbackItems = useMemo(
1477
+ const keywords = useMemo7(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1478
+ const feedbackItems = useMemo7(
965
1479
  () => getOgDescriptionValidation(value || "", keywords, isParentseoField),
966
1480
  [value, keywords, isParentseoField]
967
1481
  );
968
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1482
+ return /* @__PURE__ */ jsxs11(Stack9, { space: 3, children: [
969
1483
  renderDefault(props),
970
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
971
- /* @__PURE__ */ jsx(
1484
+ /* @__PURE__ */ jsx11(
1485
+ SeoGenButton,
1486
+ {
1487
+ field: "ogDescription",
1488
+ ai: options == null ? void 0 : options.ai,
1489
+ seoFieldPath: String(path[0]),
1490
+ onGenerate: (v) => onChange(PatchEvent6.from(set6(v)))
1491
+ }
1492
+ ),
1493
+ /* @__PURE__ */ jsx11(Stack9, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1494
+ /* @__PURE__ */ jsx11(
972
1495
  "div",
973
1496
  {
974
1497
  style: {
@@ -979,25 +1502,31 @@ var OgDescription = (props) => {
979
1502
  }
980
1503
  }
981
1504
  ),
982
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1505
+ /* @__PURE__ */ jsx11(Text9, { weight: "bold", muted: true, size: 14, children: item.text })
983
1506
  ] }, item.text)) })
984
1507
  ] });
985
1508
  };
986
1509
  var OgDescription_default = OgDescription;
1510
+
1511
+ // src/components/openGraph/OgImage.tsx
1512
+ import { Stack as Stack10, Text as Text10 } from "@sanity/ui";
1513
+ import { useMemo as useMemo8 } from "react";
1514
+ import { useFormValue as useFormValue11 } from "sanity";
1515
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
987
1516
  var OgImage = (props) => {
988
1517
  const { value, renderDefault, path } = props;
989
- const seoParent = useFormValue([path[0]]);
1518
+ const seoParent = useFormValue11([path[0]]);
990
1519
  const imgValue = value;
991
1520
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
992
1521
  const altText = imgValue == null ? void 0 : imgValue.alt;
993
- const feedbackItems = useMemo(
1522
+ const feedbackItems = useMemo8(
994
1523
  () => getOgImageValidation(hasImage, altText, seoParent),
995
1524
  [hasImage, altText, seoParent]
996
1525
  );
997
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1526
+ return /* @__PURE__ */ jsxs12(Stack10, { space: 3, children: [
998
1527
  renderDefault(props),
999
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1000
- /* @__PURE__ */ jsx(
1528
+ /* @__PURE__ */ jsx12(Stack10, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1529
+ /* @__PURE__ */ jsx12(
1001
1530
  "div",
1002
1531
  {
1003
1532
  style: {
@@ -1008,19 +1537,25 @@ var OgImage = (props) => {
1008
1537
  }
1009
1538
  }
1010
1539
  ),
1011
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1540
+ /* @__PURE__ */ jsx12(Text10, { weight: "bold", muted: true, size: 14, children: item.text })
1012
1541
  ] }, item.text)) })
1013
1542
  ] });
1014
1543
  };
1015
1544
  var OgImage_default = OgImage;
1545
+
1546
+ // src/components/openGraph/OgImageUrl.tsx
1547
+ import { Stack as Stack11, Text as Text11 } from "@sanity/ui";
1548
+ import { useMemo as useMemo9 } from "react";
1549
+ import { useFormValue as useFormValue12 } from "sanity";
1550
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1016
1551
  var OgImageUrl = (props) => {
1017
1552
  const { value, renderDefault, path } = props;
1018
- const seoParent = useFormValue([path[0]]);
1019
- const feedbackItems = useMemo(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
1020
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1553
+ const seoParent = useFormValue12([path[0]]);
1554
+ const feedbackItems = useMemo9(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
1555
+ return /* @__PURE__ */ jsxs13(Stack11, { space: 3, children: [
1021
1556
  renderDefault(props),
1022
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1023
- /* @__PURE__ */ jsx(
1557
+ /* @__PURE__ */ jsx13(Stack11, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1558
+ /* @__PURE__ */ jsx13(
1024
1559
  "div",
1025
1560
  {
1026
1561
  style: {
@@ -1031,24 +1566,40 @@ var OgImageUrl = (props) => {
1031
1566
  }
1032
1567
  }
1033
1568
  ),
1034
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1569
+ /* @__PURE__ */ jsx13(Text11, { weight: "bold", muted: true, size: 14, children: item.text })
1035
1570
  ] }, item.text)) })
1036
1571
  ] });
1037
1572
  };
1038
1573
  var OgImageUrl_default = OgImageUrl;
1574
+
1575
+ // src/components/openGraph/OgTitle.tsx
1576
+ import { Stack as Stack12, Text as Text12 } from "@sanity/ui";
1577
+ import { useMemo as useMemo10 } from "react";
1578
+ import { PatchEvent as PatchEvent7, set as set7, useFormValue as useFormValue13 } from "sanity";
1579
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1039
1580
  var OgTitle = (props) => {
1040
- const { value, renderDefault, path } = props;
1041
- const parent = useFormValue([path[0]]);
1581
+ const { value, renderDefault, path, schemaType, onChange } = props;
1582
+ const { options } = schemaType;
1583
+ const parent = useFormValue13([path[0]]);
1042
1584
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1043
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1044
- const feedbackItems = useMemo(
1585
+ const keywords = useMemo10(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1586
+ const feedbackItems = useMemo10(
1045
1587
  () => getOgTitleValidation(value || "", keywords, isParentseoField),
1046
1588
  [value, keywords, isParentseoField]
1047
1589
  );
1048
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1590
+ return /* @__PURE__ */ jsxs14(Stack12, { space: 3, children: [
1049
1591
  renderDefault(props),
1050
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1051
- /* @__PURE__ */ jsx(
1592
+ /* @__PURE__ */ jsx14(
1593
+ SeoGenButton,
1594
+ {
1595
+ field: "ogTitle",
1596
+ ai: options == null ? void 0 : options.ai,
1597
+ seoFieldPath: String(path[0]),
1598
+ onGenerate: (v) => onChange(PatchEvent7.from(set7(v)))
1599
+ }
1600
+ ),
1601
+ /* @__PURE__ */ jsx14(Stack12, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1602
+ /* @__PURE__ */ jsx14(
1052
1603
  "div",
1053
1604
  {
1054
1605
  style: {
@@ -1059,7 +1610,7 @@ var OgTitle = (props) => {
1059
1610
  }
1060
1611
  }
1061
1612
  ),
1062
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1613
+ /* @__PURE__ */ jsx14(Text12, { weight: "bold", muted: true, size: 14, children: item.text })
1063
1614
  ] }, item.text)) })
1064
1615
  ] });
1065
1616
  };
@@ -1086,8 +1637,8 @@ function openGraph(config = {}) {
1086
1637
  hidden: getFieldHiddenFunction("openGraphTitle", config),
1087
1638
  components: {
1088
1639
  input: OgTitle_default
1089
- // Can also wrap with a string input + preview
1090
- }
1640
+ },
1641
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1091
1642
  })),
1092
1643
  defineField(__spreadProps(__spreadValues({
1093
1644
  name: "description"
@@ -1097,8 +1648,8 @@ function openGraph(config = {}) {
1097
1648
  hidden: getFieldHiddenFunction("openGraphDescription", config),
1098
1649
  components: {
1099
1650
  input: OgDescription_default
1100
- // Can also wrap with a text area + preview
1101
- }
1651
+ },
1652
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1102
1653
  })),
1103
1654
  defineField(__spreadProps(__spreadValues({
1104
1655
  name: "siteName"
@@ -1180,19 +1731,38 @@ function openGraph(config = {}) {
1180
1731
  ]
1181
1732
  });
1182
1733
  }
1734
+
1735
+ // src/schemas/types/twitter/index.ts
1736
+ import { defineField as defineField2, defineType as defineType2 } from "sanity";
1737
+
1738
+ // src/components/twitter/twitterDescription.tsx
1739
+ import { Stack as Stack13, Text as Text13 } from "@sanity/ui";
1740
+ import { useMemo as useMemo11 } from "react";
1741
+ import { PatchEvent as PatchEvent8, set as set8, useFormValue as useFormValue14 } from "sanity";
1742
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1183
1743
  var TwitterDescription = (props) => {
1184
- const { value, renderDefault, path } = props;
1185
- const parent = useFormValue([path[0]]);
1744
+ const { value, renderDefault, path, schemaType, onChange } = props;
1745
+ const { options } = schemaType;
1746
+ const parent = useFormValue14([path[0]]);
1186
1747
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1187
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1188
- const feedbackItems = useMemo(
1748
+ const keywords = useMemo11(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1749
+ const feedbackItems = useMemo11(
1189
1750
  () => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
1190
1751
  [value, keywords, isParentseoField]
1191
1752
  );
1192
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1753
+ return /* @__PURE__ */ jsxs15(Stack13, { space: 3, children: [
1193
1754
  renderDefault(props),
1194
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1195
- /* @__PURE__ */ jsx(
1755
+ /* @__PURE__ */ jsx15(
1756
+ SeoGenButton,
1757
+ {
1758
+ field: "twitterDescription",
1759
+ ai: options == null ? void 0 : options.ai,
1760
+ seoFieldPath: String(path[0]),
1761
+ onGenerate: (v) => onChange(PatchEvent8.from(set8(v)))
1762
+ }
1763
+ ),
1764
+ /* @__PURE__ */ jsx15(Stack13, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs15("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1765
+ /* @__PURE__ */ jsx15(
1196
1766
  "div",
1197
1767
  {
1198
1768
  style: {
@@ -1203,25 +1773,31 @@ var TwitterDescription = (props) => {
1203
1773
  }
1204
1774
  }
1205
1775
  ),
1206
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1776
+ /* @__PURE__ */ jsx15(Text13, { weight: "bold", muted: true, size: 14, children: item.text })
1207
1777
  ] }, item.text)) })
1208
1778
  ] });
1209
1779
  };
1210
1780
  var twitterDescription_default = TwitterDescription;
1781
+
1782
+ // src/components/twitter/TwitterImage.tsx
1783
+ import { Stack as Stack14, Text as Text14 } from "@sanity/ui";
1784
+ import { useMemo as useMemo12 } from "react";
1785
+ import { useFormValue as useFormValue15 } from "sanity";
1786
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1211
1787
  var TwitterImage = (props) => {
1212
1788
  const { value, renderDefault, path } = props;
1213
- const seoParent = useFormValue([path[0]]);
1789
+ const seoParent = useFormValue15([path[0]]);
1214
1790
  const imgValue = value;
1215
1791
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
1216
1792
  const altText = imgValue == null ? void 0 : imgValue.alt;
1217
- const feedbackItems = useMemo(
1793
+ const feedbackItems = useMemo12(
1218
1794
  () => getTwitterImageValidation(hasImage, altText, seoParent),
1219
1795
  [hasImage, altText, seoParent]
1220
1796
  );
1221
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1797
+ return /* @__PURE__ */ jsxs16(Stack14, { space: 3, children: [
1222
1798
  renderDefault(props),
1223
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1224
- /* @__PURE__ */ jsx(
1799
+ /* @__PURE__ */ jsx16(Stack14, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1800
+ /* @__PURE__ */ jsx16(
1225
1801
  "div",
1226
1802
  {
1227
1803
  style: {
@@ -1232,22 +1808,28 @@ var TwitterImage = (props) => {
1232
1808
  }
1233
1809
  }
1234
1810
  ),
1235
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1811
+ /* @__PURE__ */ jsx16(Text14, { weight: "bold", muted: true, size: 14, children: item.text })
1236
1812
  ] }, item.text)) })
1237
1813
  ] });
1238
1814
  };
1239
1815
  var TwitterImage_default = TwitterImage;
1816
+
1817
+ // src/components/twitter/TwitterImageUrl.tsx
1818
+ import { Stack as Stack15, Text as Text15 } from "@sanity/ui";
1819
+ import { useMemo as useMemo13 } from "react";
1820
+ import { useFormValue as useFormValue16 } from "sanity";
1821
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1240
1822
  var TwitterImageUrl = (props) => {
1241
1823
  const { value, renderDefault, path } = props;
1242
- const seoParent = useFormValue([path[0]]);
1243
- const feedbackItems = useMemo(
1824
+ const seoParent = useFormValue16([path[0]]);
1825
+ const feedbackItems = useMemo13(
1244
1826
  () => getTwitterImageUrlValidation(value, seoParent),
1245
1827
  [value, seoParent]
1246
1828
  );
1247
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1829
+ return /* @__PURE__ */ jsxs17(Stack15, { space: 3, children: [
1248
1830
  renderDefault(props),
1249
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1250
- /* @__PURE__ */ jsx(
1831
+ /* @__PURE__ */ jsx17(Stack15, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1832
+ /* @__PURE__ */ jsx17(
1251
1833
  "div",
1252
1834
  {
1253
1835
  style: {
@@ -1258,24 +1840,40 @@ var TwitterImageUrl = (props) => {
1258
1840
  }
1259
1841
  }
1260
1842
  ),
1261
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1843
+ /* @__PURE__ */ jsx17(Text15, { weight: "bold", muted: true, size: 14, children: item.text })
1262
1844
  ] }, item.text)) })
1263
1845
  ] });
1264
1846
  };
1265
1847
  var TwitterImageUrl_default = TwitterImageUrl;
1848
+
1849
+ // src/components/twitter/twitterTitle.tsx
1850
+ import { Stack as Stack16, Text as Text16 } from "@sanity/ui";
1851
+ import { useMemo as useMemo14 } from "react";
1852
+ import { PatchEvent as PatchEvent9, set as set9, useFormValue as useFormValue17 } from "sanity";
1853
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1266
1854
  var TwitterTitle = (props) => {
1267
- const { value, renderDefault, path } = props;
1268
- const parent = useFormValue([path[0]]);
1855
+ const { value, renderDefault, path, schemaType, onChange } = props;
1856
+ const { options } = schemaType;
1857
+ const parent = useFormValue17([path[0]]);
1269
1858
  const isParentseoField = parent && (parent == null ? void 0 : parent._type) === "seoFields";
1270
- const keywords = useMemo(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1271
- const feedbackItems = useMemo(
1859
+ const keywords = useMemo14(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1860
+ const feedbackItems = useMemo14(
1272
1861
  () => getTwitterTitleValidation(value || "", keywords, isParentseoField),
1273
1862
  [value, keywords, isParentseoField]
1274
1863
  );
1275
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1864
+ return /* @__PURE__ */ jsxs18(Stack16, { space: 3, children: [
1276
1865
  renderDefault(props),
1277
- /* @__PURE__ */ jsx(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1278
- /* @__PURE__ */ jsx(
1866
+ /* @__PURE__ */ jsx18(
1867
+ SeoGenButton,
1868
+ {
1869
+ field: "twitterTitle",
1870
+ ai: options == null ? void 0 : options.ai,
1871
+ seoFieldPath: String(path[0]),
1872
+ onGenerate: (v) => onChange(PatchEvent9.from(set9(v)))
1873
+ }
1874
+ ),
1875
+ /* @__PURE__ */ jsx18(Stack16, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs18("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1876
+ /* @__PURE__ */ jsx18(
1279
1877
  "div",
1280
1878
  {
1281
1879
  style: {
@@ -1286,7 +1884,7 @@ var TwitterTitle = (props) => {
1286
1884
  }
1287
1885
  }
1288
1886
  ),
1289
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1887
+ /* @__PURE__ */ jsx18(Text16, { weight: "bold", muted: true, size: 14, children: item.text })
1290
1888
  ] }, item.text)) })
1291
1889
  ] });
1292
1890
  };
@@ -1294,12 +1892,12 @@ var twitterTitle_default = TwitterTitle;
1294
1892
 
1295
1893
  // src/schemas/types/twitter/index.ts
1296
1894
  function twitter(config = {}) {
1297
- return defineType({
1895
+ return defineType2({
1298
1896
  name: "twitter",
1299
1897
  title: "X (Formerly Twitter)",
1300
1898
  type: "object",
1301
1899
  fields: [
1302
- defineField(__spreadProps(__spreadValues({
1900
+ defineField2(__spreadProps(__spreadValues({
1303
1901
  name: "card"
1304
1902
  }, getFieldInfo("twitterCard", config.fieldOverrides)), {
1305
1903
  type: "string",
@@ -1315,28 +1913,29 @@ function twitter(config = {}) {
1315
1913
  initialValue: "summary_large_image"
1316
1914
  // good default
1317
1915
  })),
1318
- defineField(__spreadProps(__spreadValues({
1916
+ defineField2(__spreadProps(__spreadValues({
1319
1917
  name: "site"
1320
1918
  }, getFieldInfo("twitterSite", config.fieldOverrides)), {
1321
1919
  type: "string",
1322
1920
  hidden: getFieldHiddenFunction("twitterSite", config)
1323
1921
  })),
1324
- defineField(__spreadProps(__spreadValues({
1922
+ defineField2(__spreadProps(__spreadValues({
1325
1923
  name: "creator",
1326
1924
  type: "string"
1327
1925
  }, getFieldInfo("twitterCreator", config.fieldOverrides)), {
1328
1926
  hidden: getFieldHiddenFunction("twitterCreator", config)
1329
1927
  })),
1330
- defineField(__spreadProps(__spreadValues({
1928
+ defineField2(__spreadProps(__spreadValues({
1331
1929
  name: "title",
1332
1930
  type: "string"
1333
1931
  }, getFieldInfo("twitterTitle", config.fieldOverrides)), {
1334
1932
  hidden: getFieldHiddenFunction("twitterTitle", config),
1335
1933
  components: {
1336
1934
  input: twitterTitle_default
1337
- }
1935
+ },
1936
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1338
1937
  })),
1339
- defineField(__spreadProps(__spreadValues({
1938
+ defineField2(__spreadProps(__spreadValues({
1340
1939
  name: "description",
1341
1940
  type: "text",
1342
1941
  rows: 3
@@ -1344,9 +1943,10 @@ function twitter(config = {}) {
1344
1943
  hidden: getFieldHiddenFunction("twitterDescription", config),
1345
1944
  components: {
1346
1945
  input: twitterDescription_default
1347
- }
1946
+ },
1947
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1348
1948
  })),
1349
- defineField(__spreadProps(__spreadValues({
1949
+ defineField2(__spreadProps(__spreadValues({
1350
1950
  name: "imageType"
1351
1951
  }, getFieldInfo("twitterImageType", config.fieldOverrides)), {
1352
1952
  type: "string",
@@ -1359,7 +1959,7 @@ function twitter(config = {}) {
1359
1959
  hidden: getFieldHiddenFunction("twitterImage", config),
1360
1960
  initialValue: "upload"
1361
1961
  })),
1362
- defineField(__spreadProps(__spreadValues({
1962
+ defineField2(__spreadProps(__spreadValues({
1363
1963
  name: "image"
1364
1964
  }, getFieldInfo("twitterImage", config.fieldOverrides)), {
1365
1965
  type: "image",
@@ -1370,7 +1970,7 @@ function twitter(config = {}) {
1370
1970
  input: TwitterImage_default
1371
1971
  },
1372
1972
  fields: [
1373
- defineField({
1973
+ defineField2({
1374
1974
  name: "alt",
1375
1975
  title: "Image Alt Text",
1376
1976
  type: "string",
@@ -1384,7 +1984,7 @@ function twitter(config = {}) {
1384
1984
  return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
1385
1985
  }
1386
1986
  })),
1387
- defineField(__spreadProps(__spreadValues({
1987
+ defineField2(__spreadProps(__spreadValues({
1388
1988
  name: "imageUrl"
1389
1989
  }, getFieldInfo("twitterImageUrl", config.fieldOverrides)), {
1390
1990
  type: "url",
@@ -1403,7 +2003,7 @@ function twitter(config = {}) {
1403
2003
  }
1404
2004
 
1405
2005
  // src/schemas/index.ts
1406
- var LazySeoPreview = lazy(() => import('./SeoPreview-4PPAF4NO.js'));
2006
+ var LazySeoPreview = lazy(() => import("./SeoPreview-UNQBKTQO.js"));
1407
2007
  var SeoPreviewWrapper = (props) => createElement(Suspense, { fallback: null }, createElement(LazySeoPreview, props));
1408
2008
  function buildFieldGroupMap(groups) {
1409
2009
  const map = /* @__PURE__ */ new Map();
@@ -1438,18 +2038,87 @@ function withGroup(field, fieldGroupMap) {
1438
2038
  if (!groups || groups.length === 0) return field;
1439
2039
  return __spreadProps(__spreadValues({}, field), { group: groups.length === 1 ? groups[0] : groups });
1440
2040
  }
2041
+ function shouldShowPreviewField(seoPreview) {
2042
+ if (typeof seoPreview === "boolean") return seoPreview;
2043
+ if (typeof seoPreview === "object") return !isEmpty(seoPreview);
2044
+ return false;
2045
+ }
2046
+ function getSeoPreviewSuffixOptions(seoPreview) {
2047
+ if (typeof seoPreview !== "object" || !seoPreview) return {};
2048
+ const extra = {};
2049
+ if (seoPreview.titleSuffix) extra.titleSuffix = seoPreview.titleSuffix;
2050
+ if (seoPreview.titleSuffixQuery) extra.titleSuffixQuery = seoPreview.titleSuffixQuery;
2051
+ return extra;
2052
+ }
2053
+ function getPreviewFieldOptions(config) {
2054
+ const seoPreview = config.seoPreview;
2055
+ const extra = {};
2056
+ if (typeof seoPreview === "object" && seoPreview) {
2057
+ if (seoPreview.prefix) extra.prefix = seoPreview.prefix;
2058
+ if (seoPreview.titleSuffixInheritColor) {
2059
+ extra.titleSuffixInheritColor = seoPreview.titleSuffixInheritColor;
2060
+ }
2061
+ }
2062
+ return __spreadValues(__spreadValues(__spreadValues({
2063
+ baseUrl: config.baseUrl || "https://www.example.com"
2064
+ }, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(seoPreview)), extra);
2065
+ }
2066
+ function getAiOption(config) {
2067
+ return config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {};
2068
+ }
2069
+ function getTitleFieldOptions(config) {
2070
+ return __spreadValues(__spreadValues(__spreadValues({}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(config.seoPreview)), getAiOption(config));
2071
+ }
2072
+ function buildGeoChecklistField(config, fieldGroupMap) {
2073
+ if (config.geo === false) return [];
2074
+ return [
2075
+ withGroup(
2076
+ defineField3(__spreadProps(__spreadValues({
2077
+ name: "geoChecklist"
2078
+ }, getFieldInfo("geoChecklist", config.fieldOverrides)), {
2079
+ type: "string",
2080
+ readOnly: true,
2081
+ components: {
2082
+ input: GEOChecklist_default
2083
+ },
2084
+ options: __spreadValues({}, config.licenseKey ? { licenseKey: config.licenseKey } : {}),
2085
+ hidden: getFieldHiddenFunction("geoChecklist", config)
2086
+ })),
2087
+ fieldGroupMap
2088
+ )
2089
+ ];
2090
+ }
2091
+ function buildMetaTagsPreviewField(config, fieldGroupMap) {
2092
+ if (config.metaTagsPreview === false) return [];
2093
+ return [
2094
+ withGroup(
2095
+ defineField3(__spreadProps(__spreadValues({
2096
+ name: "metaTagsPreview"
2097
+ }, getFieldInfo("metaTagsPreview", config.fieldOverrides)), {
2098
+ type: "string",
2099
+ readOnly: true,
2100
+ components: {
2101
+ input: MetaTagsPreview_default
2102
+ },
2103
+ hidden: getFieldHiddenFunction("metaTagsPreview", config)
2104
+ })),
2105
+ fieldGroupMap
2106
+ )
2107
+ ];
2108
+ }
1441
2109
  function seoFieldsSchema(config = {}) {
2110
+ var _a;
1442
2111
  const groupsCfg = config.fieldGroups;
1443
2112
  const fieldGroupMap = (groupsCfg == null ? void 0 : groupsCfg.length) ? buildFieldGroupMap(groupsCfg) : void 0;
1444
2113
  const sanityGroups = (groupsCfg == null ? void 0 : groupsCfg.length) ? toSanityGroups(groupsCfg) : void 0;
1445
- return defineType(__spreadProps(__spreadValues({
2114
+ return defineType3(__spreadProps(__spreadValues({
1446
2115
  name: "seoFields",
1447
2116
  title: "SEO Fields",
1448
2117
  type: "object"
1449
2118
  }, sanityGroups ? { groups: sanityGroups } : {}), {
1450
2119
  fields: [
1451
2120
  withGroup(
1452
- defineField({
2121
+ defineField3({
1453
2122
  name: "robots",
1454
2123
  title: "Robots Settings",
1455
2124
  type: "robots",
@@ -1458,16 +2127,14 @@ function seoFieldsSchema(config = {}) {
1458
2127
  fieldGroupMap
1459
2128
  ),
1460
2129
  // 👇 conditionally spread preview field
1461
- ...typeof config.seoPreview === "boolean" && config.seoPreview || typeof config.seoPreview === "object" && !isEmpty(config.seoPreview) ? [
2130
+ ...shouldShowPreviewField(config.seoPreview) ? [
1462
2131
  withGroup(
1463
- defineField({
2132
+ defineField3({
1464
2133
  name: "preview",
1465
2134
  title: "SEO Preview",
1466
2135
  type: "string",
1467
2136
  components: { input: SeoPreviewWrapper },
1468
- options: __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({
1469
- baseUrl: config.baseUrl || "https://www.example.com"
1470
- }, 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 } : {}),
2137
+ options: getPreviewFieldOptions(config),
1471
2138
  initialValue: "",
1472
2139
  readOnly: true
1473
2140
  }),
@@ -1475,20 +2142,20 @@ function seoFieldsSchema(config = {}) {
1475
2142
  )
1476
2143
  ] : [],
1477
2144
  withGroup(
1478
- defineField(__spreadProps(__spreadValues({
2145
+ defineField3(__spreadProps(__spreadValues({
1479
2146
  name: "title"
1480
2147
  }, getFieldInfo("title", config.fieldOverrides)), {
1481
2148
  type: "string",
1482
2149
  components: {
1483
2150
  input: MetaTitle_default
1484
2151
  },
1485
- 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 } : {}),
2152
+ options: getTitleFieldOptions(config),
1486
2153
  hidden: getFieldHiddenFunction("title", config)
1487
2154
  })),
1488
2155
  fieldGroupMap
1489
2156
  ),
1490
2157
  withGroup(
1491
- defineField(__spreadProps(__spreadValues({
2158
+ defineField3(__spreadProps(__spreadValues({
1492
2159
  name: "description"
1493
2160
  }, getFieldInfo("description", config.fieldOverrides)), {
1494
2161
  type: "text",
@@ -1496,12 +2163,13 @@ function seoFieldsSchema(config = {}) {
1496
2163
  components: {
1497
2164
  input: MetaDescription_default
1498
2165
  },
2166
+ options: getAiOption(config),
1499
2167
  hidden: getFieldHiddenFunction("description", config)
1500
2168
  })),
1501
2169
  fieldGroupMap
1502
2170
  ),
1503
2171
  withGroup(
1504
- defineField(__spreadProps(__spreadValues({
2172
+ defineField3(__spreadProps(__spreadValues({
1505
2173
  name: "metaImage"
1506
2174
  }, getFieldInfo("metaImage", config.fieldOverrides)), {
1507
2175
  type: "image",
@@ -1516,7 +2184,7 @@ function seoFieldsSchema(config = {}) {
1516
2184
  fieldGroupMap
1517
2185
  ),
1518
2186
  withGroup(
1519
- defineField(__spreadProps(__spreadValues({
2187
+ defineField3(__spreadProps(__spreadValues({
1520
2188
  name: "metaAttributes"
1521
2189
  }, getFieldInfo("metaAttributes", config.fieldOverrides)), {
1522
2190
  type: "array",
@@ -1526,19 +2194,21 @@ function seoFieldsSchema(config = {}) {
1526
2194
  fieldGroupMap
1527
2195
  ),
1528
2196
  withGroup(
1529
- defineField(__spreadProps(__spreadValues({
2197
+ defineField3(__spreadProps(__spreadValues({
1530
2198
  name: "keywords"
1531
2199
  }, getFieldInfo("keywords", config.fieldOverrides)), {
1532
2200
  title: "Keywords",
1533
2201
  type: "array",
1534
2202
  of: [{ type: "string" }],
1535
2203
  description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
2204
+ components: config.ai ? { input: KeywordsInput_default } : void 0,
2205
+ options: getAiOption(config),
1536
2206
  hidden: getFieldHiddenFunction("keywords", config)
1537
2207
  })),
1538
2208
  fieldGroupMap
1539
2209
  ),
1540
2210
  withGroup(
1541
- defineField(__spreadProps(__spreadValues({
2211
+ defineField3(__spreadProps(__spreadValues({
1542
2212
  name: "canonicalUrl"
1543
2213
  }, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
1544
2214
  title: "Canonical URL",
@@ -1549,71 +2219,54 @@ function seoFieldsSchema(config = {}) {
1549
2219
  fieldGroupMap
1550
2220
  ),
1551
2221
  withGroup(
1552
- defineField(__spreadProps(__spreadValues({
2222
+ defineField3(__spreadProps(__spreadValues({
1553
2223
  name: "focusKeyword"
1554
2224
  }, getFieldInfo("focusKeyword", config.fieldOverrides)), {
1555
2225
  type: "string",
1556
2226
  components: {
1557
2227
  input: FocusKeywordInput_default
1558
2228
  },
2229
+ options: getAiOption(config),
1559
2230
  hidden: getFieldHiddenFunction("focusKeyword", config)
1560
2231
  })),
1561
2232
  fieldGroupMap
1562
2233
  ),
1563
2234
  withGroup(
1564
- defineField(__spreadProps(__spreadValues({
2235
+ defineField3(__spreadProps(__spreadValues(__spreadProps(__spreadValues({
1565
2236
  name: "hreflangs"
1566
2237
  }, getFieldInfo("hreflangs", config.fieldOverrides)), {
1567
2238
  type: "array",
1568
2239
  of: [{ type: "hreflangEntry" }],
1569
- description: "Add alternate language/region versions. Include x-default for the fallback URL.",
2240
+ description: "Add alternate language/region versions. Include x-default for the fallback URL."
2241
+ }), ((_a = config.hreflang) == null ? void 0 : _a.autoFill) ? {
2242
+ components: { input: HreflangInput_default },
2243
+ options: {
2244
+ hreflang: config.hreflang,
2245
+ baseUrl: config.baseUrl,
2246
+ apiVersion: config.apiVersion
2247
+ }
2248
+ } : {}), {
1570
2249
  hidden: getFieldHiddenFunction("hreflangs", config)
1571
2250
  })),
1572
2251
  fieldGroupMap
1573
2252
  ),
1574
- ...config.geo === false ? [] : [
1575
- withGroup(
1576
- defineField(__spreadProps(__spreadValues({
1577
- name: "geoChecklist"
1578
- }, getFieldInfo("geoChecklist", config.fieldOverrides)), {
1579
- type: "string",
1580
- readOnly: true,
1581
- components: {
1582
- input: GEOChecklist_default
1583
- },
1584
- options: __spreadValues({}, config.licenseKey ? { licenseKey: config.licenseKey } : {}),
1585
- hidden: getFieldHiddenFunction("geoChecklist", config)
1586
- })),
1587
- fieldGroupMap
1588
- )
1589
- ],
1590
- ...config.metaTagsPreview === false ? [] : [
1591
- withGroup(
1592
- defineField(__spreadProps(__spreadValues({
1593
- name: "metaTagsPreview"
1594
- }, getFieldInfo("metaTagsPreview", config.fieldOverrides)), {
1595
- type: "string",
1596
- readOnly: true,
1597
- components: {
1598
- input: MetaTagsPreview_default
1599
- },
1600
- hidden: getFieldHiddenFunction("metaTagsPreview", config)
1601
- })),
1602
- fieldGroupMap
1603
- )
1604
- ],
2253
+ ...buildGeoChecklistField(config, fieldGroupMap),
2254
+ ...buildMetaTagsPreviewField(config, fieldGroupMap),
1605
2255
  withGroup(openGraph(config), fieldGroupMap),
1606
2256
  withGroup(twitter(config), fieldGroupMap)
1607
2257
  ]
1608
2258
  }));
1609
2259
  }
2260
+
2261
+ // src/schemas/types/baseMeta/index.ts
2262
+ import { defineField as defineField4, defineType as defineType4 } from "sanity";
1610
2263
  function baseMeta(config = {}) {
1611
- return defineType({
2264
+ return defineType4({
1612
2265
  name: "baseMeta",
1613
2266
  title: "Basic Meta Settings",
1614
2267
  type: "object",
1615
2268
  fields: [
1616
- defineField(__spreadProps(__spreadValues({
2269
+ defineField4(__spreadProps(__spreadValues({
1617
2270
  name: "title"
1618
2271
  }, getFieldInfo("title", config.fieldOverrides)), {
1619
2272
  type: "string",
@@ -1622,7 +2275,7 @@ function baseMeta(config = {}) {
1622
2275
  },
1623
2276
  hidden: getFieldHiddenFunction("title", config)
1624
2277
  })),
1625
- defineField(__spreadProps(__spreadValues({
2278
+ defineField4(__spreadProps(__spreadValues({
1626
2279
  name: "description"
1627
2280
  }, getFieldInfo("description", config.fieldOverrides)), {
1628
2281
  type: "text",
@@ -1632,7 +2285,7 @@ function baseMeta(config = {}) {
1632
2285
  },
1633
2286
  hidden: getFieldHiddenFunction("description", config)
1634
2287
  })),
1635
- defineField(__spreadProps(__spreadValues({
2288
+ defineField4(__spreadProps(__spreadValues({
1636
2289
  name: "metaImage"
1637
2290
  }, getFieldInfo("metaImage", config.fieldOverrides)), {
1638
2291
  type: "image",
@@ -1644,7 +2297,7 @@ function baseMeta(config = {}) {
1644
2297
  },
1645
2298
  hidden: getFieldHiddenFunction("metaImage", config)
1646
2299
  })),
1647
- defineField(__spreadProps(__spreadValues({
2300
+ defineField4(__spreadProps(__spreadValues({
1648
2301
  name: "keywords"
1649
2302
  }, getFieldInfo("keywords", config.fieldOverrides)), {
1650
2303
  title: "Keywords",
@@ -1653,7 +2306,7 @@ function baseMeta(config = {}) {
1653
2306
  description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
1654
2307
  hidden: getFieldHiddenFunction("keywords", config)
1655
2308
  })),
1656
- defineField(__spreadProps(__spreadValues({
2309
+ defineField4(__spreadProps(__spreadValues({
1657
2310
  name: "canonicalUrl"
1658
2311
  }, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
1659
2312
  title: "Canonical URL",
@@ -1661,7 +2314,7 @@ function baseMeta(config = {}) {
1661
2314
  description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page.",
1662
2315
  hidden: getFieldHiddenFunction("canonicalUrl", config)
1663
2316
  })),
1664
- defineField(__spreadProps(__spreadValues({
2317
+ defineField4(__spreadProps(__spreadValues({
1665
2318
  name: "metaAttributes"
1666
2319
  }, getFieldInfo("metaAttributes", config.fieldOverrides)), {
1667
2320
  type: "array",
@@ -1671,12 +2324,15 @@ function baseMeta(config = {}) {
1671
2324
  ]
1672
2325
  });
1673
2326
  }
1674
- var hreflang_default = defineType({
2327
+
2328
+ // src/schemas/types/hreflang/index.ts
2329
+ import { defineField as defineField5, defineType as defineType5 } from "sanity";
2330
+ var hreflang_default = defineType5({
1675
2331
  name: "hreflangEntry",
1676
2332
  title: "Hreflang Entry",
1677
2333
  type: "object",
1678
2334
  fields: [
1679
- defineField({
2335
+ defineField5({
1680
2336
  name: "locale",
1681
2337
  title: "Language / Region",
1682
2338
  type: "string",
@@ -1689,7 +2345,7 @@ var hreflang_default = defineType({
1689
2345
  return true;
1690
2346
  })
1691
2347
  }),
1692
- defineField({
2348
+ defineField5({
1693
2349
  name: "url",
1694
2350
  title: "URL",
1695
2351
  type: "url",
@@ -1703,17 +2359,20 @@ var hreflang_default = defineType({
1703
2359
  }
1704
2360
  }
1705
2361
  });
1706
- var metaAttribute_default = defineType({
2362
+
2363
+ // src/schemas/types/metaAttribute/index.ts
2364
+ import { defineField as defineField6, defineType as defineType6 } from "sanity";
2365
+ var metaAttribute_default = defineType6({
1707
2366
  name: "metaAttribute",
1708
2367
  title: "Meta Attribute",
1709
2368
  type: "object",
1710
2369
  fields: [
1711
- defineField({
2370
+ defineField6({
1712
2371
  name: "key",
1713
2372
  title: "Attribute Name",
1714
2373
  type: "string"
1715
2374
  }),
1716
- defineField({
2375
+ defineField6({
1717
2376
  name: "type",
1718
2377
  title: "Attribute Value Type",
1719
2378
  type: "string",
@@ -1725,13 +2384,13 @@ var metaAttribute_default = defineType({
1725
2384
  },
1726
2385
  initialValue: "string"
1727
2386
  }),
1728
- defineField({
2387
+ defineField6({
1729
2388
  name: "value",
1730
2389
  title: "Attribute Value",
1731
2390
  type: "string",
1732
2391
  hidden: ({ parent }) => (parent == null ? void 0 : parent.type) === "image"
1733
2392
  }),
1734
- defineField({
2393
+ defineField6({
1735
2394
  name: "image",
1736
2395
  title: "Attribute Image Value",
1737
2396
  type: "image",
@@ -1761,7 +2420,10 @@ var metaAttribute_default = defineType({
1761
2420
  }
1762
2421
  }
1763
2422
  });
1764
- var metaTag_default = defineType({
2423
+
2424
+ // src/schemas/types/metaTag/index.ts
2425
+ import { defineType as defineType7 } from "sanity";
2426
+ var metaTag_default = defineType7({
1765
2427
  name: "metaTag",
1766
2428
  title: "Meta Tag",
1767
2429
  type: "object",
@@ -1775,33 +2437,36 @@ var metaTag_default = defineType({
1775
2437
  }
1776
2438
  ]
1777
2439
  });
1778
- var robots_default = defineType({
2440
+
2441
+ // src/schemas/types/robots/index.ts
2442
+ import { defineField as defineField7, defineType as defineType8 } from "sanity";
2443
+ var robots_default = defineType8({
1779
2444
  name: "robots",
1780
2445
  title: "Robots Settings",
1781
2446
  type: "object",
1782
2447
  fields: [
1783
- defineField({
2448
+ defineField7({
1784
2449
  name: "noIndex",
1785
2450
  title: "No Index",
1786
2451
  type: "boolean",
1787
2452
  initialValue: false,
1788
2453
  description: "Enable this to prevent search engines from indexing this page. The page will not appear in search results."
1789
2454
  }),
1790
- defineField({
2455
+ defineField7({
1791
2456
  name: "noFollow",
1792
2457
  title: "No Follow",
1793
2458
  type: "boolean",
1794
2459
  initialValue: false,
1795
2460
  description: "Enable this to prevent search engines from following links on this page. Links will not pass SEO value."
1796
2461
  }),
1797
- defineField({
2462
+ defineField7({
1798
2463
  name: "noTranslate",
1799
2464
  title: "No Translate",
1800
2465
  type: "boolean",
1801
2466
  initialValue: false,
1802
2467
  description: "Enable this to prevent search engines from offering a translated version of this page in search results."
1803
2468
  }),
1804
- defineField({
2469
+ defineField7({
1805
2470
  name: "noImageIndex",
1806
2471
  title: "No Image Index",
1807
2472
  type: "boolean",
@@ -1898,11 +2563,11 @@ var seofields = definePlugin((config = {}) => {
1898
2563
  const cfg = config;
1899
2564
  const { healthDashboard = true, licenseKey, publishGate } = cfg;
1900
2565
  const dash = resolveDashboardConfig(healthDashboard, cfg.apiVersion, licenseKey);
1901
- const LazySeoHealthTool = lazy(() => import('./SeoHealthTool-3CUKGADZ.js'));
1902
- const BoundSeoHealthTool = () => createElement(
1903
- Suspense,
2566
+ const LazySeoHealthTool = lazy2(() => import("./SeoHealthTool-CWI3KB2V.js"));
2567
+ const BoundSeoHealthTool = () => createElement2(
2568
+ Suspense2,
1904
2569
  { fallback: null },
1905
- createElement(LazySeoHealthTool, {
2570
+ createElement2(LazySeoHealthTool, {
1906
2571
  icon: dash.icon,
1907
2572
  title: dash.title,
1908
2573
  description: dash.description,
@@ -1957,6 +2622,10 @@ var seofields = definePlugin((config = {}) => {
1957
2622
  });
1958
2623
  var plugin_default = seofields;
1959
2624
 
2625
+ // src/components/SeoHealthPane.tsx
2626
+ import { createSeoHealthPane } from "seofields-pro";
2627
+ import { createSeoHealthPaneDefault } from "seofields-pro";
2628
+
1960
2629
  // src/utils/scoring.ts
1961
2630
  var getStatusCategory = (score) => {
1962
2631
  if (score >= 80) return "excellent";
@@ -2025,6 +2694,50 @@ var scoreTwitterCard = (twitter2) => {
2025
2694
  }
2026
2695
  return { score, issues };
2027
2696
  };
2697
+ var scoreKeywordArray = (keywords, title, description) => {
2698
+ if (!keywords || keywords.length === 0) {
2699
+ return { score: 0, issues: ["No keywords defined"], stuffed: false };
2700
+ }
2701
+ const issues = [];
2702
+ let score = 2;
2703
+ if (hasMatchingKeyword(title || "", keywords) || hasMatchingKeyword(description || "", keywords)) {
2704
+ score += 3;
2705
+ } else {
2706
+ issues.push("Keywords defined but not used in title or description");
2707
+ }
2708
+ const stuffed = hasKeywordOveruse(title || "", keywords) || hasKeywordOveruse(description || "", keywords);
2709
+ if (stuffed) issues.push("Keyword stuffing detected \u2014 reduce repeated keyword usage");
2710
+ return { score, issues, stuffed };
2711
+ };
2712
+ var scoreFocusKeyword = (focusKeyword, title, description) => {
2713
+ const placement = getFocusKeywordPlacement(focusKeyword, title, description);
2714
+ if (!placement.hasFocusKeyword) {
2715
+ return { score: 0, issues: ["No focus keyword defined"], stuffed: false };
2716
+ }
2717
+ const issues = [];
2718
+ let score = 1;
2719
+ if (placement.atStartOfTitle) {
2720
+ score += 4;
2721
+ } else if (placement.inTitle) {
2722
+ score += 2;
2723
+ issues.push("Focus keyword is in the title but not at the start");
2724
+ } else if (placement.inDescription) {
2725
+ score += 1;
2726
+ issues.push("Focus keyword missing from meta title");
2727
+ } else {
2728
+ issues.push("Focus keyword missing from meta title and description");
2729
+ }
2730
+ if (placement.isStuffed)
2731
+ issues.push("Focus keyword appears too many times in title \u2014 avoid stuffing");
2732
+ return { score, issues, stuffed: placement.isStuffed };
2733
+ };
2734
+ var scoreKeywordOptimization = (keywords, focusKeyword, title, description) => {
2735
+ const arrayResult = scoreKeywordArray(keywords, title, description);
2736
+ const focusResult = scoreFocusKeyword(focusKeyword, title, description);
2737
+ let score = arrayResult.score + focusResult.score;
2738
+ if (arrayResult.stuffed || focusResult.stuffed) score = Math.max(0, score - 2);
2739
+ return { score: Math.min(score, 10), issues: [...arrayResult.issues, ...focusResult.issues] };
2740
+ };
2028
2741
  var calculateHealthScore = (doc) => {
2029
2742
  if (!doc.seo) {
2030
2743
  return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
@@ -2033,6 +2746,7 @@ var calculateHealthScore = (doc) => {
2033
2746
  const title = seo.title;
2034
2747
  const description = seo.description;
2035
2748
  const keywords = seo.keywords;
2749
+ const focusKeyword = seo.focusKeyword;
2036
2750
  const robots = seo.robots;
2037
2751
  const openGraph2 = seo.openGraph;
2038
2752
  const twitter2 = seo.twitter;
@@ -2046,8 +2760,9 @@ var calculateHealthScore = (doc) => {
2046
2760
  issues.push(...descResult.issues);
2047
2761
  if (seo.metaImage) score += 10;
2048
2762
  else issues.push("Missing meta image");
2049
- if (keywords && keywords.length > 0) score += 10;
2050
- else issues.push("No keywords defined");
2763
+ const keywordResult = scoreKeywordOptimization(keywords, focusKeyword, title, description);
2764
+ score += keywordResult.score;
2765
+ issues.push(...keywordResult.issues);
2051
2766
  if (robots && !robots.noIndex) score += 5;
2052
2767
  else if (!robots) score += 5;
2053
2768
  const ogResult = scoreOpenGraph(openGraph2);
@@ -2074,7 +2789,19 @@ var calculateHealthScore = (doc) => {
2074
2789
 
2075
2790
  // src/index.ts
2076
2791
  var src_default = plugin_default;
2077
-
2078
- export { types as allSchemas, baseMeta as baseMetaSchema, calculateHealthScore, src_default as default, metaAttribute_default as metaAttributeSchema, metaTag_default as metaTagSchema, openGraph as openGraphSchema, robots_default as robotsSchema, seoFieldsSchema, twitter as twitterSchema };
2079
- //# sourceMappingURL=index.js.map
2792
+ export {
2793
+ SchemaOrgJsonLdPreview as SchemaOrgDocumentJsonLdPreview,
2794
+ SchemaOrgJsonLdPreview,
2795
+ types as allSchemas,
2796
+ baseMeta as baseMetaSchema,
2797
+ calculateHealthScore,
2798
+ createSeoHealthPane,
2799
+ src_default as default,
2800
+ metaAttribute_default as metaAttributeSchema,
2801
+ metaTag_default as metaTagSchema,
2802
+ openGraph as openGraphSchema,
2803
+ robots_default as robotsSchema,
2804
+ seoFieldsSchema,
2805
+ twitter as twitterSchema
2806
+ };
2080
2807
  //# sourceMappingURL=index.js.map