sanity-plugin-seofields 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { useClient, useFormValue, set, defineType, defineField, definePlugin } from "sanity";
1
+ import { useClient, useFormValue, defineType, defineField, definePlugin } from "sanity";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
- import { useEffect, useMemo } from "react";
3
+ import { useMemo } from "react";
4
4
  import { Stack, Text, Box } from "@sanity/ui";
5
5
  const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (title, keywordList) => {
6
6
  if (!title || keywordList.length === 0) return !1;
@@ -18,7 +18,7 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
18
18
  if (!title) return !1;
19
19
  const firstWord = title.trim().split(" ")[0].toLowerCase();
20
20
  return stopWords.includes(firstWord);
21
- }, truncate = (text, maxLength) => text.length > maxLength ? text.slice(0, maxLength) + "\u2026" : text, hasExcessivePunctuation = (title) => /[!@#$%^&*]{2,}/.test(title), getMetaTitleValidationMessages = (title, keywords) => {
21
+ }, truncate = (text, maxLength) => text.length > maxLength ? text.slice(0, maxLength) + "\u2026" : text, hasExcessivePunctuation = (title) => /[!@#$%^&*]{2,}/.test(title), getMetaTitleValidationMessages = (title, keywords, isParentseoField) => {
22
22
  const feedback = [], charCount = title?.length || 0;
23
23
  if (!title?.trim())
24
24
  return feedback.push({ text: "Meta Title is empty. Add content to improve SEO.", color: "red" }), feedback;
@@ -28,22 +28,23 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
28
28
  }) : charCount > 60 ? feedback.push({
29
29
  text: `Title is ${charCount} characters \u2014 exceeds recommended 60.`,
30
30
  color: "red"
31
- }) : feedback.push({ text: `Title length (${charCount}) looks good for SEO.`, color: "green" }), keywords.length > 0) {
32
- const hasKeyword = hasMatchingKeyword(title, keywords);
33
- feedback.push({
34
- text: hasKeyword ? "Keyword found in title \u2014 good job!" : "Keywords defined but missing in title.",
35
- color: hasKeyword ? "green" : "red"
36
- }), hasKeywordOveruse(title, keywords) && feedback.push({
37
- text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
38
- color: "orange"
39
- });
40
- } else
41
- feedback.push({
42
- text: "No keywords defined. Consider adding relevant keywords.",
43
- color: "orange"
44
- });
31
+ }) : feedback.push({ text: `Title length (${charCount}) looks good for SEO.`, color: "green" }), isParentseoField)
32
+ if (keywords.length > 0) {
33
+ const hasKeyword = hasMatchingKeyword(title, keywords);
34
+ feedback.push({
35
+ text: hasKeyword ? "Keyword found in title \u2014 good job!" : "Keywords defined but missing in title.",
36
+ color: hasKeyword ? "green" : "red"
37
+ }), hasKeywordOveruse(title, keywords) && feedback.push({
38
+ text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
39
+ color: "orange"
40
+ });
41
+ } else
42
+ feedback.push({
43
+ text: "No keywords defined. Consider adding relevant keywords.",
44
+ color: "orange"
45
+ });
45
46
  return startsWithStopWord(title) && feedback.push({ text: "Title starts with a stop word \u2014 consider rephrasing.", color: "orange" }), hasExcessivePunctuation(title) && feedback.push({ text: "Title contains excessive punctuation \u2014 simplify it.", color: "orange" }), feedback;
