sanity-plugin-seofields 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +437 -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-3OK3S2F7.cjs +673 -0
  11. package/dist/chunk-3OK3S2F7.cjs.map +1 -0
  12. package/dist/{chunk-B5IVI5LP.cjs → chunk-5XTQRILL.cjs} +286 -236
  13. package/dist/chunk-5XTQRILL.cjs.map +1 -0
  14. package/dist/{chunk-254YHUN3.cjs → chunk-7N4MLTMR.js} +8 -6
  15. package/dist/chunk-7N4MLTMR.js.map +1 -0
  16. package/dist/{chunk-DDAAVRWG.js → chunk-A57XNQC7.cjs} +9 -4
  17. package/dist/chunk-A57XNQC7.cjs.map +1 -0
  18. package/dist/chunk-BWLJDK5J.js +624 -0
  19. package/dist/chunk-BWLJDK5J.js.map +1 -0
  20. package/dist/chunk-FUUWBQE2.cjs +195 -0
  21. package/dist/chunk-FUUWBQE2.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-KIEO6QG3.js +195 -0
  25. package/dist/chunk-KIEO6QG3.js.map +1 -0
  26. package/dist/chunk-R2U7JF7U.cjs +624 -0
  27. package/dist/chunk-R2U7JF7U.cjs.map +1 -0
  28. package/dist/{chunk-VR44E62I.js → chunk-XZKQWV2H.js} +204 -33
  29. package/dist/chunk-XZKQWV2H.js.map +1 -0
  30. package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
  31. package/dist/chunk-Y46ACXM4.cjs.map +1 -0
  32. package/dist/chunk-Z5AOUU4H.js +673 -0
  33. package/dist/chunk-Z5AOUU4H.js.map +1 -0
  34. package/dist/cli.js +27 -27
  35. package/dist/{component-DEXwtemT.d.ts → component-CjT1hvxh.d.ts} +2 -2
  36. package/dist/{component-BzI-LHPw.d.cts → component-WYh0z8as.d.cts} +2 -2
  37. package/dist/define-cli.cjs +2 -4
  38. package/dist/define-cli.cjs.map +1 -1
  39. package/dist/define-cli.js +4 -4
  40. package/dist/define-cli.js.map +1 -1
  41. package/dist/head.cjs +14 -0
  42. package/dist/head.cjs.map +1 -0
  43. package/dist/head.d.cts +254 -0
  44. package/dist/head.d.ts +254 -0
  45. package/dist/head.js +14 -0
  46. package/dist/head.js.map +1 -0
  47. package/dist/index.cjs +978 -367
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +6 -374
  50. package/dist/index.d.ts +6 -374
  51. package/dist/index.js +920 -286
  52. package/dist/index.js.map +1 -1
  53. package/dist/next.cjs +204 -455
  54. package/dist/next.cjs.map +1 -1
  55. package/dist/next.d.cts +8 -206
  56. package/dist/next.d.ts +8 -206
  57. package/dist/next.js +181 -111
  58. package/dist/next.js.map +1 -1
  59. package/dist/plugin-DQdThyxG.d.ts +449 -0
  60. package/dist/plugin-DoGeZP79.d.cts +449 -0
  61. package/dist/schema/next.cjs +173 -327
  62. package/dist/schema/next.cjs.map +1 -1
  63. package/dist/schema/next.d.cts +4 -4
  64. package/dist/schema/next.d.ts +4 -4
  65. package/dist/schema/next.js +172 -8
  66. package/dist/schema/next.js.map +1 -1
  67. package/dist/schema.cjs +376 -517
  68. package/dist/schema.cjs.map +1 -1
  69. package/dist/schema.d.cts +5 -5
  70. package/dist/schema.d.ts +5 -5
  71. package/dist/schema.js +216 -11
  72. package/dist/schema.js.map +1 -1
  73. package/dist/server.cjs +149 -0
  74. package/dist/server.cjs.map +1 -0
  75. package/dist/server.d.cts +102 -0
  76. package/dist/server.d.ts +102 -0
  77. package/dist/server.js +149 -0
  78. package/dist/server.js.map +1 -0
  79. package/dist/{types-CW8qFdAn.d.ts → types-BAdN1RMu.d.ts} +2 -2
  80. package/dist/{types-B_vjMPtM.d.cts → types-C5i3KMgs.d.cts} +2 -2
  81. package/dist/{types-BXSCbl6p.d.cts → types-DP-DiW6f.d.cts} +2 -2
  82. package/dist/{types-C19cQx5D.d.ts → types-DRLUGLtX.d.ts} +2 -2
  83. package/dist/{types-yVmQfby9.d.cts → types-DxlPXihz.d.cts} +1 -1
  84. package/dist/{types-yVmQfby9.d.ts → types-DxlPXihz.d.ts} +1 -1
  85. package/package.json +25 -4
  86. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +0 -4
  87. package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +0 -1
  88. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +0 -10
  89. package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +0 -1
  90. package/dist/SeoHealthTool-2KKQJLJL.cjs +0 -11
  91. package/dist/SeoHealthTool-2KKQJLJL.cjs.map +0 -1
  92. package/dist/SeoHealthTool-3CUKGADZ.js +0 -5
  93. package/dist/SeoHealthTool-3CUKGADZ.js.map +0 -1
  94. package/dist/SeoPreview-3EXR6FD4.cjs.map +0 -1
  95. package/dist/SeoPreview-4PPAF4NO.js.map +0 -1
  96. package/dist/chunk-254YHUN3.cjs.map +0 -1
  97. package/dist/chunk-27XSYENH.js +0 -2215
  98. package/dist/chunk-27XSYENH.js.map +0 -1
  99. package/dist/chunk-B5IVI5LP.cjs.map +0 -1
  100. package/dist/chunk-BNXMFD3T.cjs +0 -431
  101. package/dist/chunk-BNXMFD3T.cjs.map +0 -1
  102. package/dist/chunk-DDAAVRWG.js.map +0 -1
  103. package/dist/chunk-HDZZQCH7.js.map +0 -1
  104. package/dist/chunk-P6IOWIO2.cjs +0 -480
  105. package/dist/chunk-P6IOWIO2.cjs.map +0 -1
  106. package/dist/chunk-UOCZFYYP.js +0 -407
  107. package/dist/chunk-UOCZFYYP.js.map +0 -1
  108. package/dist/chunk-VR44E62I.js.map +0 -1
  109. package/dist/chunk-WKXHX3GO.js +0 -424
  110. package/dist/chunk-WKXHX3GO.js.map +0 -1
  111. package/dist/chunk-XZDLJ2N6.cjs +0 -2224
  112. package/dist/chunk-XZDLJ2N6.cjs.map +0 -1
  113. package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,56 @@
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
+ analyzeReadability,
3
+ generateSeoText,
4
+ pickTestOutput
5
+ } from "./chunk-Z5AOUU4H.js";
6
+ import {
7
+ SchemaOrgJsonLdPreview,
8
+ tokenizeHtml
9
+ } from "./chunk-XZKQWV2H.js";
10
+ import {
11
+ getFocusKeywordPlacement,
12
+ getMetaDescriptionValidationMessages,
13
+ getMetaImageValidation,
14
+ getMetaTitleValidationMessages,
15
+ getOgDescriptionValidation,
16
+ getOgImageUrlValidation,
17
+ getOgImageValidation,
18
+ getOgTitleValidation,
19
+ getTwitterDescriptionValidation,
20
+ getTwitterImageUrlValidation,
21
+ getTwitterImageValidation,
22
+ getTwitterTitleValidation,
23
+ hasKeywordOveruse,
24
+ hasMatchingKeyword
25
+ } from "./chunk-HHO2AKAP.js";
26
+ import {
27
+ __spreadProps,
28
+ __spreadValues
29
+ } from "./chunk-7N4MLTMR.js";
12
30
 
31
+ // src/plugin.ts
32
+ import { createElement as createElement2, lazy as lazy2, Suspense as Suspense2 } from "react";
33
+ import { definePlugin } from "sanity";
34
+
35
+ // src/actions/SeoPublishGateAction.tsx
36
+ import { createSeoPublishGateAction } from "seofields-pro";
37
+
38
+ // src/schemas/index.ts
39
+ import { createElement, lazy, Suspense } from "react";
40
+ import {
41
+ defineField as defineField3,
42
+ defineType as defineType3
43
+ } from "sanity";
44
+
45
+ // src/components/geo/GEOChecklist.tsx
46
+ import { useMemo, useState } from "react";
47
+ import { useFormValue } from "sanity";
48
+ import {
49
+ buildGEOProChecks,
50
+ GEOChecklistProChecks,
51
+ GEOLockedTeaser
52
+ } from "seofields-pro";
53
+ import { jsx, jsxs } from "react/jsx-runtime";
13
54
  var GEOChecklist = (props) => {
14
55
  var _a;
15
56
  const { path, schemaType } = props;
@@ -339,23 +380,174 @@ function CheckRow({ check }) {
339
380
  );
340
381
  }
341
382
  var GEOChecklist_default = GEOChecklist;