46
- }, getMetaDescriptionValidationMessages = (description, keywords) => {
47
+ }, getMetaDescriptionValidationMessages = (description, keywords, isParentseoField) => {
47
48
  const feedback = [], charCount = description?.length || 0;
48
49
  if (!description?.trim())
49
50
  return feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" }), feedback;
@@ -53,20 +54,21 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
53
54
  }) : charCount > 160 ? feedback.push({
54
55
  text: `Description is ${charCount} chars \u2014 exceeds recommended 160.`,
55
56
  color: "red"
56
- }) : feedback.push({ text: `Description length (${charCount}) looks good for SEO.`, color: "green" }), keywords.length > 0) {
57
- const hasKeyword = hasMatchingKeyword(description, keywords);
58
- feedback.push({
59
- text: hasKeyword ? "Keyword found in description \u2014 good job!" : "Keywords defined but missing in description.",
60
- color: hasKeyword ? "green" : "red"
61
- }), hasKeywordOveruse(description, keywords) && feedback.push({
62
- text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
63
- color: "orange"
64
- });
65
- } else
66
- feedback.push({
67
- text: "No keywords defined. Consider adding relevant keywords.",
68
- color: "orange"
69
- });
57
+ }) : feedback.push({ text: `Description length (${charCount}) looks good for SEO.`, color: "green" }), isParentseoField)
58
+ if (keywords.length > 0) {
59
+ const hasKeyword = hasMatchingKeyword(description, keywords);
60
+ feedback.push({
61
+ text: hasKeyword ? "Keyword found in description \u2014 good job!" : "Keywords defined but missing in description.",
62
+ color: hasKeyword ? "green" : "red"
63
+ }), hasKeywordOveruse(description, keywords) && feedback.push({
64
+ text: "Keyword appears too many times \u2014 avoid keyword stuffing.",
65
+ color: "orange"
66
+ });
67
+ } else
68
+ feedback.push({
69
+ text: "No keywords defined. Consider adding relevant keywords.",
70
+ color: "orange"
71
+ });
70
72
  return startsWithStopWord(description) && feedback.push({
71
73
  text: "Description starts with a stop word \u2014 consider rephrasing.",
72
74
  color: "orange"
@@ -74,32 +76,33 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
74
76
  text: "Description contains excessive punctuation \u2014 simplify it.",
75
77
  color: "orange"
76
78
  }), feedback;
77
- }, getOgTitleValidation = (title, keywords = []) => {
79
+ }, getOgTitleValidation = (title, keywords = [], isParentseoField) => {
78
80
  const feedback = [], count = title?.length || 0;
79
81
  if (!title?.trim())
80
82
  return feedback.push({ text: "OG Title is empty. Add content for better social preview.", color: "red" }), feedback;
81
83
  if (count < 40 ? feedback.push({
82
84
  text: `OG Title is ${count} chars \u2014 shorter than recommended 40.`,
83
85
  color: "orange"
84
- }) : count > 60 ? feedback.push({ text: `OG Title is ${count} chars \u2014 exceeds recommended 60.`, color: "red" }) : feedback.push({ text: `OG Title length (${count}) looks good.`, color: "green" }), keywords.length > 0) {
85
- const hasKeyword = hasMatchingKeyword(title, keywords);
86
- feedback.push({
87
- text: hasKeyword ? "Keyword found in OG title \u2014 good job!" : "Keywords defined but missing in OG title.",
88
- color: hasKeyword ? "green" : "red"
89
- }), hasKeywordOveruse(title, keywords) && feedback.push({
90
- text: "Keyword appears too many times in OG title \u2014 avoid keyword stuffing.",
91
- color: "orange"
92
- });
93
- } else
94
- feedback.push({
95
- text: "No keywords defined. Consider adding relevant keywords.",
96
- color: "orange"
97
- });
86
+ }) : count > 60 ? feedback.push({ text: `OG Title is ${count} chars \u2014 exceeds recommended 60.`, color: "red" }) : feedback.push({ text: `OG Title length (${count}) looks good.`, color: "green" }), isParentseoField)
87
+ if (keywords.length > 0) {
88
+ const hasKeyword = hasMatchingKeyword(title, keywords);
89
+ feedback.push({
90
+ text: hasKeyword ? "Keyword found in OG title \u2014 good job!" : "Keywords defined but missing in OG title.",
91
+ color: hasKeyword ? "green" : "red"
92
+ }), hasKeywordOveruse(title, keywords) && feedback.push({
93
+ text: "Keyword appears too many times in OG title \u2014 avoid keyword stuffing.",
94
+ color: "orange"
95
+ });
96
+ } else
97
+ feedback.push({
98
+ text: "No keywords defined. Consider adding relevant keywords.",
99
+ color: "orange"
100
+ });
98
101
  return startsWithStopWord(title) && feedback.push({
99
102
  text: "OG Title starts with a stop word \u2014 consider rephrasing.",
100
103
  color: "orange"
101
104
  }), hasExcessivePunctuation(title) && feedback.push({ text: "OG Title contains excessive punctuation \u2014 simplify it.", color: "orange" }), feedback;
102
- }, getOgDescriptionValidation = (desc, keywords = []) => {
105
+ }, getOgDescriptionValidation = (desc, keywords = [], isParentseoField) => {
103
106
  const feedback = [], count = desc?.length || 0;
104
107
  if (!desc?.trim())
105
108
  return feedback.push({
@@ -112,20 +115,21 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
112
115
  }) : count > 120 ? feedback.push({
113
116
  text: `OG Description is ${count} chars \u2014 exceeds recommended 120.`,
114
117
  color: "red"
115
- }) : feedback.push({ text: `OG Description length (${count}) looks good.`, color: "green" }), keywords.length > 0) {
116
- const hasKeyword = hasMatchingKeyword(desc, keywords);
117
- feedback.push({
118
- text: hasKeyword ? "Keyword found in OG description \u2014 good job!" : "Keywords defined but missing in OG description.",
119
- color: hasKeyword ? "green" : "red"
120
- }), hasKeywordOveruse(desc, keywords) && feedback.push({
121
- text: "Keyword appears too many times in OG description \u2014 avoid keyword stuffing.",
122
- color: "orange"
123
- });
124
- } else
125
- feedback.push({
126
- text: "No keywords defined. Consider adding relevant keywords.",
127
- color: "orange"
128
- });
118
+ }) : feedback.push({ text: `OG Description length (${count}) looks good.`, color: "green" }), isParentseoField)
119
+ if (keywords.length > 0) {
120
+ const hasKeyword = hasMatchingKeyword(desc, keywords);
121
+ feedback.push({
122
+ text: hasKeyword ? "Keyword found in OG description \u2014 good job!" : "Keywords defined but missing in OG description.",
123
+ color: hasKeyword ? "green" : "red"
124
+ }), hasKeywordOveruse(desc, keywords) && feedback.push({
125
+ text: "Keyword appears too many times in OG description \u2014 avoid keyword stuffing.",
126
+ color: "orange"
127
+ });
128
+ } else
129
+ feedback.push({
130
+ text: "No keywords defined. Consider adding relevant keywords.",
131
+ color: "orange"
132
+ });
129
133
  return startsWithStopWord(desc) && feedback.push({
130
134
  text: "OG Description starts with a stop word \u2014 consider rephrasing.",
131
135
  color: "orange"
@@ -133,7 +137,7 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
133
137
  text: "OG Description contains excessive punctuation \u2014 simplify it.",
134
138
  color: "orange"
135
139
  }), feedback;