383
+
384
+ // src/components/meta/FocusKeywordInput.tsx
385
+ import { Stack, Text as Text2 } from "@sanity/ui";
386
+ import { useMemo as useMemo2 } from "react";
387
+ import { PatchEvent, set, useFormValue as useFormValue3 } from "sanity";
388
+
389
+ // src/components/ai/SeoGenButton.tsx
390
+ import { SparklesIcon } from "@sanity/icons";
391
+ import { Button, Card, Flex, Text } from "@sanity/ui";
392
+ import { useCallback, useEffect, useRef, useState as useState2 } from "react";
393
+ import { useFormValue as useFormValue2, useWorkspace } from "sanity";
394
+
395
+ // src/utils/extractDocText.ts
396
+ function extractBlockText(block) {
397
+ if (block._type === "block" && Array.isArray(block.children)) {
398
+ return block.children.map((s) => {
399
+ var _a;
400
+ return (_a = s.text) != null ? _a : "";
401
+ }).join("");
402
+ }
403
+ const parts = [];
404
+ for (const key of Object.keys(block)) {
405
+ if (key === "_type" || key === "_key") continue;
406
+ const val = block[key];
407
+ if (typeof val === "string" && val.trim()) parts.push(val.trim());
408
+ else if (Array.isArray(val)) parts.push(extractArrayText(val));
409
+ }
410
+ return parts.filter(Boolean).join(" ");
411
+ }
412
+ function extractArrayText(arr) {
413
+ return arr.map((b) => extractBlockText(b)).filter(Boolean).join("\n");
414
+ }
415
+ function extractFieldText(value) {
416
+ if (typeof value === "string") return value;
417
+ if (!Array.isArray(value)) return "";
418
+ return extractArrayText(value).replace(/\n{3,}/g, "\n\n").trim();
419
+ }
420
+ function extractDocText(doc, bodyField) {
421
+ const fields = [bodyField].flat();
422
+ const text = fields.map((f) => extractFieldText(doc[f])).filter(Boolean).join("\n\n");
423
+ return text.slice(0, 4e3);
424
+ }
425
+ function resolveContentFields(content, docType) {
426
+ var _a;
427
+ if (!content || typeof content === "string" || Array.isArray(content)) return content != null ? content : "body";
428
+ const key = docType && docType in content ? docType : "default";
429
+ return (_a = content[key]) != null ? _a : "body";
430
+ }
431
+
432
+ // src/components/ai/SeoGenButton.tsx
433
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
434
+ function SeoGenButton({
435
+ field,
436
+ ai,
437
+ seoFieldPath,
438
+ onGenerate
439
+ }) {
440
+ var _a, _b, _c, _d, _e, _f, _g;
441
+ const [state, setState] = useState2("idle");
442
+ const [errorMsg, setErrorMsg] = useState2(null);
443
+ const abortRef = useRef(null);
444
+ const cooldownRef = useRef(null);
445
+ const rootDoc = useFormValue2([]);
446
+ const workspace = useWorkspace();
447
+ const seoObj = useFormValue2([seoFieldPath]);
448
+ const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
449
+ const keywords = (_b = seoObj == null ? void 0 : seoObj.keywords) != null ? _b : [];
450
+ const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
451
+ const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
452
+ const meta = field === "focusKeyword" ? {
453
+ title: (_c = seoObj == null ? void 0 : seoObj.title) != null ? _c : void 0,
454
+ description: (_d = seoObj == null ? void 0 : seoObj.description) != null ? _d : void 0,
455
+ slug: (_f = (_e = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _e.current) != null ? _f : void 0
456
+ } : void 0;
457
+ const handleGenerate = useCallback(async () => {
458
+ var _a2;
459
+ if (!ai) return;
460
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
461
+ if (cooldownRef.current) clearTimeout(cooldownRef.current);
462
+ setState("loading");
463
+ setErrorMsg(null);
464
+ try {
465
+ let result;
466
+ if (ai.testMode) {
467
+ await new Promise((r) => setTimeout(r, 400));
468
+ result = pickTestOutput(field);
469
+ } else {
470
+ const controller = new AbortController();
471
+ abortRef.current = controller;
472
+ result = await generateSeoText(field, content, focusKeyword, keywords, {
473
+ config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
474
+ signal: controller.signal,
475
+ meta
476
+ });
477
+ }
478
+ onGenerate(result);
479
+ setState("cooldown");
480
+ cooldownRef.current = setTimeout(() => setState("idle"), 2e3);
481
+ } catch (err) {
482
+ if (err.name === "AbortError") {
483
+ setState("idle");
484
+ return;
485
+ }
486
+ setErrorMsg(err instanceof Error ? err.message : "Generation failed.");
487
+ setState("error");
488
+ }
489
+ }, [ai, field, content, focusKeyword, keywords, meta, onGenerate, workspace.projectId]);
490
+ useEffect(() => {
491
+ return () => {
492
+ var _a2;
493
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
494
+ if (cooldownRef.current) clearTimeout(cooldownRef.current);
495
+ };
496
+ }, []);
497
+ const isConfigured = Boolean(
498
+ (ai == null ? void 0 : ai.apiKey) || (ai == null ? void 0 : ai.endpoint) || (ai == null ? void 0 : ai.testMode) || (ai == null ? void 0 : ai.provider) === "ollama"
499
+ );
500
+ if (!isConfigured) return null;
501
+ const isLoading = state === "loading";
502
+ const isTestMode = Boolean(ai == null ? void 0 : ai.testMode);
503
+ let buttonText = "Generate with AI";
504
+ if (isLoading) buttonText = "Generating\u2026";
505
+ else if (isTestMode) buttonText = "Generate (test)";
506
+ const isFullWidth = ((_g = ai == null ? void 0 : ai.buttonWidth) != null ? _g : "full") === "full";
507
+ return /* @__PURE__ */ jsxs2(Flex, { direction: "column", gap: 1, align: isFullWidth ? "stretch" : "flex-start", children: [
508
+ /* @__PURE__ */ jsx2(
509
+ Button,
510
+ {
511
+ mode: "ghost",
512
+ tone: "primary",
513
+ paddingX: isFullWidth ? 2 : 3,
514
+ paddingY: 3,
515
+ fontSize: 1,
516
+ icon: SparklesIcon,
517
+ text: buttonText,
518
+ onClick: handleGenerate,
519
+ disabled: isLoading || state === "cooldown"
520
+ }
521
+ ),
522
+ !content && /* @__PURE__ */ jsxs2(Text, { size: 0, muted: true, children: [
523
+ "(Set ",
524
+ /* @__PURE__ */ jsx2("code", { children: "ai.content" }),
525
+ " in plugin config for better results)"
526
+ ] }),
527
+ state === "error" && errorMsg && /* @__PURE__ */ jsx2(Card, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx2(Text, { size: 1, children: errorMsg }) })
528
+ ] });
529
+ }
530
+
531
+ // src/components/meta/FocusKeywordInput.tsx
532
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
342
533
  var FocusKeywordInput = (props) => {
343
534
  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;
535
+ const { value, renderDefault, path, schemaType, onChange } = props;
536
+ const { options } = schemaType;
537
+ const parent = useFormValue3([path[0]]);
538
+ const rootDoc = useFormValue3([]);
539
+ const feedbackItems = useMemo2(() => {
540
+ var _a2;
349
541
  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();
542
+ const title = (parent == null ? void 0 : parent.title) || "";
543
+ const description = (parent == null ? void 0 : parent.description) || "";
353
544
  const slug = ((_a2 = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a2.current) || "";
545
+ const placement = getFocusKeywordPlacement(value, title, description);
354
546
  const items = [];
355
547
  if (title) {
356
- if (title.startsWith(kw)) {
548
+ if (placement.atStartOfTitle) {
357
549
  items.push({ text: "Keyword at start of title \u2014 excellent", color: "green" });
358
- } else if (title.includes(kw)) {
550
+ } else if (placement.inTitle) {
359
551
  items.push({
360
552
  text: "Keyword in title \u2014 move it to the start for more prominence",
361
553
  color: "orange"
@@ -365,14 +557,14 @@ var FocusKeywordInput = (props) => {
365
557
  }
366
558
  }
367
559
  if (description) {
368
- if (description.includes(kw)) {
560
+ if (placement.inDescription) {
369
561
  items.push({ text: "Keyword in meta description", color: "green" });
370
562
  } else {
371
563
  items.push({ text: "Not in description \u2014 include it naturally", color: "orange" });
372
564
  }
373
565
  }
374
566
  if (slug) {
375
- const kwSlug = kw.replace(/\s+/g, "-");
567
+ const kwSlug = value.toLowerCase().replace(/\s+/g, "-");
376
568
  if (slug.includes(kwSlug)) {
377
569
  items.push({ text: "Keyword in URL slug", color: "green" });
378
570
  } else {
@@ -382,8 +574,7 @@ var FocusKeywordInput = (props) => {
382
574
  });
383
575
  }
384
576
  }
385
- const safeKw = kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
386
- if (((_b = title.match(new RegExp(safeKw, "g"))) != null ? _b : []).length > 2) {
577
+ if (placement.isStuffed) {
387
578
  items.push({
388
579
  text: "Keyword appears too many times in title \u2014 avoid stuffing",
389
580
  color: "red"
@@ -391,10 +582,19 @@ var FocusKeywordInput = (props) => {
391
582
  }
392
583
  return items;
393
584
  }, [value, parent == null ? void 0 : parent.title, parent == null ? void 0 : parent.description, (_a = rootDoc == null ? void 0 : rootDoc.slug) == null ? void 0 : _a.current]);
394
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
585
+ return /* @__PURE__ */ jsxs3(Stack, { space: 3, children: [
395
586
  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(
587
+ /* @__PURE__ */ jsx3(
588
+ SeoGenButton,
589
+ {
590
+ field: "focusKeyword",
591
+ ai: options == null ? void 0 : options.ai,
592
+ seoFieldPath: String(path[0]),
593
+ onGenerate: (v) => onChange(PatchEvent.from(set(v)))
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsx3(Stack, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
597
+ /* @__PURE__ */ jsx3(
398
598
  "div",
399
599
  {
400
600
  style: {
@@ -405,87 +605,276 @@ var FocusKeywordInput = (props) => {
405
605
  }
406
606
  }
407
607
  ),
408
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
608
+ /* @__PURE__ */ jsx3(Text2, { weight: "bold", muted: true, size: 14, children: item.text })
409
609
  ] }, item.text)) })
410
610
  ] });
411
611
  };
412
612
  var FocusKeywordInput_default = FocusKeywordInput;
413
613
 
414
- // src/utils/readability.ts
415
- function countSyllables(word) {
614
+ // src/components/meta/KeywordsInput.tsx
615
+ import { Stack as Stack3 } from "@sanity/ui";
616
+ import { PatchEvent as PatchEvent2, set as set2 } from "sanity";
617
+
618
+ // src/components/ai/KeywordSuggestions.tsx
619
+ import { SparklesIcon as SparklesIcon2 } from "@sanity/icons";
620
+ import { Badge, Box, Button as Button2, Card as Card2, Flex as Flex2, Spinner, Stack as Stack2, Text as Text3 } from "@sanity/ui";
621
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
622
+ import { useFormValue as useFormValue4, useWorkspace as useWorkspace2 } from "sanity";
623
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
624
+ function KeywordSuggestions({
625
+ ai,
626
+ seoFieldPath,
627
+ existing,
628
+ onAdd,
629
+ onAddAll
630
+ }) {
416
631
  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);
632
+ const [panelState, setPanelState] = useState3("idle");
633
+ const [suggestions, setSuggestions] = useState3([]);
634
+ const [errorMsg, setErrorMsg] = useState3(null);
635
+ const abortRef = useRef2(null);
636
+ const rootDoc = useFormValue4([]);
637
+ const workspace = useWorkspace2();
638
+ const seoObj = useFormValue4([seoFieldPath]);
639
+ const focusKeyword = (_a = seoObj == null ? void 0 : seoObj.focusKeyword) != null ? _a : "";
640
+ const bodyField = resolveContentFields(ai == null ? void 0 : ai.content, rootDoc == null ? void 0 : rootDoc._type);
641
+ const content = rootDoc ? extractDocText(rootDoc, bodyField) : "";
642
+ const handleSuggest = useCallback2(async () => {
643
+ var _a2;
644
+ if (!ai) return;
645
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
646
+ abortRef.current = new AbortController();
647
+ setPanelState("loading");
648
+ setErrorMsg(null);
649
+ setSuggestions([]);
650
+ try {
651
+ let csv;
652
+ if (ai.testMode) {
653
+ await new Promise((r) => setTimeout(r, 400));
654
+ csv = pickTestOutput("keywords");
655
+ } else {
656
+ csv = await generateSeoText("keywords", content, focusKeyword, existing, {
657
+ config: __spreadProps(__spreadValues({}, ai), { _projectId: workspace.projectId }),
658
+ signal: abortRef.current.signal
659
+ });
660
+ }
661
+ const parsed = csv.split(",").map((k) => k.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
662
+ if (parsed.length === 0) {
663
+ setErrorMsg("No keywords returned. Try again.");
664
+ setPanelState("error");
665
+ return;
666
+ }
667
+ setSuggestions(parsed);
668
+ setPanelState("suggestions");
669
+ } catch (err) {
670
+ if (err.name === "AbortError") {
671
+ setPanelState("idle");
672
+ return;
673
+ }
674
+ setErrorMsg(err instanceof Error ? err.message : "Keyword generation failed.");
675
+ setPanelState("error");
676
+ }
677
+ }, [ai, content, focusKeyword, existing, workspace.projectId]);
678
+ const handleAdd = useCallback2(
679
+ (kw) => {
680
+ onAdd(kw);
681
+ setSuggestions((prev) => prev.filter((s) => s !== kw));
682
+ if (suggestions.length <= 1) setPanelState("idle");
683
+ },
684
+ [onAdd, suggestions.length]
685
+ );
686
+ const handleAddAll = useCallback2(() => {
687
+ const toAdd = suggestions.filter((kw) => !existing.includes(kw));
688
+ if (toAdd.length > 0) onAddAll(toAdd);
689
+ setSuggestions([]);
690
+ setPanelState("idle");
691
+ }, [suggestions, existing, onAddAll]);
692
+ const handleDismiss = useCallback2(() => {
693
+ var _a2;
694
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
695
+ setSuggestions([]);
696
+ setPanelState("idle");
697
+ setErrorMsg(null);
698
+ }, []);
699
+ useEffect2(() => {
700
+ return () => {
701
+ var _a2;
702
+ (_a2 = abortRef.current) == null ? void 0 : _a2.abort();
703
+ };
704
+ }, []);
705
+ const isConfigured = Boolean(
706
+ (ai == null ? void 0 : ai.apiKey) || (ai == null ? void 0 : ai.endpoint) || (ai == null ? void 0 : ai.testMode) || (ai == null ? void 0 : ai.provider) === "ollama"
707
+ );
708
+ if (!isConfigured) return null;
709
+ if (panelState === "idle" || panelState === "error") {
710
+ return /* @__PURE__ */ jsxs4(Stack2, { space: 2, children: [
711
+ /* @__PURE__ */ jsx4(
712
+ Button2,
713
+ {
714
+ mode: "ghost",
715
+ tone: "primary",
716
+ paddingX: 2,
717
+ paddingY: 3,
718
+ fontSize: 1,
719
+ icon: SparklesIcon2,
720
+ text: (ai == null ? void 0 : ai.testMode) ? "Suggest Keywords (test)" : "Suggest Keywords with AI",
721
+ onClick: handleSuggest
722
+ }
723
+ ),
724
+ panelState === "error" && errorMsg && /* @__PURE__ */ jsx4(Card2, { padding: 2, tone: "critical", radius: 2, children: /* @__PURE__ */ jsx4(Text3, { size: 1, children: errorMsg }) })
725
+ ] });
423
726
  }
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";
727
+ if (panelState === "loading") {
728
+ return /* @__PURE__ */ jsxs4(Flex2, { align: "center", gap: 2, paddingY: 2, children: [
729
+ /* @__PURE__ */ jsx4(Spinner, { muted: true }),
730
+ /* @__PURE__ */ jsx4(Text3, { size: 1, muted: true, children: "Finding keywords\u2026" })
731
+ ] });
455
732
  }
456
- return {
457
- score,
458
- gradeLevel,
459
- avgWordsPerSentence,
460
- words,
461
- sentences,
462
- label,
463
- color
464
- };
733
+ const pendingSuggestions = suggestions.filter((kw) => !existing.includes(kw));
734
+ const alreadyAdded = suggestions.filter((kw) => existing.includes(kw));
735
+ return /* @__PURE__ */ jsx4(Card2, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ jsxs4(Stack2, { space: 3, children: [
736
+ /* @__PURE__ */ jsxs4(Flex2, { align: "center", justify: "space-between", children: [
737
+ /* @__PURE__ */ jsx4(Text3, { size: 1, weight: "semibold", muted: true, children: "AI keyword suggestions" }),
738
+ /* @__PURE__ */ jsxs4(Flex2, { gap: 2, children: [
739
+ pendingSuggestions.length > 1 && /* @__PURE__ */ jsx4(
740
+ Button2,
741
+ {
742
+ mode: "ghost",
743
+ tone: "primary",
744
+ paddingX: 2,
745
+ paddingY: 2,
746
+ fontSize: 1,
747
+ text: "Add all",
748
+ onClick: handleAddAll
749
+ }
750
+ ),
751
+ /* @__PURE__ */ jsx4(
752
+ Button2,
753
+ {
754
+ mode: "ghost",
755
+ tone: "default",
756
+ paddingX: 2,
757
+ paddingY: 2,
758
+ fontSize: 1,
759
+ text: "Dismiss",
760
+ onClick: handleDismiss
761
+ }
762
+ )
763
+ ] })
764
+ ] }),
765
+ pendingSuggestions.length > 0 && /* @__PURE__ */ jsxs4(Box, { children: [
766
+ /* @__PURE__ */ jsx4(Text3, { size: 0, muted: true, style: { marginBottom: 8 }, children: "Click to add:" }),
767
+ /* @__PURE__ */ jsx4(Flex2, { gap: 2, wrap: "wrap", style: { marginTop: 6 }, children: pendingSuggestions.map((kw) => /* @__PURE__ */ jsxs4(
768
+ "button",
769
+ {
770
+ type: "button",
771
+ onClick: () => handleAdd(kw),
772
+ style: {
773
+ padding: "4px 10px",
774
+ borderRadius: 20,
775
+ border: "1px solid var(--card-border-color)",
776
+ background: "transparent",
777
+ color: "var(--card-fg-color)",
778
+ cursor: "pointer",
779
+ fontSize: 12,
780
+ lineHeight: 1.4,
781
+ transition: "background 0.15s"
782
+ },
783
+ onMouseEnter: (e) => {
784
+ ;
785
+ e.currentTarget.style.background = "var(--card-border-color)";
786
+ },
787
+ onMouseLeave: (e) => {
788
+ ;
789
+ e.currentTarget.style.background = "transparent";
790
+ },
791
+ children: [
792
+ "+ ",
793
+ kw
794
+ ]
795
+ },
796
+ kw
797
+ )) })
798
+ ] }),
799
+ alreadyAdded.length > 0 && /* @__PURE__ */ jsxs4(Box, { children: [
800
+ /* @__PURE__ */ jsx4(Text3, { size: 0, muted: true, style: { marginBottom: 6 }, children: "Already added:" }),
801
+ /* @__PURE__ */ jsx4(Flex2, { gap: 2, wrap: "wrap", style: { marginTop: 4 }, children: alreadyAdded.map((kw) => /* @__PURE__ */ jsxs4(Badge, { tone: "positive", radius: 5, fontSize: 0, padding: 2, children: [
802
+ "\u2713 ",
803
+ kw
804
+ ] }, kw)) })
805
+ ] }),
806
+ pendingSuggestions.length === 0 && /* @__PURE__ */ jsx4(Text3, { size: 1, muted: true, children: "All suggested keywords added." })
807
+ ] }) });
465
808
  }
809
+
810
+ // src/components/meta/KeywordsInput.tsx
811
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
812
+ var KeywordsInput = (props) => {
813
+ const { value, renderDefault, path, schemaType, onChange } = props;
814
+ const { options } = schemaType;
815
+ const existing = value != null ? value : [];
816
+ const handleAdd = (kw) => {
817
+ if (existing.includes(kw)) return;
818
+ onChange(PatchEvent2.from(set2([...existing, kw])));
819
+ };
820
+ const handleAddAll = (keywords) => {
821
+ const merged = [.../* @__PURE__ */ new Set([...existing, ...keywords])];
822
+ onChange(PatchEvent2.from(set2(merged)));
823
+ };
824
+ return /* @__PURE__ */ jsxs5(Stack3, { space: 3, children: [
825
+ renderDefault(props),
826
+ /* @__PURE__ */ jsx5(
827
+ KeywordSuggestions,
828
+ {
829
+ ai: options == null ? void 0 : options.ai,
830
+ seoFieldPath: String(path[0]),
831
+ existing,
832
+ onAdd: handleAdd,
833
+ onAddAll: handleAddAll
834
+ }
835
+ )
836
+ ] });
837
+ };
838
+ var KeywordsInput_default = KeywordsInput;
839
+
840
+ // src/components/meta/MetaDescription.tsx
841
+ import { Stack as Stack4, Text as Text4 } from "@sanity/ui";
842
+ import { useMemo as useMemo3 } from "react";
843
+ import { PatchEvent as PatchEvent3, set as set3, useFormValue as useFormValue5 } from "sanity";
844
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
466
845
  var MetaDescription = (props) => {
467
- const { value, renderDefault, path } = props;
468
- const parent = useFormValue([path[0]]);
846
+ const { value, renderDefault, path, schemaType, onChange } = props;
847
+ const { options } = schemaType;
848
+ const parent = useFormValue5([path[0]]);
469
849
  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(
850
+ const keywords = useMemo3(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
851
+ const feedbackItems = useMemo3(
472
852
  () => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
473
853
  [value, keywords, isParentseoField]
474
854
  );
475
- const readability = useMemo(() => analyzeReadability(value || ""), [value]);
476
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
855
+ const readability = useMemo3(() => analyzeReadability(value || ""), [value]);
856
+ return /* @__PURE__ */ jsxs6(Stack4, { space: 3, children: [
477
857
  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(
858
+ /* @__PURE__ */ jsx6(
859
+ SeoGenButton,
860
+ {
861
+ field: "description",
862
+ ai: options == null ? void 0 : options.ai,
863
+ seoFieldPath: String(path[0]),
864
+ onGenerate: (v) => onChange(PatchEvent3.from(set3(v)))
865
+ }
866
+ ),
867
+ /* @__PURE__ */ jsx6(Stack4, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
868
+ /* @__PURE__ */ jsx6(
480
869
  "div",
481
870
  {
482
871
  style: { width: 10, height: 10, borderRadius: "50%", backgroundColor: item.color }
483
872
  }
484
873
  ),
485
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
874
+ /* @__PURE__ */ jsx6(Text4, { weight: "bold", muted: true, size: 14, children: item.text })
486
875
  ] }, item.text)) }),
487
- readability && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
488
- /* @__PURE__ */ jsx(
876
+ readability && /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
877
+ /* @__PURE__ */ jsx6(
489
878
  "div",
490
879
  {
491
880
  style: {
@@ -496,7 +885,7 @@ var MetaDescription = (props) => {
496
885
  }
497
886
  }
498
887
  ),
499
- /* @__PURE__ */ jsxs(Text, { weight: "bold", muted: true, size: 14, children: [
888
+ /* @__PURE__ */ jsxs6(Text4, { weight: "bold", muted: true, size: 14, children: [
500
889
  "Readability: ",
501
890
  readability.label,
502
891
  " (Flesch ",
@@ -510,18 +899,24 @@ var MetaDescription = (props) => {
510
899
  ] });
511
900
  };
512
901
  var MetaDescription_default = MetaDescription;
902
+
903
+ // src/components/meta/MetaImage.tsx
904
+ import { Stack as Stack5, Text as Text5 } from "@sanity/ui";
905
+ import { useMemo as useMemo4 } from "react";
906
+ import { useFormValue as useFormValue6 } from "sanity";
907
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
513
908
  var MetaImage = (props) => {
514
909
  const { value, renderDefault, path } = props;
515
- const seoParent = useFormValue([path[0]]);
910
+ const seoParent = useFormValue6([path[0]]);
516
911
  const hasImage = !!(value == null ? void 0 : value.asset);
517
- const feedbackItems = useMemo(
912
+ const feedbackItems = useMemo4(
518
913
  () => getMetaImageValidation(hasImage, seoParent),
519
914
  [hasImage, seoParent]
520
915
  );
521
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
916
+ return /* @__PURE__ */ jsxs7(Stack5, { space: 3, children: [
522
917
  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(
918
+ /* @__PURE__ */ jsx7(Stack5, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
919
+ /* @__PURE__ */ jsx7(
525
920
  "div",
526
921
  {
527
922
  style: {
@@ -532,11 +927,17 @@ var MetaImage = (props) => {
532
927
  }
533
928
  }
534
929
  ),
535
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
930
+ /* @__PURE__ */ jsx7(Text5, { weight: "bold", muted: true, size: 14, children: item.text })
536
931
  ] }, item.text)) })
537
932
  ] });
538
933
  };
539
934
  var MetaImage_default = MetaImage;
935
+
936
+ // src/components/meta/MetaTagsPreview.tsx
937
+ import { Box as Box2, Button as Button3, Card as Card3, Flex as Flex3, Stack as Stack6, Text as Text6 } from "@sanity/ui";
938
+ import { useMemo as useMemo5, useState as useState4 } from "react";
939
+ import { useClient, useColorScheme, useFormValue as useFormValue7 } from "sanity";
940
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
540
941
  function resolveImageUrl(asset, projectId, dataset) {
541
942
  const ref = asset == null ? void 0 : asset._ref;
542
943
  if (!ref) return null;
@@ -548,13 +949,13 @@ function resolveImageUrl(asset, projectId, dataset) {
548
949
  }
549
950
  var MetaTagsPreview = (props) => {
550
951
  const { path } = props;
551
- const [expanded, setExpanded] = useState(false);
552
- const [copyLabel, setCopyLabel] = useState("Copy");
952
+ const [expanded, setExpanded] = useState4(false);
953
+ const [copyLabel, setCopyLabel] = useState4("Copy");
553
954
  const { scheme } = useColorScheme();
554
- const seoFields = useFormValue([path[0]]);
955
+ const seoFields = useFormValue7([path[0]]);
555
956
  const client = useClient({ apiVersion: "2024-01-01" });
556
957
  const { projectId = "", dataset = "" } = client.config();
557
- const rawHtml = useMemo(() => {
958
+ const rawHtml = useMemo5(() => {
558
959
  var _a, _b, _c, _d, _e, _f, _g;
559
960
  const lines = [];
560
961
  lines.push('<meta charset="UTF-8" />');
@@ -610,8 +1011,8 @@ var MetaTagsPreview = (props) => {
610
1011
  }
611
1012
  return lines.join("\n");
612
1013
  }, [seoFields, projectId, dataset]);
613
- const tagCount = useMemo(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
614
- const tokens = useMemo(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
1014
+ const tagCount = useMemo5(() => rawHtml.split("\n").filter(Boolean).length, [rawHtml]);
1015
+ const tokens = useMemo5(() => tokenizeHtml(rawHtml, scheme), [rawHtml, scheme]);
615
1016
  const handleCopy = () => {
616
1017
  navigator.clipboard.writeText(rawHtml).then(() => {
617
1018
  setCopyLabel("Copied!");
@@ -624,16 +1025,16 @@ var MetaTagsPreview = (props) => {
624
1025
  const headerBg = isDark ? "#21262d" : "#f0f2f4";
625
1026
  const headerBorder = isDark ? "#30363d" : "#d0d7de";
626
1027
  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: [
1028
+ return /* @__PURE__ */ jsx8(Card3, { padding: 3, radius: 2, tone: "default", border: true, children: /* @__PURE__ */ jsxs8(Stack6, { space: 3, children: [
1029
+ /* @__PURE__ */ jsxs8(Flex3, { align: "center", justify: "space-between", gap: 3, children: [
1030
+ /* @__PURE__ */ jsx8(Text6, { weight: "semibold", size: 1, children: "Meta Tags Preview" }),
1031
+ /* @__PURE__ */ jsxs8(Flex3, { gap: 2, align: "center", children: [
1032
+ /* @__PURE__ */ jsxs8(Text6, { size: 0, muted: true, children: [
632
1033
  tagCount,
633
1034
  " tags generated"
634
1035
  ] }),
635
- /* @__PURE__ */ jsx(
636
- Button,
1036
+ /* @__PURE__ */ jsx8(
1037
+ Button3,
637
1038
  {
638
1039
  text: copyLabel,
639
1040
  tone: "default",
@@ -645,7 +1046,7 @@ var MetaTagsPreview = (props) => {
645
1046
  )
646
1047
  ] })
647
1048
  ] }),
648
- /* @__PURE__ */ jsxs(
1049
+ /* @__PURE__ */ jsxs8(
649
1050
  "button",
650
1051
  {
651
1052
  type: "button",
@@ -666,12 +1067,12 @@ var MetaTagsPreview = (props) => {
666
1067
  color: headerText
667
1068
  },
668
1069
  children: [
669
- /* @__PURE__ */ jsx("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
1070
+ /* @__PURE__ */ jsx8("span", { style: { fontSize: 10 }, children: expanded ? "\u25B2" : "\u25BC" }),
670
1071
  expanded ? "Hide tags" : "Show tags"
671
1072
  ]
672
1073
  }
673
1074
  ),
674
- expanded && /* @__PURE__ */ jsxs(
1075
+ expanded && /* @__PURE__ */ jsxs8(
675
1076
  "div",
676
1077
  {
677
1078
  style: {
@@ -680,7 +1081,7 @@ var MetaTagsPreview = (props) => {
680
1081
  overflow: "hidden"
681
1082
  },
682
1083
  children: [
683
- /* @__PURE__ */ jsxs(
1084
+ /* @__PURE__ */ jsxs8(
684
1085
  "div",
685
1086
  {
686
1087
  style: {
@@ -692,7 +1093,7 @@ var MetaTagsPreview = (props) => {
692
1093
  gap: 6
693
1094
  },
694
1095
  children: [
695
- /* @__PURE__ */ jsx(
1096
+ /* @__PURE__ */ jsx8(
696
1097
  "span",
697
1098
  {
698
1099
  style: {
@@ -704,12 +1105,12 @@ var MetaTagsPreview = (props) => {
704
1105
  }
705
1106
  }
706
1107
  ),
707
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
1108
+ /* @__PURE__ */ jsx8("span", { style: { fontSize: 11, color: headerText, fontFamily: "monospace" }, children: "HTML" })
708
1109
  ]
709
1110
  }
710
1111
  ),
711
- /* @__PURE__ */ jsx(
712
- Box,
1112
+ /* @__PURE__ */ jsx8(
1113
+ Box2,
713
1114
  {
714
1115
  as: "pre",
715
1116
  padding: 3,
@@ -724,7 +1125,7 @@ var MetaTagsPreview = (props) => {
724
1125
  wordBreak: "break-word",
725
1126
  backgroundColor: codeBg
726
1127
  },
727
- children: tokens.map((token, idx) => /* @__PURE__ */ jsx("span", { style: { color: token.color }, children: token.text }, idx))
1128
+ children: tokens.map((token, idx) => /* @__PURE__ */ jsx8("span", { style: { color: token.color }, children: token.text }, idx))
728
1129
  }
729
1130
  )
730
1131
  ]
@@ -733,20 +1134,26 @@ var MetaTagsPreview = (props) => {
733
1134
  ] }) });
734
1135
  };
735
1136
  var MetaTagsPreview_default = MetaTagsPreview;
1137
+
1138
+ // src/components/meta/MetaTitle.tsx
1139
+ import { Stack as Stack7, Text as Text7 } from "@sanity/ui";
1140
+ import { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
1141
+ import { PatchEvent as PatchEvent4, set as set4, useClient as useClient2, useFormValue as useFormValue8 } from "sanity";
1142
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
736
1143
  var MetaTitle = (props) => {
737
1144
  var _a, _b, _c;
738
- const { value, renderDefault, path, schemaType } = props;
1145
+ const { value, renderDefault, path, schemaType, onChange } = props;
739
1146
  const { options } = schemaType;
740
- const parent = useFormValue([path[0]]);
1147
+ const parent = useFormValue8([path[0]]);
741
1148
  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);
1149
+ const keywords = useMemo6(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1150
+ const rootDoc = (_a = useFormValue8([])) != null ? _a : {};
1151
+ const client = useClient2({ apiVersion: (_b = options == null ? void 0 : options.apiVersion) != null ? _b : "2024-01-01" });
1152
+ const [groqTitleSuffix, setGroqTitleSuffix] = useState5("");
1153
+ const [isDuplicateTitle, setIsDuplicateTitle] = useState5(false);
747
1154
  const titleSuffixQuery = options == null ? void 0 : options.titleSuffixQuery;
748
1155
  const titleSuffixOption = options == null ? void 0 : options.titleSuffix;
749
- useEffect(() => {
1156
+ useEffect3(() => {
750
1157
  if (!titleSuffixQuery) return;
751
1158
  client.fetch(titleSuffixQuery).then((result) => {
752
1159
  setGroqTitleSuffix(result === null || result === void 0 ? "" : String(result));
@@ -755,7 +1162,7 @@ var MetaTitle = (props) => {
755
1162
  });
756
1163
  }, [titleSuffixQuery, client]);
757
1164
  const currentDocId = (_c = rootDoc._id) != null ? _c : "";
758
- useEffect(() => {
1165
+ useEffect3(() => {
759
1166
  if (!(value == null ? void 0 : value.trim()) || !currentDocId) {
760
1167
  setIsDuplicateTitle(false);
761
1168
  return () => {
@@ -769,7 +1176,7 @@ var MetaTitle = (props) => {
769
1176
  }, 600);
770
1177
  return () => clearTimeout(timer);
771
1178
  }, [value, client, currentDocId]);
772
- const resolvedSuffix = useMemo(() => {
1179
+ const resolvedSuffix = useMemo6(() => {
773
1180
  if (titleSuffixQuery) return groqTitleSuffix;
774
1181
  if (!titleSuffixOption) return "";
775
1182
  if (typeof titleSuffixOption === "function") {
@@ -778,15 +1185,24 @@ var MetaTitle = (props) => {
778
1185
  return titleSuffixOption;
779
1186
  }, [titleSuffixQuery, groqTitleSuffix, titleSuffixOption, rootDoc]);
780
1187
  const suffixLength = resolvedSuffix ? resolvedSuffix.length + 3 : 0;
781
- const feedbackItems = useMemo(
1188
+ const feedbackItems = useMemo6(
782
1189
  () => getMetaTitleValidationMessages(value || "", keywords, isParentseoField, suffixLength),
783
1190
  [value, keywords, isParentseoField, suffixLength]
784
1191
  );
785
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1192
+ return /* @__PURE__ */ jsxs9(Stack7, { space: 3, children: [
786
1193
  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(
1194
+ /* @__PURE__ */ jsx9(
1195
+ SeoGenButton,
1196
+ {
1197
+ field: "title",
1198
+ ai: options == null ? void 0 : options.ai,
1199
+ seoFieldPath: String(path[0]),
1200
+ onGenerate: (v) => onChange(PatchEvent4.from(set4(v)))
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsxs9(Stack7, { space: 2, children: [
1204
+ feedbackItems.map((item) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1205
+ /* @__PURE__ */ jsx9(
790
1206
  "div",
791
1207
  {
792
1208
  style: {
@@ -797,10 +1213,10 @@ var MetaTitle = (props) => {
797
1213
  }
798
1214
  }
799
1215
  ),
800
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1216
+ /* @__PURE__ */ jsx9(Text7, { weight: "bold", muted: true, size: 14, children: item.text })
801
1217
  ] }, item.text)),
802
- isDuplicateTitle && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
803
- /* @__PURE__ */ jsx(
1218
+ isDuplicateTitle && /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1219
+ /* @__PURE__ */ jsx9(
804
1220
  "div",
805
1221
  {
806
1222
  style: {
@@ -811,7 +1227,7 @@ var MetaTitle = (props) => {
811
1227
  }
812
1228
  }
813
1229
  ),
814
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
1230
+ /* @__PURE__ */ jsx9(Text7, { weight: "bold", muted: true, size: 14, children: "Duplicate meta title \u2014 another published document uses this exact title." })
815
1231
  ] })
816
1232
  ] })
817
1233
  ] });
@@ -819,6 +1235,10 @@ var MetaTitle = (props) => {
819
1235
  var MetaTitle_default = MetaTitle;
820
1236
 
821
1237
  // src/utils/fieldsUtils.ts
1238
+ function withLicense(ai, licenseKey) {
1239
+ if (!ai) return void 0;
1240
+ return licenseKey ? __spreadProps(__spreadValues({}, ai), { _licenseKey: licenseKey }) : ai;
1241
+ }
822
1242
  var DEFAULT_FIELD_INFO = {
823
1243
  title: {
824
1244
  title: "Meta Title",
@@ -956,19 +1376,38 @@ var getFieldHiddenFunction = (fieldName, config) => {
956
1376
  var isEmpty = (value) => {
957
1377
  return value === null || value === void 0 || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0 || typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
958
1378
  };
1379
+
1380
+ // src/schemas/types/openGraph/index.ts
1381
+ import { defineField, defineType } from "sanity";
1382
+
1383
+ // src/components/openGraph/OgDescription.tsx
1384
+ import { Stack as Stack8, Text as Text8 } from "@sanity/ui";
1385
+ import { useMemo as useMemo7 } from "react";
1386
+ import { PatchEvent as PatchEvent5, set as set5, useFormValue as useFormValue9 } from "sanity";
1387
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
959
1388
  var OgDescription = (props) => {
960
- const { value, renderDefault, path } = props;
961
- const parent = useFormValue([path[0]]);
1389
+ const { value, renderDefault, path, schemaType, onChange } = props;
1390
+ const { options } = schemaType;
1391
+ const parent = useFormValue9([path[0]]);
962
1392
  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(
1393
+ const keywords = useMemo7(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1394
+ const feedbackItems = useMemo7(
965
1395
  () => getOgDescriptionValidation(value || "", keywords, isParentseoField),
966
1396
  [value, keywords, isParentseoField]
967
1397
  );
968
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1398
+ return /* @__PURE__ */ jsxs10(Stack8, { space: 3, children: [
969
1399
  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(
1400
+ /* @__PURE__ */ jsx10(
1401
+ SeoGenButton,
1402
+ {
1403
+ field: "ogDescription",
1404
+ ai: options == null ? void 0 : options.ai,
1405
+ seoFieldPath: String(path[0]),
1406
+ onGenerate: (v) => onChange(PatchEvent5.from(set5(v)))
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ jsx10(Stack8, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1410
+ /* @__PURE__ */ jsx10(
972
1411
  "div",
973
1412
  {
974
1413
  style: {
@@ -979,25 +1418,31 @@ var OgDescription = (props) => {
979
1418
  }
980
1419
  }
981
1420
  ),
982
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1421
+ /* @__PURE__ */ jsx10(Text8, { weight: "bold", muted: true, size: 14, children: item.text })
983
1422
  ] }, item.text)) })
984
1423
  ] });
985
1424
  };
986
1425
  var OgDescription_default = OgDescription;
1426
+
1427
+ // src/components/openGraph/OgImage.tsx
1428
+ import { Stack as Stack9, Text as Text9 } from "@sanity/ui";
1429
+ import { useMemo as useMemo8 } from "react";
1430
+ import { useFormValue as useFormValue10 } from "sanity";
1431
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
987
1432
  var OgImage = (props) => {
988
1433
  const { value, renderDefault, path } = props;
989
- const seoParent = useFormValue([path[0]]);
1434
+ const seoParent = useFormValue10([path[0]]);
990
1435
  const imgValue = value;
991
1436
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
992
1437
  const altText = imgValue == null ? void 0 : imgValue.alt;
993
- const feedbackItems = useMemo(
1438
+ const feedbackItems = useMemo8(
994
1439
  () => getOgImageValidation(hasImage, altText, seoParent),
995
1440
  [hasImage, altText, seoParent]
996
1441
  );
997
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1442
+ return /* @__PURE__ */ jsxs11(Stack9, { space: 3, children: [
998
1443
  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(
1444
+ /* @__PURE__ */ jsx11(Stack9, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs11("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1445
+ /* @__PURE__ */ jsx11(
1001
1446
  "div",
1002
1447
  {
1003
1448
  style: {
@@ -1008,19 +1453,25 @@ var OgImage = (props) => {
1008
1453
  }
1009
1454
  }
1010
1455
  ),
1011
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1456
+ /* @__PURE__ */ jsx11(Text9, { weight: "bold", muted: true, size: 14, children: item.text })
1012
1457
  ] }, item.text)) })
1013
1458
  ] });
1014
1459
  };
1015
1460
  var OgImage_default = OgImage;
1461
+
1462
+ // src/components/openGraph/OgImageUrl.tsx
1463
+ import { Stack as Stack10, Text as Text10 } from "@sanity/ui";
1464
+ import { useMemo as useMemo9 } from "react";
1465
+ import { useFormValue as useFormValue11 } from "sanity";
1466
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1016
1467
  var OgImageUrl = (props) => {
1017
1468
  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: [
1469
+ const seoParent = useFormValue11([path[0]]);
1470
+ const feedbackItems = useMemo9(() => getOgImageUrlValidation(value, seoParent), [value, seoParent]);
1471
+ return /* @__PURE__ */ jsxs12(Stack10, { space: 3, children: [
1021
1472
  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(
1473
+ /* @__PURE__ */ jsx12(Stack10, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1474
+ /* @__PURE__ */ jsx12(
1024
1475
  "div",
1025
1476
  {
1026
1477
  style: {
@@ -1031,24 +1482,40 @@ var OgImageUrl = (props) => {
1031
1482
  }
1032
1483
  }
1033
1484
  ),
1034
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1485
+ /* @__PURE__ */ jsx12(Text10, { weight: "bold", muted: true, size: 14, children: item.text })
1035
1486
  ] }, item.text)) })
1036
1487
  ] });
1037
1488
  };
1038
1489
  var OgImageUrl_default = OgImageUrl;
1490
+
1491
+ // src/components/openGraph/OgTitle.tsx
1492
+ import { Stack as Stack11, Text as Text11 } from "@sanity/ui";
1493
+ import { useMemo as useMemo10 } from "react";
1494
+ import { PatchEvent as PatchEvent6, set as set6, useFormValue as useFormValue12 } from "sanity";
1495
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1039
1496
  var OgTitle = (props) => {
1040
- const { value, renderDefault, path } = props;
1041
- const parent = useFormValue([path[0]]);
1497
+ const { value, renderDefault, path, schemaType, onChange } = props;
1498
+ const { options } = schemaType;
1499
+ const parent = useFormValue12([path[0]]);
1042
1500
  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(
1501
+ const keywords = useMemo10(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1502
+ const feedbackItems = useMemo10(
1045
1503
  () => getOgTitleValidation(value || "", keywords, isParentseoField),
1046
1504
  [value, keywords, isParentseoField]
1047
1505
  );
1048
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1506
+ return /* @__PURE__ */ jsxs13(Stack11, { space: 3, children: [
1049
1507
  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(
1508
+ /* @__PURE__ */ jsx13(
1509
+ SeoGenButton,
1510
+ {
1511
+ field: "ogTitle",
1512
+ ai: options == null ? void 0 : options.ai,
1513
+ seoFieldPath: String(path[0]),
1514
+ onGenerate: (v) => onChange(PatchEvent6.from(set6(v)))
1515
+ }
1516
+ ),
1517
+ /* @__PURE__ */ jsx13(Stack11, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1518
+ /* @__PURE__ */ jsx13(
1052
1519
  "div",
1053
1520
  {
1054
1521
  style: {
@@ -1059,7 +1526,7 @@ var OgTitle = (props) => {
1059
1526
  }
1060
1527
  }
1061
1528
  ),
1062
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1529
+ /* @__PURE__ */ jsx13(Text11, { weight: "bold", muted: true, size: 14, children: item.text })
1063
1530
  ] }, item.text)) })
1064
1531
  ] });
1065
1532
  };
@@ -1086,8 +1553,8 @@ function openGraph(config = {}) {
1086
1553
  hidden: getFieldHiddenFunction("openGraphTitle", config),
1087
1554
  components: {
1088
1555
  input: OgTitle_default
1089
- // Can also wrap with a string input + preview
1090
- }
1556
+ },
1557
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1091
1558
  })),
1092
1559
  defineField(__spreadProps(__spreadValues({
1093
1560
  name: "description"
@@ -1097,8 +1564,8 @@ function openGraph(config = {}) {
1097
1564
  hidden: getFieldHiddenFunction("openGraphDescription", config),
1098
1565
  components: {
1099
1566
  input: OgDescription_default
1100
- // Can also wrap with a text area + preview
1101
- }
1567
+ },
1568
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1102
1569
  })),
1103
1570
  defineField(__spreadProps(__spreadValues({
1104
1571
  name: "siteName"
@@ -1180,19 +1647,38 @@ function openGraph(config = {}) {
1180
1647
  ]
1181
1648
  });
1182
1649
  }
1650
+
1651
+ // src/schemas/types/twitter/index.ts
1652
+ import { defineField as defineField2, defineType as defineType2 } from "sanity";
1653
+
1654
+ // src/components/twitter/twitterDescription.tsx
1655
+ import { Stack as Stack12, Text as Text12 } from "@sanity/ui";
1656
+ import { useMemo as useMemo11 } from "react";
1657
+ import { PatchEvent as PatchEvent7, set as set7, useFormValue as useFormValue13 } from "sanity";
1658
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1183
1659
  var TwitterDescription = (props) => {
1184
- const { value, renderDefault, path } = props;
1185
- const parent = useFormValue([path[0]]);
1660
+ const { value, renderDefault, path, schemaType, onChange } = props;
1661
+ const { options } = schemaType;
1662
+ const parent = useFormValue13([path[0]]);
1186
1663
  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(
1664
+ const keywords = useMemo11(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1665
+ const feedbackItems = useMemo11(
1189
1666
  () => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
1190
1667
  [value, keywords, isParentseoField]
1191
1668
  );
1192
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1669
+ return /* @__PURE__ */ jsxs14(Stack12, { space: 3, children: [
1193
1670
  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(
1671
+ /* @__PURE__ */ jsx14(
1672
+ SeoGenButton,
1673
+ {
1674
+ field: "twitterDescription",
1675
+ ai: options == null ? void 0 : options.ai,
1676
+ seoFieldPath: String(path[0]),
1677
+ onGenerate: (v) => onChange(PatchEvent7.from(set7(v)))
1678
+ }
1679
+ ),
1680
+ /* @__PURE__ */ jsx14(Stack12, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1681
+ /* @__PURE__ */ jsx14(
1196
1682
  "div",
1197
1683
  {
1198
1684
  style: {
@@ -1203,25 +1689,31 @@ var TwitterDescription = (props) => {
1203
1689
  }
1204
1690
  }
1205
1691
  ),
1206
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1692
+ /* @__PURE__ */ jsx14(Text12, { weight: "bold", muted: true, size: 14, children: item.text })
1207
1693
  ] }, item.text)) })
1208
1694
  ] });
1209
1695
  };
1210
1696
  var twitterDescription_default = TwitterDescription;
1697
+
1698
+ // src/components/twitter/TwitterImage.tsx
1699
+ import { Stack as Stack13, Text as Text13 } from "@sanity/ui";
1700
+ import { useMemo as useMemo12 } from "react";
1701
+ import { useFormValue as useFormValue14 } from "sanity";
1702
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1211
1703
  var TwitterImage = (props) => {
1212
1704
  const { value, renderDefault, path } = props;
1213
- const seoParent = useFormValue([path[0]]);
1705
+ const seoParent = useFormValue14([path[0]]);
1214
1706
  const imgValue = value;
1215
1707
  const hasImage = !!(imgValue == null ? void 0 : imgValue.asset);
1216
1708
  const altText = imgValue == null ? void 0 : imgValue.alt;
1217
- const feedbackItems = useMemo(
1709
+ const feedbackItems = useMemo12(
1218
1710
  () => getTwitterImageValidation(hasImage, altText, seoParent),
1219
1711
  [hasImage, altText, seoParent]
1220
1712
  );
1221
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1713
+ return /* @__PURE__ */ jsxs15(Stack13, { space: 3, children: [
1222
1714
  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(
1715
+ /* @__PURE__ */ jsx15(Stack13, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs15("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1716
+ /* @__PURE__ */ jsx15(
1225
1717
  "div",
1226
1718
  {
1227
1719
  style: {
@@ -1232,22 +1724,28 @@ var TwitterImage = (props) => {
1232
1724
  }
1233
1725
  }
1234
1726
  ),
1235
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1727
+ /* @__PURE__ */ jsx15(Text13, { weight: "bold", muted: true, size: 14, children: item.text })
1236
1728
  ] }, item.text)) })
1237
1729
  ] });
1238
1730
  };
1239
1731
  var TwitterImage_default = TwitterImage;
1732
+
1733
+ // src/components/twitter/TwitterImageUrl.tsx
1734
+ import { Stack as Stack14, Text as Text14 } from "@sanity/ui";
1735
+ import { useMemo as useMemo13 } from "react";
1736
+ import { useFormValue as useFormValue15 } from "sanity";
1737
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1240
1738
  var TwitterImageUrl = (props) => {
1241
1739
  const { value, renderDefault, path } = props;
1242
- const seoParent = useFormValue([path[0]]);
1243
- const feedbackItems = useMemo(
1740
+ const seoParent = useFormValue15([path[0]]);
1741
+ const feedbackItems = useMemo13(
1244
1742
  () => getTwitterImageUrlValidation(value, seoParent),
1245
1743
  [value, seoParent]
1246
1744
  );
1247
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1745
+ return /* @__PURE__ */ jsxs16(Stack14, { space: 3, children: [
1248
1746
  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(
1747
+ /* @__PURE__ */ jsx16(Stack14, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs16("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1748
+ /* @__PURE__ */ jsx16(
1251
1749
  "div",
1252
1750
  {
1253
1751
  style: {
@@ -1258,24 +1756,40 @@ var TwitterImageUrl = (props) => {
1258
1756
  }
1259
1757
  }
1260
1758
  ),
1261
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1759
+ /* @__PURE__ */ jsx16(Text14, { weight: "bold", muted: true, size: 14, children: item.text })
1262
1760
  ] }, item.text)) })
1263
1761
  ] });
1264
1762
  };
1265
1763
  var TwitterImageUrl_default = TwitterImageUrl;
1764
+
1765
+ // src/components/twitter/twitterTitle.tsx
1766
+ import { Stack as Stack15, Text as Text15 } from "@sanity/ui";
1767
+ import { useMemo as useMemo14 } from "react";
1768
+ import { PatchEvent as PatchEvent8, set as set8, useFormValue as useFormValue16 } from "sanity";
1769
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1266
1770
  var TwitterTitle = (props) => {
1267
- const { value, renderDefault, path } = props;
1268
- const parent = useFormValue([path[0]]);
1771
+ const { value, renderDefault, path, schemaType, onChange } = props;
1772
+ const { options } = schemaType;
1773
+ const parent = useFormValue16([path[0]]);
1269
1774
  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(
1775
+ const keywords = useMemo14(() => (parent == null ? void 0 : parent.keywords) || [], [parent == null ? void 0 : parent.keywords]);
1776
+ const feedbackItems = useMemo14(
1272
1777
  () => getTwitterTitleValidation(value || "", keywords, isParentseoField),
1273
1778
  [value, keywords, isParentseoField]
1274
1779
  );
1275
- return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
1780
+ return /* @__PURE__ */ jsxs17(Stack15, { space: 3, children: [
1276
1781
  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(
1782
+ /* @__PURE__ */ jsx17(
1783
+ SeoGenButton,
1784
+ {
1785
+ field: "twitterTitle",
1786
+ ai: options == null ? void 0 : options.ai,
1787
+ seoFieldPath: String(path[0]),
1788
+ onGenerate: (v) => onChange(PatchEvent8.from(set8(v)))
1789
+ }
1790
+ ),
1791
+ /* @__PURE__ */ jsx17(Stack15, { space: 2, children: feedbackItems.map((item) => /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", gap: 7 }, children: [
1792
+ /* @__PURE__ */ jsx17(
1279
1793
  "div",
1280
1794
  {
1281
1795
  style: {
@@ -1286,7 +1800,7 @@ var TwitterTitle = (props) => {
1286
1800
  }
1287
1801
  }
1288
1802
  ),
1289
- /* @__PURE__ */ jsx(Text, { weight: "bold", muted: true, size: 14, children: item.text })
1803
+ /* @__PURE__ */ jsx17(Text15, { weight: "bold", muted: true, size: 14, children: item.text })
1290
1804
  ] }, item.text)) })
1291
1805
  ] });
1292
1806
  };
@@ -1294,12 +1808,12 @@ var twitterTitle_default = TwitterTitle;
1294
1808
 
1295
1809
  // src/schemas/types/twitter/index.ts
1296
1810
  function twitter(config = {}) {
1297
- return defineType({
1811
+ return defineType2({
1298
1812
  name: "twitter",
1299
1813
  title: "X (Formerly Twitter)",
1300
1814
  type: "object",
1301
1815
  fields: [
1302
- defineField(__spreadProps(__spreadValues({
1816
+ defineField2(__spreadProps(__spreadValues({
1303
1817
  name: "card"
1304
1818
  }, getFieldInfo("twitterCard", config.fieldOverrides)), {
1305
1819
  type: "string",
@@ -1315,28 +1829,29 @@ function twitter(config = {}) {
1315
1829
  initialValue: "summary_large_image"
1316
1830
  // good default
1317
1831
  })),
1318
- defineField(__spreadProps(__spreadValues({
1832
+ defineField2(__spreadProps(__spreadValues({
1319
1833
  name: "site"
1320
1834
  }, getFieldInfo("twitterSite", config.fieldOverrides)), {
1321
1835
  type: "string",
1322
1836
  hidden: getFieldHiddenFunction("twitterSite", config)
1323
1837
  })),
1324
- defineField(__spreadProps(__spreadValues({
1838
+ defineField2(__spreadProps(__spreadValues({
1325
1839
  name: "creator",
1326
1840
  type: "string"
1327
1841
  }, getFieldInfo("twitterCreator", config.fieldOverrides)), {
1328
1842
  hidden: getFieldHiddenFunction("twitterCreator", config)
1329
1843
  })),
1330
- defineField(__spreadProps(__spreadValues({
1844
+ defineField2(__spreadProps(__spreadValues({
1331
1845
  name: "title",
1332
1846
  type: "string"
1333
1847
  }, getFieldInfo("twitterTitle", config.fieldOverrides)), {
1334
1848
  hidden: getFieldHiddenFunction("twitterTitle", config),
1335
1849
  components: {
1336
1850
  input: twitterTitle_default
1337
- }
1851
+ },
1852
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1338
1853
  })),
1339
- defineField(__spreadProps(__spreadValues({
1854
+ defineField2(__spreadProps(__spreadValues({
1340
1855
  name: "description",
1341
1856
  type: "text",
1342
1857
  rows: 3
@@ -1344,9 +1859,10 @@ function twitter(config = {}) {
1344
1859
  hidden: getFieldHiddenFunction("twitterDescription", config),
1345
1860
  components: {
1346
1861
  input: twitterDescription_default
1347
- }
1862
+ },
1863
+ options: __spreadValues({}, config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {})
1348
1864
  })),
1349
- defineField(__spreadProps(__spreadValues({
1865
+ defineField2(__spreadProps(__spreadValues({
1350
1866
  name: "imageType"
1351
1867
  }, getFieldInfo("twitterImageType", config.fieldOverrides)), {
1352
1868
  type: "string",
@@ -1359,7 +1875,7 @@ function twitter(config = {}) {
1359
1875
  hidden: getFieldHiddenFunction("twitterImage", config),
1360
1876
  initialValue: "upload"
1361
1877
  })),
1362
- defineField(__spreadProps(__spreadValues({
1878
+ defineField2(__spreadProps(__spreadValues({
1363
1879
  name: "image"
1364
1880
  }, getFieldInfo("twitterImage", config.fieldOverrides)), {
1365
1881
  type: "image",
@@ -1370,7 +1886,7 @@ function twitter(config = {}) {
1370
1886
  input: TwitterImage_default
1371
1887
  },
1372
1888
  fields: [
1373
- defineField({
1889
+ defineField2({
1374
1890
  name: "alt",
1375
1891
  title: "Image Alt Text",
1376
1892
  type: "string",
@@ -1384,7 +1900,7 @@ function twitter(config = {}) {
1384
1900
  return typeof hiddenFn === "function" ? hiddenFn(context) : hiddenFn;
1385
1901
  }
1386
1902
  })),
1387
- defineField(__spreadProps(__spreadValues({
1903
+ defineField2(__spreadProps(__spreadValues({
1388
1904
  name: "imageUrl"
1389
1905
  }, getFieldInfo("twitterImageUrl", config.fieldOverrides)), {
1390
1906
  type: "url",
@@ -1403,7 +1919,7 @@ function twitter(config = {}) {
1403
1919
  }
1404
1920
 
1405
1921
  // src/schemas/index.ts
1406
- var LazySeoPreview = lazy(() => import('./SeoPreview-4PPAF4NO.js'));
1922
+ var LazySeoPreview = lazy(() => import("./SeoPreview-UNQBKTQO.js"));
1407
1923
  var SeoPreviewWrapper = (props) => createElement(Suspense, { fallback: null }, createElement(LazySeoPreview, props));
1408
1924
  function buildFieldGroupMap(groups) {
1409
1925
  const map = /* @__PURE__ */ new Map();
@@ -1438,18 +1954,86 @@ function withGroup(field, fieldGroupMap) {
1438
1954
  if (!groups || groups.length === 0) return field;
1439
1955
  return __spreadProps(__spreadValues({}, field), { group: groups.length === 1 ? groups[0] : groups });
1440
1956
  }
1957
+ function shouldShowPreviewField(seoPreview) {
1958
+ if (typeof seoPreview === "boolean") return seoPreview;
1959
+ if (typeof seoPreview === "object") return !isEmpty(seoPreview);
1960
+ return false;
1961
+ }
1962
+ function getSeoPreviewSuffixOptions(seoPreview) {
1963
+ if (typeof seoPreview !== "object" || !seoPreview) return {};
1964
+ const extra = {};
1965
+ if (seoPreview.titleSuffix) extra.titleSuffix = seoPreview.titleSuffix;
1966
+ if (seoPreview.titleSuffixQuery) extra.titleSuffixQuery = seoPreview.titleSuffixQuery;
1967
+ return extra;
1968
+ }
1969
+ function getPreviewFieldOptions(config) {
1970
+ const seoPreview = config.seoPreview;
1971
+ const extra = {};
1972
+ if (typeof seoPreview === "object" && seoPreview) {
1973
+ if (seoPreview.prefix) extra.prefix = seoPreview.prefix;
1974
+ if (seoPreview.titleSuffixInheritColor) {
1975
+ extra.titleSuffixInheritColor = seoPreview.titleSuffixInheritColor;
1976
+ }
1977
+ }
1978
+ return __spreadValues(__spreadValues(__spreadValues({
1979
+ baseUrl: config.baseUrl || "https://www.example.com"
1980
+ }, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(seoPreview)), extra);
1981
+ }
1982
+ function getAiOption(config) {
1983
+ return config.ai ? { ai: withLicense(config.ai, config.licenseKey) } : {};
1984
+ }
1985
+ function getTitleFieldOptions(config) {
1986
+ return __spreadValues(__spreadValues(__spreadValues({}, config.apiVersion ? { apiVersion: config.apiVersion } : {}), getSeoPreviewSuffixOptions(config.seoPreview)), getAiOption(config));
1987
+ }
1988
+ function buildGeoChecklistField(config, fieldGroupMap) {
1989
+ if (config.geo === false) return [];
1990
+ return [
1991
+ withGroup(
1992
+ defineField3(__spreadProps(__spreadValues({
1993
+ name: "geoChecklist"
1994
+ }, getFieldInfo("geoChecklist", config.fieldOverrides)), {
1995
+ type: "string",
1996
+ readOnly: true,
1997
+ components: {
1998
+ input: GEOChecklist_default
1999
+ },
2000
+ options: __spreadValues({}, config.licenseKey ? { licenseKey: config.licenseKey } : {}),
2001
+ hidden: getFieldHiddenFunction("geoChecklist", config)
2002
+ })),
2003
+ fieldGroupMap
2004
+ )
2005
+ ];
2006
+ }
2007
+ function buildMetaTagsPreviewField(config, fieldGroupMap) {
2008
+ if (config.metaTagsPreview === false) return [];
2009
+ return [
2010
+ withGroup(
2011
+ defineField3(__spreadProps(__spreadValues({
2012
+ name: "metaTagsPreview"
2013
+ }, getFieldInfo("metaTagsPreview", config.fieldOverrides)), {
2014
+ type: "string",
2015
+ readOnly: true,
2016
+ components: {
2017
+ input: MetaTagsPreview_default
2018
+ },
2019
+ hidden: getFieldHiddenFunction("metaTagsPreview", config)
2020
+ })),
2021
+ fieldGroupMap
2022
+ )
2023
+ ];
2024
+ }
1441
2025
  function seoFieldsSchema(config = {}) {
1442
2026
  const groupsCfg = config.fieldGroups;
1443
2027
  const fieldGroupMap = (groupsCfg == null ? void 0 : groupsCfg.length) ? buildFieldGroupMap(groupsCfg) : void 0;
1444
2028
  const sanityGroups = (groupsCfg == null ? void 0 : groupsCfg.length) ? toSanityGroups(groupsCfg) : void 0;
1445
- return defineType(__spreadProps(__spreadValues({
2029
+ return defineType3(__spreadProps(__spreadValues({
1446
2030
  name: "seoFields",
1447
2031
  title: "SEO Fields",
1448
2032
  type: "object"
1449
2033
  }, sanityGroups ? { groups: sanityGroups } : {}), {
1450
2034
  fields: [
1451
2035
  withGroup(
1452
- defineField({
2036
+ defineField3({
1453
2037
  name: "robots",
1454
2038
  title: "Robots Settings",
1455
2039
  type: "robots",
@@ -1458,16 +2042,14 @@ function seoFieldsSchema(config = {}) {
1458
2042
  fieldGroupMap
1459
2043
  ),
1460
2044
  // 👇 conditionally spread preview field
1461
- ...typeof config.seoPreview === "boolean" && config.seoPreview || typeof config.seoPreview === "object" && !isEmpty(config.seoPreview) ? [
2045
+ ...shouldShowPreviewField(config.seoPreview) ? [
1462
2046
  withGroup(
1463
- defineField({
2047
+ defineField3({
1464
2048
  name: "preview",
1465
2049
  title: "SEO Preview",
1466
2050
  type: "string",
1467
2051
  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 } : {}),
2052
+ options: getPreviewFieldOptions(config),
1471
2053
  initialValue: "",
1472
2054
  readOnly: true
1473
2055
  }),
@@ -1475,20 +2057,20 @@ function seoFieldsSchema(config = {}) {
1475
2057
  )
1476
2058
  ] : [],
1477
2059
  withGroup(
1478
- defineField(__spreadProps(__spreadValues({
2060
+ defineField3(__spreadProps(__spreadValues({
1479
2061
  name: "title"
1480
2062
  }, getFieldInfo("title", config.fieldOverrides)), {
1481
2063
  type: "string",
1482
2064
  components: {
1483
2065
  input: MetaTitle_default
1484
2066
  },
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 } : {}),
2067
+ options: getTitleFieldOptions(config),
1486
2068
  hidden: getFieldHiddenFunction("title", config)
1487
2069
  })),
1488
2070
  fieldGroupMap
1489
2071
  ),
1490
2072
  withGroup(
1491
- defineField(__spreadProps(__spreadValues({
2073
+ defineField3(__spreadProps(__spreadValues({
1492
2074
  name: "description"
1493
2075
  }, getFieldInfo("description", config.fieldOverrides)), {
1494
2076
  type: "text",
@@ -1496,12 +2078,13 @@ function seoFieldsSchema(config = {}) {
1496
2078
  components: {
1497
2079
  input: MetaDescription_default
1498
2080
  },
2081
+ options: getAiOption(config),
1499
2082
  hidden: getFieldHiddenFunction("description", config)
1500
2083
  })),
1501
2084
  fieldGroupMap
1502
2085
  ),
1503
2086
  withGroup(
1504
- defineField(__spreadProps(__spreadValues({
2087
+ defineField3(__spreadProps(__spreadValues({
1505
2088
  name: "metaImage"
1506
2089
  }, getFieldInfo("metaImage", config.fieldOverrides)), {
1507
2090
  type: "image",
@@ -1516,7 +2099,7 @@ function seoFieldsSchema(config = {}) {
1516
2099
  fieldGroupMap
1517
2100
  ),
1518
2101
  withGroup(
1519
- defineField(__spreadProps(__spreadValues({
2102
+ defineField3(__spreadProps(__spreadValues({
1520
2103
  name: "metaAttributes"
1521
2104
  }, getFieldInfo("metaAttributes", config.fieldOverrides)), {
1522
2105
  type: "array",
@@ -1526,19 +2109,21 @@ function seoFieldsSchema(config = {}) {
1526
2109
  fieldGroupMap
1527
2110
  ),
1528
2111
  withGroup(
1529
- defineField(__spreadProps(__spreadValues({
2112
+ defineField3(__spreadProps(__spreadValues({
1530
2113
  name: "keywords"
1531
2114
  }, getFieldInfo("keywords", config.fieldOverrides)), {
1532
2115
  title: "Keywords",
1533
2116
  type: "array",
1534
2117
  of: [{ type: "string" }],
1535
2118
  description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
2119
+ components: config.ai ? { input: KeywordsInput_default } : void 0,
2120
+ options: getAiOption(config),
1536
2121
  hidden: getFieldHiddenFunction("keywords", config)
1537
2122
  })),
1538
2123
  fieldGroupMap
1539
2124
  ),
1540
2125
  withGroup(
1541
- defineField(__spreadProps(__spreadValues({
2126
+ defineField3(__spreadProps(__spreadValues({
1542
2127
  name: "canonicalUrl"
1543
2128
  }, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
1544
2129
  title: "Canonical URL",
@@ -1549,19 +2134,20 @@ function seoFieldsSchema(config = {}) {
1549
2134
  fieldGroupMap
1550
2135
  ),
1551
2136
  withGroup(
1552
- defineField(__spreadProps(__spreadValues({
2137
+ defineField3(__spreadProps(__spreadValues({
1553
2138
  name: "focusKeyword"
1554
2139
  }, getFieldInfo("focusKeyword", config.fieldOverrides)), {
1555
2140
  type: "string",
1556
2141
  components: {
1557
2142
  input: FocusKeywordInput_default
1558
2143
  },
2144
+ options: getAiOption(config),
1559
2145
  hidden: getFieldHiddenFunction("focusKeyword", config)
1560
2146
  })),
1561
2147
  fieldGroupMap
1562
2148
  ),
1563
2149
  withGroup(
1564
- defineField(__spreadProps(__spreadValues({
2150
+ defineField3(__spreadProps(__spreadValues({
1565
2151
  name: "hreflangs"
1566
2152
  }, getFieldInfo("hreflangs", config.fieldOverrides)), {
1567
2153
  type: "array",
@@ -1571,49 +2157,23 @@ function seoFieldsSchema(config = {}) {
1571
2157
  })),
1572
2158
  fieldGroupMap
1573
2159
  ),
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
- ],
2160
+ ...buildGeoChecklistField(config, fieldGroupMap),
2161
+ ...buildMetaTagsPreviewField(config, fieldGroupMap),
1605
2162
  withGroup(openGraph(config), fieldGroupMap),
1606
2163
  withGroup(twitter(config), fieldGroupMap)
1607
2164
  ]
1608
2165
  }));
1609
2166
  }
2167
+
2168
+ // src/schemas/types/baseMeta/index.ts
2169
+ import { defineField as defineField4, defineType as defineType4 } from "sanity";
1610
2170
  function baseMeta(config = {}) {
1611
- return defineType({
2171
+ return defineType4({
1612
2172
  name: "baseMeta",
1613
2173
  title: "Basic Meta Settings",
1614
2174
  type: "object",
1615
2175
  fields: [
1616
- defineField(__spreadProps(__spreadValues({
2176
+ defineField4(__spreadProps(__spreadValues({
1617
2177
  name: "title"
1618
2178
  }, getFieldInfo("title", config.fieldOverrides)), {
1619
2179
  type: "string",
@@ -1622,7 +2182,7 @@ function baseMeta(config = {}) {
1622
2182
  },
1623
2183
  hidden: getFieldHiddenFunction("title", config)
1624
2184
  })),
1625
- defineField(__spreadProps(__spreadValues({
2185
+ defineField4(__spreadProps(__spreadValues({
1626
2186
  name: "description"
1627
2187
  }, getFieldInfo("description", config.fieldOverrides)), {
1628
2188
  type: "text",
@@ -1632,7 +2192,7 @@ function baseMeta(config = {}) {
1632
2192
  },
1633
2193
  hidden: getFieldHiddenFunction("description", config)
1634
2194
  })),
1635
- defineField(__spreadProps(__spreadValues({
2195
+ defineField4(__spreadProps(__spreadValues({
1636
2196
  name: "metaImage"
1637
2197
  }, getFieldInfo("metaImage", config.fieldOverrides)), {
1638
2198
  type: "image",
@@ -1644,7 +2204,7 @@ function baseMeta(config = {}) {
1644
2204
  },
1645
2205
  hidden: getFieldHiddenFunction("metaImage", config)
1646
2206
  })),
1647
- defineField(__spreadProps(__spreadValues({
2207
+ defineField4(__spreadProps(__spreadValues({
1648
2208
  name: "keywords"
1649
2209
  }, getFieldInfo("keywords", config.fieldOverrides)), {
1650
2210
  title: "Keywords",
@@ -1653,7 +2213,7 @@ function baseMeta(config = {}) {
1653
2213
  description: "Add relevant keywords for this page. These keywords will be used for SEO purposes.",
1654
2214
  hidden: getFieldHiddenFunction("keywords", config)
1655
2215
  })),
1656
- defineField(__spreadProps(__spreadValues({
2216
+ defineField4(__spreadProps(__spreadValues({
1657
2217
  name: "canonicalUrl"
1658
2218
  }, getFieldInfo("canonicalUrl", config.fieldOverrides)), {
1659
2219
  title: "Canonical URL",
@@ -1661,7 +2221,7 @@ function baseMeta(config = {}) {
1661
2221
  description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page.",
1662
2222
  hidden: getFieldHiddenFunction("canonicalUrl", config)
1663
2223
  })),
1664
- defineField(__spreadProps(__spreadValues({
2224
+ defineField4(__spreadProps(__spreadValues({
1665
2225
  name: "metaAttributes"
1666
2226
  }, getFieldInfo("metaAttributes", config.fieldOverrides)), {
1667
2227
  type: "array",
@@ -1671,12 +2231,15 @@ function baseMeta(config = {}) {
1671
2231
  ]
1672
2232
  });
1673
2233
  }
1674
- var hreflang_default = defineType({
2234
+
2235
+ // src/schemas/types/hreflang/index.ts
2236
+ import { defineField as defineField5, defineType as defineType5 } from "sanity";
2237
+ var hreflang_default = defineType5({
1675
2238
  name: "hreflangEntry",
1676
2239
  title: "Hreflang Entry",
1677
2240
  type: "object",
1678
2241
  fields: [
1679
- defineField({
2242
+ defineField5({
1680
2243
  name: "locale",
1681
2244
  title: "Language / Region",
1682
2245
  type: "string",
@@ -1689,7 +2252,7 @@ var hreflang_default = defineType({
1689
2252
  return true;
1690
2253
  })
1691
2254
  }),
1692
- defineField({
2255
+ defineField5({
1693
2256
  name: "url",
1694
2257
  title: "URL",
1695
2258
  type: "url",
@@ -1703,17 +2266,20 @@ var hreflang_default = defineType({
1703
2266
  }
1704
2267
  }
1705
2268
  });
1706
- var metaAttribute_default = defineType({
2269
+
2270
+ // src/schemas/types/metaAttribute/index.ts
2271
+ import { defineField as defineField6, defineType as defineType6 } from "sanity";
2272
+ var metaAttribute_default = defineType6({
1707
2273
  name: "metaAttribute",
1708
2274
  title: "Meta Attribute",
1709
2275
  type: "object",
1710
2276
  fields: [
1711
- defineField({
2277
+ defineField6({
1712
2278
  name: "key",
1713
2279
  title: "Attribute Name",
1714
2280
  type: "string"
1715
2281
  }),
1716
- defineField({
2282
+ defineField6({
1717
2283
  name: "type",
1718
2284
  title: "Attribute Value Type",
1719
2285
  type: "string",
@@ -1725,13 +2291,13 @@ var metaAttribute_default = defineType({
1725
2291
  },
1726
2292
  initialValue: "string"
1727
2293
  }),
1728
- defineField({
2294
+ defineField6({
1729
2295
  name: "value",
1730
2296
  title: "Attribute Value",
1731
2297
  type: "string",
1732
2298
  hidden: ({ parent }) => (parent == null ? void 0 : parent.type) === "image"
1733
2299
  }),
1734
- defineField({
2300
+ defineField6({
1735
2301
  name: "image",
1736
2302
  title: "Attribute Image Value",
1737
2303
  type: "image",
@@ -1761,7 +2327,10 @@ var metaAttribute_default = defineType({
1761
2327
  }
1762
2328
  }
1763
2329
  });
1764
- var metaTag_default = defineType({
2330
+
2331
+ // src/schemas/types/metaTag/index.ts
2332
+ import { defineType as defineType7 } from "sanity";
2333
+ var metaTag_default = defineType7({
1765
2334
  name: "metaTag",
1766
2335
  title: "Meta Tag",
1767
2336
  type: "object",
@@ -1775,33 +2344,36 @@ var metaTag_default = defineType({
1775
2344
  }
1776
2345
  ]
1777
2346
  });
1778
- var robots_default = defineType({
2347
+
2348
+ // src/schemas/types/robots/index.ts
2349
+ import { defineField as defineField7, defineType as defineType8 } from "sanity";
2350
+ var robots_default = defineType8({
1779
2351
  name: "robots",
1780
2352
  title: "Robots Settings",
1781
2353
  type: "object",
1782
2354
  fields: [
1783
- defineField({
2355
+ defineField7({
1784
2356
  name: "noIndex",
1785
2357
  title: "No Index",
1786
2358
  type: "boolean",
1787
2359
  initialValue: false,
1788
2360
  description: "Enable this to prevent search engines from indexing this page. The page will not appear in search results."
1789
2361
  }),
1790
- defineField({
2362
+ defineField7({
1791
2363
  name: "noFollow",
1792
2364
  title: "No Follow",
1793
2365
  type: "boolean",
1794
2366
  initialValue: false,
1795
2367
  description: "Enable this to prevent search engines from following links on this page. Links will not pass SEO value."
1796
2368
  }),
1797
- defineField({
2369
+ defineField7({
1798
2370
  name: "noTranslate",
1799
2371
  title: "No Translate",
1800
2372
  type: "boolean",
1801
2373
  initialValue: false,
1802
2374
  description: "Enable this to prevent search engines from offering a translated version of this page in search results."
1803
2375
  }),
1804
- defineField({
2376
+ defineField7({
1805
2377
  name: "noImageIndex",
1806
2378
  title: "No Image Index",
1807
2379
  type: "boolean",
@@ -1898,11 +2470,11 @@ var seofields = definePlugin((config = {}) => {
1898
2470
  const cfg = config;
1899
2471
  const { healthDashboard = true, licenseKey, publishGate } = cfg;
1900
2472
  const dash = resolveDashboardConfig(healthDashboard, cfg.apiVersion, licenseKey);
1901
- const LazySeoHealthTool = lazy(() => import('./SeoHealthTool-3CUKGADZ.js'));
1902
- const BoundSeoHealthTool = () => createElement(
1903
- Suspense,
2473
+ const LazySeoHealthTool = lazy2(() => import("./SeoHealthTool-CWI3KB2V.js"));
2474
+ const BoundSeoHealthTool = () => createElement2(
2475
+ Suspense2,
1904
2476
  { fallback: null },
1905
- createElement(LazySeoHealthTool, {
2477
+ createElement2(LazySeoHealthTool, {
1906
2478
  icon: dash.icon,
1907
2479
  title: dash.title,
1908
2480
  description: dash.description,
@@ -1957,6 +2529,10 @@ var seofields = definePlugin((config = {}) => {
1957
2529
  });
1958
2530
  var plugin_default = seofields;
1959
2531
 
2532
+ // src/components/SeoHealthPane.tsx
2533
+ import { createSeoHealthPane } from "seofields-pro";
2534
+ import { createSeoHealthPaneDefault } from "seofields-pro";
2535
+
1960
2536
  // src/utils/scoring.ts
1961
2537
  var getStatusCategory = (score) => {
1962
2538
  if (score >= 80) return "excellent";
@@ -2025,6 +2601,50 @@ var scoreTwitterCard = (twitter2) => {
2025
2601
  }
2026
2602
  return { score, issues };
2027
2603
  };
2604
+ var scoreKeywordArray = (keywords, title, description) => {
2605
+ if (!keywords || keywords.length === 0) {
2606
+ return { score: 0, issues: ["No keywords defined"], stuffed: false };
2607
+ }
2608
+ const issues = [];
2609
+ let score = 2;
2610
+ if (hasMatchingKeyword(title || "", keywords) || hasMatchingKeyword(description || "", keywords)) {
2611
+ score += 3;
2612
+ } else {
2613
+ issues.push("Keywords defined but not used in title or description");
2614
+ }
2615
+ const stuffed = hasKeywordOveruse(title || "", keywords) || hasKeywordOveruse(description || "", keywords);
2616
+ if (stuffed) issues.push("Keyword stuffing detected \u2014 reduce repeated keyword usage");
2617
+ return { score, issues, stuffed };
2618
+ };
2619
+ var scoreFocusKeyword = (focusKeyword, title, description) => {
2620
+ const placement = getFocusKeywordPlacement(focusKeyword, title, description);
2621
+ if (!placement.hasFocusKeyword) {
2622
+ return { score: 0, issues: ["No focus keyword defined"], stuffed: false };
2623
+ }
2624
+ const issues = [];
2625
+ let score = 1;
2626
+ if (placement.atStartOfTitle) {
2627
+ score += 4;
2628
+ } else if (placement.inTitle) {
2629
+ score += 2;
2630
+ issues.push("Focus keyword is in the title but not at the start");
2631
+ } else if (placement.inDescription) {
2632
+ score += 1;
2633
+ issues.push("Focus keyword missing from meta title");
2634
+ } else {
2635
+ issues.push("Focus keyword missing from meta title and description");
2636
+ }
2637
+ if (placement.isStuffed)
2638
+ issues.push("Focus keyword appears too many times in title \u2014 avoid stuffing");
2639
+ return { score, issues, stuffed: placement.isStuffed };
2640
+ };
2641
+ var scoreKeywordOptimization = (keywords, focusKeyword, title, description) => {
2642
+ const arrayResult = scoreKeywordArray(keywords, title, description);
2643
+ const focusResult = scoreFocusKeyword(focusKeyword, title, description);
2644
+ let score = arrayResult.score + focusResult.score;
2645
+ if (arrayResult.stuffed || focusResult.stuffed) score = Math.max(0, score - 2);
2646
+ return { score: Math.min(score, 10), issues: [...arrayResult.issues, ...focusResult.issues] };
2647
+ };
2028
2648
  var calculateHealthScore = (doc) => {
2029
2649
  if (!doc.seo) {
2030
2650
  return { score: 0, status: "missing", issues: ["SEO fields not configured"] };
@@ -2033,6 +2653,7 @@ var calculateHealthScore = (doc) => {
2033
2653
  const title = seo.title;
2034
2654
  const description = seo.description;
2035
2655
  const keywords = seo.keywords;
2656
+ const focusKeyword = seo.focusKeyword;
2036
2657
  const robots = seo.robots;
2037
2658
  const openGraph2 = seo.openGraph;
2038
2659
  const twitter2 = seo.twitter;
@@ -2046,8 +2667,9 @@ var calculateHealthScore = (doc) => {
2046
2667
  issues.push(...descResult.issues);
2047
2668
  if (seo.metaImage) score += 10;
2048
2669
  else issues.push("Missing meta image");
2049
- if (keywords && keywords.length > 0) score += 10;
2050
- else issues.push("No keywords defined");
2670
+ const keywordResult = scoreKeywordOptimization(keywords, focusKeyword, title, description);
2671
+ score += keywordResult.score;
2672
+ issues.push(...keywordResult.issues);
2051
2673
  if (robots && !robots.noIndex) score += 5;
2052
2674
  else if (!robots) score += 5;
2053
2675
  const ogResult = scoreOpenGraph(openGraph2);
@@ -2074,7 +2696,19 @@ var calculateHealthScore = (doc) => {
2074
2696
 
2075
2697
  // src/index.ts
2076
2698
  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
2699
+ export {
2700
+ SchemaOrgJsonLdPreview as SchemaOrgDocumentJsonLdPreview,
2701
+ SchemaOrgJsonLdPreview,
2702
+ types as allSchemas,
2703
+ baseMeta as baseMetaSchema,
2704
+ calculateHealthScore,
2705
+ createSeoHealthPane,
2706
+ src_default as default,
2707
+ metaAttribute_default as metaAttributeSchema,
2708
+ metaTag_default as metaTagSchema,
2709
+ openGraph as openGraphSchema,
2710
+ robots_default as robotsSchema,
2711
+ seoFieldsSchema,
2712
+ twitter as twitterSchema
2713
+ };
2080
2714
  //# sourceMappingURL=index.js.map