136
- }, getTwitterTitleValidation = (title, keywords = []) => {
140
+ }, getTwitterTitleValidation = (title, keywords = [], isParentseoField) => {
137
141
  const feedback = [], count = title?.length || 0;
138
142
  if (!title?.trim())
139
143
  return feedback.push({ text: "Twitter Title is empty. Add content for better SEO.", color: "red" }), feedback;
@@ -143,19 +147,20 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
143
147
  }) : count > 70 ? feedback.push({
144
148
  text: `Twitter Title is ${count} chars \u2014 exceeds recommended 70.`,
145
149
  color: "red"
146
- }) : feedback.push({ text: `Twitter Title length (${count}) looks good.`, color: "green" }), keywords.length > 0) {
147
- const hasKeyword = hasMatchingKeyword(title, keywords);
148
- feedback.push({
149
- text: hasKeyword ? "Keyword found in Twitter title \u2014 good job!" : "Keywords defined but missing in Twitter title.",
150
- color: hasKeyword ? "green" : "red"
151
- });
152
- } else
153
- feedback.push({
154
- text: "No keywords defined. Consider adding relevant keywords.",
155
- color: "orange"
156
- });
150
+ }) : feedback.push({ text: `Twitter Title length (${count}) looks good.`, color: "green" }), isParentseoField)
151
+ if (keywords.length > 0) {
152
+ const hasKeyword = hasMatchingKeyword(title, keywords);
153
+ feedback.push({
154
+ text: hasKeyword ? "Keyword found in Twitter title \u2014 good job!" : "Keywords defined but missing in Twitter title.",
155
+ color: hasKeyword ? "green" : "red"
156
+ });
157
+ } else
158
+ feedback.push({
159
+ text: "No keywords defined. Consider adding relevant keywords.",
160
+ color: "orange"
161
+ });
157
162
  return /[!@#$%^&*]{2,}/.test(title) && feedback.push({ text: "Twitter Title has excessive punctuation \u2014 simplify it.", color: "orange" }), feedback;
158
- }, getTwitterDescriptionValidation = (desc, keywords = []) => {
163
+ }, getTwitterDescriptionValidation = (desc, keywords = [], isParentseoField) => {
159
164
  const feedback = [], count = desc?.length || 0;
160
165
  if (!desc?.trim())
161
166
  return feedback.push({ text: "Twitter Description is empty. Add content for better SEO.", color: "red" }), feedback;
@@ -165,32 +170,27 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
165
170
  }) : count > 200 ? feedback.push({
166
171
  text: `Twitter Description is ${count} chars \u2014 exceeds recommended 200.`,
167
172
  color: "red"
168
- }) : feedback.push({ text: `Twitter Description length (${count}) looks good.`, color: "green" }), keywords.length > 0) {
169
- const hasKeyword = hasMatchingKeyword(desc, keywords);
170
- feedback.push({
171
- text: hasKeyword ? "Keyword found in Twitter description \u2014 good job!" : "Keywords defined but missing in Twitter description.",
172
- color: hasKeyword ? "green" : "red"
173
- });
174
- } else
175
- feedback.push({
176
- text: "No keywords defined. Consider adding relevant keywords.",
177
- color: "orange"
178
- });
173
+ }) : feedback.push({ text: `Twitter Description length (${count}) looks good.`, color: "green" }), isParentseoField)
174
+ if (keywords.length > 0) {
175
+ const hasKeyword = hasMatchingKeyword(desc, keywords);
176
+ feedback.push({
177
+ text: hasKeyword ? "Keyword found in Twitter description \u2014 good job!" : "Keywords defined but missing in Twitter description.",
178
+ color: hasKeyword ? "green" : "red"
179
+ });
180
+ } else
181
+ feedback.push({
182
+ text: "No keywords defined. Consider adding relevant keywords.",
183
+ color: "orange"
184
+ });
179
185
  return /[!@#$%^&*]{2,}/.test(desc) && feedback.push({
180
186
  text: "Twitter Description has excessive punctuation \u2014 simplify it.",
181
187
  color: "orange"
182
188
  }), feedback;
183
189
  }, MetaTitle = (props) => {
184
- const client = useClient({ apiVersion: "2024-05-05" }), { value, onChange, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [];
185
- useEffect(() => {
186
- value || (async () => {
187
- const data = await client.fetch("*[_type=='homePage'][0]{'title':seo.title}");
188
- data?.title && !value && onChange(set(data.title));
189
- })();
190
- }, [client, onChange, value]);
191
- const feedbackItems = useMemo(
192
- () => getMetaTitleValidationMessages(value || "", keywords),
193
- [value, keywords]
190
+ useClient({ apiVersion: "2024-05-05" });
191
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
192
+ () => getMetaTitleValidationMessages(value || "", keywords, isParentseoField),
193
+ [value, keywords, isParentseoField]
194
194
  );
195
195
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
196
196
  renderDefault(props),
@@ -210,16 +210,10 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
210
210
  ] }, item.text)) })
211
211
  ] });
212
212
  }, MetaDescription = (props) => {
213
- const client = useClient({ apiVersion: "2024-05-05" }), { value, onChange, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [];
214
- useEffect(() => {
215
- value || (async () => {
216
- const data = await client.fetch("*[_type=='homePage'][0]{'description':seo.description}");
217
- data?.description && !value && onChange(set(data.description));
218
- })();
219
- }, [client, onChange, value]);
220
- const feedbackItems = useMemo(
221
- () => getMetaDescriptionValidationMessages(value || "", keywords),
222
- [value, keywords]
213
+ useClient({ apiVersion: "2024-05-05" });
214
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
215
+ () => getMetaDescriptionValidationMessages(value || "", keywords, isParentseoField),
216
+ [value, keywords, isParentseoField]
223
217
  );
224
218
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
225
219
  renderDefault(props),
@@ -284,91 +278,133 @@ const stopWords = ["the", "a", "an", "and", "or", "but"], hasMatchingKeyword = (
284
278
  ] })
285
279
  }
286
280
  );
281
+ }, DEFAULT_FIELD_INFO = {
282
+ title: {
283
+ title: "Meta Title",
284
+ description: "The meta title is displayed in search engine results as the clickable headline for a given result. It should be concise and accurately reflect the content of the page."
285
+ },
286
+ description: {
287
+ title: "Meta Description",
288
+ description: "Provide a concise summary of the page content. This description may be used by search engines in search results."
289
+ },
290
+ metaImage: {
291
+ title: "Meta Image",
292
+ description: "Upload an image that represents the content of the page. This image may be used in social media previews and search engine results."
293
+ },
294
+ keywords: {
295
+ title: "Keywords",
296
+ description: "Add relevant keywords for this page. These keywords will be used for SEO purposes."
297
+ },
298
+ canonicalUrl: {
299
+ title: "Canonical URL",
300
+ description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page."
301
+ },
302
+ robots: {
303
+ title: "Robots Settings",
304
+ description: "Configure how search engine crawlers should index and follow links on this page."
305
+ },
306
+ metaAttributes: {
307
+ title: "Additional Meta Attributes",
308
+ description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
309
+ }
310
+ }, getFieldInfo = (fieldName, fieldOverrides) => {
311
+ const fieldInfo = fieldOverrides && fieldOverrides[fieldName] || DEFAULT_FIELD_INFO[fieldName];
312
+ return fieldInfo ? { title: fieldInfo.title, description: fieldInfo.description } : { title: "", description: "" };
287
313
  };
288
- var seoFields = defineType({
289
- name: "seoFields",
290
- title: "SEO Fields",
291
- type: "object",
292
- fields: [
293
- defineField({
294
- name: "robots",
295
- title: "Robots Settings",
296
- type: "robots"
297
- // Use the separate robots type here
298
- }),
299
- defineField({
300
- name: "preview",
301
- title: "SEO Preview",
302
- type: "string",
303
- components: {
304
- input: SeoPreview
305
- // custom React component
306
- },
307
- // you can mark it read-only if you don't want editors to change it
308
- readOnly: !0
309
- }),
310
- defineField({
311
- name: "title",
312
- title: "Meta Title",
313
- type: "string",
314
- description: "The meta title is displayed in search engine results as the clickable headline for a given result. It should be concise and accurately reflect the content of the page.",
315
- components: {
316
- input: MetaTitle
317
- }
318
- // validation: (Rule) => Rule.max(60).warning('Meta title should be under 60 characters.'),
319
- }),
320
- defineField({
321
- name: "description",
322
- title: "Meta Description",
323
- type: "text",
324
- rows: 3,
325
- description: "Provide a concise summary of the page content. This description may be used by search engines in search results.",
326
- components: {
327
- input: MetaDescription
328
- }
329
- // validation: (Rule) => Rule.max(160).warning('Meta description should be under 160 characters.'),
330
- }),
331
- defineField({
332
- name: "metaImage",
333
- title: "Meta Image",
334
- type: "image",
335
- options: {
336
- hotspot: !0
337
- },
338
- description: "Upload an image that represents the content of the page. This image may be used in social media previews and search engine results."
339
- }),
340
- defineField({
341
- name: "metaAttributes",
342
- title: "Additional Meta Attributes",
343
- type: "array",
344
- of: [{ type: "metaAttribute" }],
345
- description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
346
- }),
347
- defineField({
348
- name: "keywords",
349
- title: "Keywords",
350
- type: "array",
351
- of: [{ type: "string" }],
352
- description: "Add relevant keywords for this page. These keywords will be used for SEO purposes."
353
- }),
354
- defineField({
355
- name: "canonicalUrl",
356
- title: "Canonical URL",
357
- type: "url",
358
- description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page."
359
- }),
360
- defineField({
361
- name: "openGraph",
362
- title: "Open Graph Settings",
363
- type: "openGraph"
364
- }),
365
- defineField({
366
- name: "twitter",
367
- title: "Twitter Card Settings",
368
- type: "twitter"
369
- })
370
- ]
371
- }), metaAttribute = defineType({
314
+ function seoFieldsSchema(config = {}) {
315
+ return defineType({
316
+ name: "seoFields",
317
+ title: "SEO Fields",
318
+ type: "object",
319
+ fields: [
320
+ defineField({
321
+ name: "robots",
322
+ title: "Robots Settings",
323
+ type: "robots"
324
+ // Use the separate robots type here
325
+ }),
326
+ // 👇 conditionally spread preview field
327
+ ...config.seoPreview ? [] : [
328
+ defineField({
329
+ name: "preview",
330
+ title: "SEO Preview",
331
+ type: "string",
332
+ components: { input: SeoPreview },
333
+ readOnly: !0
334
+ })
335
+ ],
336
+ defineField({
337
+ name: "title",
338
+ ...getFieldInfo("title", config.fieldOverrides),
339
+ // title: 'Meta Title',
340
+ type: "string",
341
+ // description:
342
+ // 'The meta title is displayed in search engine results as the clickable headline for a given result. It should be concise and accurately reflect the content of the page.',
343
+ components: {
344
+ input: MetaTitle
345
+ }
346
+ // validation: (Rule) => Rule.max(60).warning('Meta title should be under 60 characters.'),
347
+ }),
348
+ defineField({
349
+ name: "description",
350
+ ...getFieldInfo("description", config.fieldOverrides),
351
+ // title: 'Meta Description',
352
+ // description:
353
+ // 'Provide a concise summary of the page content. This description may be used by search engines in search results.',
354
+ type: "text",
355
+ rows: 3,
356
+ components: {
357
+ input: MetaDescription
358
+ }
359
+ // validation: (Rule) => Rule.max(160).warning('Meta description should be under 160 characters.'),
360
+ }),
361
+ defineField({
362
+ name: "metaImage",
363
+ ...getFieldInfo("metaImage", config.fieldOverrides),
364
+ // title: 'Meta Image',
365
+ // description:
366
+ // 'Upload an image that represents the content of the page. This image may be used in social media previews and search engine results.',
367
+ type: "image",
368
+ options: {
369
+ hotspot: !0
370
+ }
371
+ }),
372
+ defineField({
373
+ name: "metaAttributes",
374
+ title: "Additional Meta Attributes",
375
+ type: "array",
376
+ of: [{ type: "metaAttribute" }],
377
+ description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
378
+ }),
379
+ defineField({
380
+ name: "keywords",
381
+ ...getFieldInfo("keywords", config.fieldOverrides),
382
+ title: "Keywords",
383
+ type: "array",
384
+ of: [{ type: "string" }],
385
+ description: "Add relevant keywords for this page. These keywords will be used for SEO purposes."
386
+ }),
387
+ defineField({
388
+ name: "canonicalUrl",
389
+ ...getFieldInfo("canonicalUrl", config.fieldOverrides),
390
+ title: "Canonical URL",
391
+ type: "url",
392
+ description: "Specify the canonical URL for this page. This helps prevent duplicate content issues by indicating the preferred version of a page."
393
+ }),
394
+ defineField({
395
+ name: "openGraph",
396
+ title: "Open Graph Settings",
397
+ type: "openGraph"
398
+ }),
399
+ defineField({
400
+ name: "twitter",
401
+ title: "Twitter Card Settings",
402
+ type: "twitter"
403
+ })
404
+ ]
405
+ });
406
+ }
407
+ var metaAttribute = defineType({
372
408
  name: "metaAttribute",
373
409
  title: "Meta Attribute",
374
410
  type: "object",
@@ -423,8 +459,8 @@ var seoFields = defineType({
423
459
  }
424
460
  });
425
461
  const OgTitle = (props) => {
426
- const { value, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [], feedbackItems = useMemo(
427
- () => getOgTitleValidation(value || "", keywords),
462
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
463
+ () => getOgTitleValidation(value || "", keywords, isParentseoField),
428
464
  [value, keywords]
429
465
  );
430
466
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
@@ -445,9 +481,9 @@ const OgTitle = (props) => {
445
481
  ] }, item.text)) })
446
482
  ] });
447
483
  }, OgDescription = (props) => {
448
- const { value, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [], feedbackItems = useMemo(
449
- () => getOgDescriptionValidation(value || "", keywords),
450
- [value, keywords]
484
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
485
+ () => getOgDescriptionValidation(value || "", keywords, isParentseoField),
486
+ [value, keywords, isParentseoField]
451
487
  );
452
488
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
453
489
  renderDefault(props),
@@ -549,8 +585,8 @@ var openGraph = defineType({
549
585
  ]
550
586
  });
551
587
  const TwitterTitle = (props) => {
552
- const { value, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [], feedbackItems = useMemo(
553
- () => getTwitterTitleValidation(value || "", keywords),
588
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
589
+ () => getTwitterTitleValidation(value || "", keywords, isParentseoField),
554
590
  [value, keywords]
555
591
  );
556
592
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
@@ -571,8 +607,8 @@ const TwitterTitle = (props) => {
571
607
  ] }, item.text)) })
572
608
  ] });
573
609
  }, TwitterDescription = (props) => {
574
- const { value, renderDefault, path } = props, keywords = useFormValue([path[0]])?.keywords || [], feedbackItems = useMemo(
575
- () => getTwitterDescriptionValidation(value || "", keywords),
610
+ const { value, renderDefault, path } = props, parent = useFormValue([path[0]]), isParentseoField = parent && parent?._type === "seoFields", keywords = parent?.keywords || [], feedbackItems = useMemo(
611
+ () => getTwitterDescriptionValidation(value || "", keywords, isParentseoField),
576
612
  [value, keywords]
577
613
  );
578
614
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
@@ -690,10 +726,24 @@ var twitter = defineType({
690
726
  description: "Add custom meta attributes to the head of the document for additional SEO and social media integration."
691
727
  }
692
728
  ]
693
- }), types = [seoFields, openGraph, metaAttribute, twitter, robots, metaTag];
729
+ });
730
+ function types(config = {}) {
731
+ return [
732
+ seoFieldsSchema(config),
733
+ // pass config here
734
+ openGraph,
735
+ twitter,
736
+ metaAttribute,
737
+ metaTag,
738
+ robots
739
+ ];
740
+ }
694
741
  const seofields = definePlugin((config = {}) => ({
695
742
  name: "sanity-plugin-seofields",
696
- schema: { types }
743
+ schema: {
744
+ types: types(config)
745
+ // pass config down to schemas
746
+ }
697
747
  })), isSeoFields = (obj) => obj && obj._type === "seoFields", isOpenGraphSettings = (obj) => obj && obj._type === "openGraph", isTwitterCardSettings = (obj) => obj && obj._type === "twitter", isMetaAttribute = (obj) => obj && obj._type === "metaAttribute", defaultSeoValidationRules = {
698
748
  title: {
699
749
  maxLength: 70,
@@ -718,6 +768,7 @@ const seofields = definePlugin((config = {}) => ({
718
768
  };
719
769
  export {
720
770
  types as allSchemas,
771
+ seofields as default,
721
772
  defaultSeoValidationRules,
722
773
  isMetaAttribute,
723
774
  isOpenGraphSettings,
@@ -726,8 +777,8 @@ export {
726
777
  metaAttribute as metaAttributeSchema,
727
778
  metaTag as metaTagSchema,
728
779
  openGraph as openGraphSchema,
729
- seoFields as seoFieldsSchema,
730
- seofields,
780
+ robots as robotsSchema,
781
+ seoFieldsSchema,
731
782
  twitter as twitterSchema
732
783
  };
733
784
  //# sourceMappingURL=index.mjs.map