sanity-plugin-seofields 1.2.4 → 1.2.6

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 (49) hide show
  1. package/dist/index.cjs +2604 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +422 -0
  4. package/dist/index.d.ts +339 -492
  5. package/dist/index.js +1284 -2013
  6. package/dist/index.js.map +1 -1
  7. package/dist/next.cjs +182 -0
  8. package/dist/next.cjs.map +1 -0
  9. package/dist/next.d.cts +241 -0
  10. package/dist/next.d.ts +202 -295
  11. package/dist/next.js +110 -70
  12. package/dist/next.js.map +1 -1
  13. package/dist/types-B91ena4g.d.cts +89 -0
  14. package/dist/types-B91ena4g.d.ts +89 -0
  15. package/package.json +37 -18
  16. package/dist/index.d.mts +0 -575
  17. package/dist/index.mjs +0 -3292
  18. package/dist/index.mjs.map +0 -1
  19. package/dist/next.d.mts +0 -334
  20. package/dist/next.mjs +0 -102
  21. package/dist/next.mjs.map +0 -1
  22. package/sanity.json +0 -8
  23. package/src/components/SeoHealthDashboard.tsx +0 -1568
  24. package/src/components/SeoHealthPane.tsx +0 -81
  25. package/src/components/SeoHealthTool.tsx +0 -11
  26. package/src/components/SeoPreview.tsx +0 -178
  27. package/src/components/meta/MetaDescription.tsx +0 -39
  28. package/src/components/meta/MetaTitle.tsx +0 -44
  29. package/src/components/openGraph/OgDescription.tsx +0 -46
  30. package/src/components/openGraph/OgTitle.tsx +0 -45
  31. package/src/components/twitter/twitterDescription.tsx +0 -45
  32. package/src/components/twitter/twitterTitle.tsx +0 -45
  33. package/src/helpers/SeoMetaTags.tsx +0 -154
  34. package/src/helpers/seoMeta.ts +0 -283
  35. package/src/index.ts +0 -26
  36. package/src/next.ts +0 -12
  37. package/src/plugin.ts +0 -344
  38. package/src/schemas/index.ts +0 -121
  39. package/src/schemas/types/index.ts +0 -20
  40. package/src/schemas/types/metaAttribute/index.ts +0 -60
  41. package/src/schemas/types/metaTag/index.ts +0 -17
  42. package/src/schemas/types/openGraph/index.ts +0 -114
  43. package/src/schemas/types/robots/index.ts +0 -26
  44. package/src/schemas/types/twitter/index.ts +0 -108
  45. package/src/types.ts +0 -108
  46. package/src/utils/fieldsUtils.ts +0 -160
  47. package/src/utils/seoUtils.ts +0 -423
  48. package/src/utils/utils.ts +0 -9
  49. package/v2-incompatible.js +0 -11
@@ -1,283 +0,0 @@
1
- /**
2
- * Headless CMS integration helpers for sanity-plugin-seofields
3
- *
4
- * Provides framework-agnostic SEO metadata utilities for use with:
5
- * - Next.js App Router → buildSeoMeta() inside generateMetadata()
6
- * - Next.js Pages Router → <SeoMetaTags> inside Next.js <Head>
7
- * - Nuxt / Remix / any SSR → <SeoMetaTags> inside your <head> slot
8
- */
9
-
10
- import type {SanityImage, SanityImageWithAlt, SeoFields} from '../types'
11
-
12
- // ─── Types ────────────────────────────────────────────────────────────────────
13
-
14
- /** Structured metadata returned by buildSeoMeta(). Compatible with Next.js Metadata (App Router). */
15
- export interface SeoMetadata {
16
- title?: string | null
17
- description?: string | null
18
- keywords?: string[]
19
- robots?: {
20
- index?: boolean
21
- follow?: boolean
22
- googleBot?: {
23
- index?: boolean
24
- follow?: boolean
25
- }
26
- }
27
- openGraph?: {
28
- type?: string
29
- url?: string
30
- title?: string
31
- description?: string
32
- siteName?: string
33
- images?: Array<{url: string; width?: number; height?: number; alt?: string}>
34
- }
35
- twitter?: {
36
- card?: string
37
- site?: string
38
- creator?: string
39
- title?: string
40
- description?: string
41
- images?: string[]
42
- }
43
- alternates?: {
44
- canonical?: string
45
- }
46
- /** Any custom meta attributes from seo.metaAttributes */
47
- other?: Record<string, string>
48
- }
49
-
50
- /** Default values used when SEO fields are missing. */
51
- export interface SeoMetaDefaults {
52
- title?: string
53
- description?: string
54
- siteName?: string
55
- twitterSite?: string
56
- twitterCreator?: string
57
- /** Fallback image URL when no OG / Twitter image is set. */
58
- ogImage?: string
59
- }
60
-
61
- /**
62
- * Permissive image shape accepted by buildSeoMeta — compatible with both the
63
- * plugin's SanityImage and Sanity's code-generated image type (where `asset`
64
- * and `alt` are optional).
65
- */
66
- interface SeoImageInput {
67
- _type?: string
68
- asset?: {_ref: string; _type: string; _weak?: boolean; [key: string]: unknown}
69
- hotspot?: unknown
70
- crop?: unknown
71
- alt?: string
72
- }
73
-
74
- /**
75
- * Input-compatible variant of SeoFields. Structurally matches Sanity's
76
- * code-generated types (where `asset`, `alt`, `key`, and `type` are all
77
- * optional), so you can pass `data.seo` from a sanityFetch result directly
78
- * without any `as any` or manual casting.
79
- */
80
- export interface SeoFieldsInput {
81
- _type?: string
82
- robots?: {noIndex?: boolean | null; noFollow?: boolean | null} | null
83
- title?: string | null
84
- description?: string | null
85
- metaImage?: SeoImageInput | null
86
- metaAttributes?: Array<{_key?: string; key?: string; value?: string; type?: string}> | null
87
- keywords?: string[] | null
88
- canonicalUrl?: string | null
89
- openGraph?: {
90
- _type?: string
91
- url?: string | null
92
- title?: string | null
93
- description?: string | null
94
- siteName?: string | null
95
- type?: string | null
96
- imageType?: string | null
97
- image?: SeoImageInput | null
98
- imageUrl?: string | null
99
- } | null
100
- twitter?: {
101
- _type?: string
102
- card?: string | null
103
- site?: string | null
104
- creator?: string | null
105
- title?: string | null
106
- description?: string | null
107
- imageType?: string | null
108
- image?: SeoImageInput | null
109
- imageUrl?: string | null
110
- } | null
111
- }
112
-
113
- /** Options accepted by buildSeoMeta(). */
114
- export interface BuildSeoMetaOptions {
115
- /**
116
- * The raw SEO object from Sanity (_type excluded or included — both work).
117
- * Pass `null` or `undefined` to fall back entirely to `defaults`.
118
- *
119
- * Accepts both the strict plugin `SeoFields` type and Sanity's code-generated
120
- * type (which has all nested fields optional) without any `as any` cast.
121
- */
122
- seo?: SeoFieldsInput | null
123
-
124
- /**
125
- * The base URL of your site, e.g. "https://example.com".
126
- * Used for canonical URL and OpenGraph URL construction.
127
- */
128
- baseUrl?: string
129
-
130
- /**
131
- * The path for the current page, e.g. "/about".
132
- * Combined with baseUrl to produce the canonical + OG url.
133
- * Defaults to "".
134
- */
135
- path?: string
136
-
137
- /**
138
- * Default values used when the Sanity SEO fields are empty / missing.
139
- */
140
- defaults?: SeoMetaDefaults
141
-
142
- /**
143
- * Resolve a Sanity image asset to a plain URL string.
144
- *
145
- * @example (using @sanity/image-url)
146
- * imageUrlResolver: (img) => urlFor(img).width(1200).url()
147
- */
148
- imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined
149
- }
150
-
151
- // ─── Internal helpers ─────────────────────────────────────────────────────────
152
-
153
- const VALID_OG_TYPES = [
154
- 'website',
155
- 'article',
156
- 'profile',
157
- 'book',
158
- 'music',
159
- 'video',
160
- 'product',
161
- ] as const
162
- type OGType = (typeof VALID_OG_TYPES)[number]
163
-
164
- /**
165
- * Coerce an arbitrary string to a valid OpenGraph type.
166
- * Falls back to "website" when the value is invalid.
167
- */
168
- export function sanitizeOGType(value?: string): OGType {
169
- if (value && (VALID_OG_TYPES as readonly string[]).includes(value)) {
170
- return value as OGType
171
- }
172
- return 'website'
173
- }
174
-
175
- const VALID_TWITTER_CARDS = ['summary', 'summary_large_image', 'app', 'player'] as const
176
- type TwitterCard = (typeof VALID_TWITTER_CARDS)[number]
177
-
178
- /**
179
- * Coerce an arbitrary string to a valid Twitter card type.
180
- * Falls back to "summary_large_image" when the value is invalid.
181
- */
182
- export function sanitizeTwitterCard(value?: string): TwitterCard {
183
- if (value && (VALID_TWITTER_CARDS as readonly string[]).includes(value)) {
184
- return value as TwitterCard
185
- }
186
- return 'summary_large_image'
187
- }
188
-
189
- // ─── Core builder ─────────────────────────────────────────────────────────────
190
-
191
- /**
192
- * Convert a Sanity SEO object into a structured metadata object.
193
- *
194
- * The return value is structurally compatible with Next.js App Router's
195
- * `Metadata` type, so you can return it directly from `generateMetadata()`.
196
- *
197
- * @example Next.js App Router
198
- * ```ts
199
- * import { buildSeoMeta } from 'sanity-plugin-seofields'
200
- * import { urlFor } from '@/sanity/lib/image'
201
- *
202
- * export async function generateMetadata(): Promise<Metadata> {
203
- * const { seo } = await sanityFetch({ query: PAGE_SEO_QUERY })
204
- * return buildSeoMeta({
205
- * seo,
206
- * baseUrl: process.env.NEXT_PUBLIC_SITE_URL,
207
- * path: '/about',
208
- * defaults: { title: 'My Site', siteName: 'My Site' },
209
- * imageUrlResolver: (img) => urlFor(img).width(1200).url(),
210
- * })
211
- * }
212
- * ```
213
- */
214
- export function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata {
215
- const {seo, baseUrl = '', path = '', defaults = {}, imageUrlResolver} = options
216
-
217
- const normalizedBase = baseUrl.replace(/\/+$/, '') // remove trailing /
218
- const normalizedPath = path.replace(/^\/+/, '') // remove leading /
219
-
220
- const fullUrl = [normalizedBase, normalizedPath].filter(Boolean).join('/')
221
-
222
- // ── OG image resolution ──
223
- let ogImageURL: string = defaults.ogImage || ''
224
- if (seo?.openGraph?.imageType === 'url' && seo.openGraph.imageUrl) {
225
- ogImageURL = seo.openGraph.imageUrl
226
- } else if (seo?.openGraph?.image && imageUrlResolver) {
227
- ogImageURL = imageUrlResolver(seo.openGraph.image as SanityImage) || ogImageURL
228
- }
229
-
230
- // ── Twitter image resolution ──
231
- let twitterImageURL: string = ogImageURL // reuse OG image as fallback
232
- if (seo?.twitter?.imageType === 'url' && seo.twitter.imageUrl) {
233
- twitterImageURL = seo.twitter.imageUrl
234
- } else if (seo?.twitter?.image && imageUrlResolver) {
235
- twitterImageURL = imageUrlResolver(seo.twitter.image as SanityImage) || twitterImageURL
236
- }
237
-
238
- // ── Custom meta attributes → `other` map ──
239
- const other: Record<string, string> = {}
240
- if (Array.isArray(seo?.metaAttributes)) {
241
- for (const attr of seo!.metaAttributes!) {
242
- if (attr.key && attr.value) {
243
- other[attr.key] = attr.value
244
- }
245
- }
246
- }
247
-
248
- const ogUrl = seo?.openGraph?.url || fullUrl
249
-
250
- return {
251
- title: seo?.title ?? defaults.title ?? null,
252
- description: seo?.description ?? defaults.description ?? null,
253
- keywords: seo?.keywords?.length ? (seo.keywords as string[]) : undefined,
254
- robots: {
255
- index: !seo?.robots?.noIndex,
256
- follow: !seo?.robots?.noFollow,
257
- googleBot: {
258
- index: !seo?.robots?.noIndex,
259
- follow: !seo?.robots?.noFollow,
260
- },
261
- },
262
- openGraph: {
263
- type: sanitizeOGType(seo?.openGraph?.type ?? undefined),
264
- url: ogUrl || undefined,
265
- title: seo?.openGraph?.title ?? defaults.title,
266
- description: seo?.openGraph?.description ?? defaults.description,
267
- siteName: seo?.openGraph?.siteName ?? defaults.siteName,
268
- images: ogImageURL ? [{url: ogImageURL}] : [],
269
- },
270
- twitter: {
271
- card: sanitizeTwitterCard(seo?.twitter?.card ?? undefined),
272
- site: seo?.twitter?.site ?? defaults.twitterSite,
273
- creator: seo?.twitter?.creator ?? defaults.twitterCreator,
274
- title: seo?.twitter?.title ?? defaults.title,
275
- description: seo?.twitter?.description ?? defaults.description,
276
- images: twitterImageURL ? [twitterImageURL] : [],
277
- },
278
- alternates: {
279
- canonical: fullUrl || undefined,
280
- },
281
- ...(Object.keys(other).length > 0 ? {other} : {}),
282
- }
283
- }
package/src/index.ts DELETED
@@ -1,26 +0,0 @@
1
- // Import the plugin
2
- import seofields from './plugin'
3
-
4
- // Default export the plugin
5
- export default seofields
6
-
7
- // Re-export everything from plugin.ts
8
- export * from './plugin'
9
-
10
- // Export schema types for external use
11
- export {default as seoFieldsSchema} from './schemas'
12
- export {default as allSchemas} from './schemas/types'
13
- export {default as metaAttributeSchema} from './schemas/types/metaAttribute'
14
- export {default as metaTagSchema} from './schemas/types/metaTag'
15
- export {default as openGraphSchema} from './schemas/types/openGraph'
16
- export {default as robotsSchema} from './schemas/types/robots'
17
- export {default as twitterSchema} from './schemas/types/twitter'
18
-
19
- // Export dashboard components and types
20
- export {default as SeoHealthDashboard} from './components/SeoHealthDashboard'
21
- export {default as SeoHealthTool} from './components/SeoHealthTool'
22
- export {createSeoHealthPane} from './components/SeoHealthPane'
23
- export type {SeoHealthPaneOptions} from './components/SeoHealthPane'
24
-
25
- // Export types
26
- export type {DocumentWithSeoHealth, SeoHealthMetrics, SeoHealthStatus} from './types'
package/src/next.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Next.js App Router helpers — safe to import in Server Components.
3
- *
4
- * @example
5
- * import { buildSeoMeta, SeoMetaTags } from 'sanity-plugin-seofields/next'
6
- */
7
- export {buildSeoMeta, sanitizeOGType, sanitizeTwitterCard} from './helpers/seoMeta'
8
-
9
- export type {BuildSeoMetaOptions, SeoMetaDefaults, SeoMetadata} from './helpers/seoMeta'
10
-
11
- export {SeoMetaTags} from './helpers/SeoMetaTags'
12
- export type {SeoMetaTagsProps} from './helpers/SeoMetaTags'
package/src/plugin.ts DELETED
@@ -1,344 +0,0 @@
1
- // plugin.ts
2
- import React from 'react'
3
- import {definePlugin} from 'sanity'
4
-
5
- import SeoHealthTool from './components/SeoHealthTool'
6
- import types from './schemas/types'
7
- import type {DocumentWithSeoHealth} from './types'
8
-
9
- export interface SeoFieldConfig {
10
- title?: string
11
- description?: string
12
- }
13
-
14
- export type SeoFieldKeys =
15
- | 'title'
16
- | 'description'
17
- | 'canonicalUrl'
18
- | 'metaImage'
19
- | 'keywords'
20
- | 'metaAttributes'
21
- | 'robots'
22
-
23
- export type openGraphFieldKeys =
24
- | 'openGraphUrl'
25
- | 'openGraphTitle'
26
- | 'openGraphDescription'
27
- | 'openGraphSiteName'
28
- | 'openGraphType'
29
- | 'openGraphImageType'
30
- | 'openGraphImage'
31
- | 'openGraphImageUrl'
32
-
33
- export type twitterFieldKeys =
34
- | 'twitterCard'
35
- | 'twitterSite'
36
- | 'twitterCreator'
37
- | 'twitterTitle'
38
- | 'twitterDescription'
39
- | 'twitterImageType'
40
- | 'twitterImage'
41
- | 'twitterImageUrl'
42
-
43
- export type AllFieldKeys = SeoFieldKeys | openGraphFieldKeys | twitterFieldKeys
44
-
45
- export type ValidHiddenFieldKeys = Exclude<
46
- AllFieldKeys,
47
- 'openGraphImageUrl' | 'twitterImageUrl' | 'openGraphImageType' | 'twitterImageType'
48
- >
49
-
50
- export interface FieldVisibilityConfig {
51
- hiddenFields?: ValidHiddenFieldKeys[]
52
- }
53
-
54
- export interface SeoFieldsPluginConfig {
55
- /**
56
- * Enable or configure the SEO preview feature.
57
- * If set to `true`, the SEO preview will be enabled with default settings.
58
- * If set to an object, you can provide a custom prefix function to modify the URL prefix in the preview.
59
- * The prefix function receives the current document as an argument and should return a string.
60
- * Example:
61
- * ```
62
- * seoPreview: {
63
- * prefix: (doc) => `/${doc.slug?.current || 'untitled'}`
64
- * }
65
- * ```
66
- */
67
- seoPreview?:
68
- | boolean
69
- | {
70
- prefix?: (doc: {_type?: string} & Record<string, unknown>) => string
71
- }
72
-
73
- /**
74
- * A mapping of field keys to their configuration settings.
75
- * This allows customization of field titles and descriptions.
76
- * For example, to change the title of the 'title' field:
77
- */
78
- fieldOverrides?: Partial<Record<AllFieldKeys, SeoFieldConfig>>
79
- /**
80
- * A mapping of document types to field visibility configurations.
81
- * This allows you to specify which fields should be hidden for specific document types.
82
- */
83
- fieldVisibility?: Record<string, FieldVisibilityConfig>
84
-
85
- /**
86
- * A list of fields that should be hidden by default in all document types.
87
- * This can be overridden by specific document type settings in `fieldVisibility`.
88
- */
89
- defaultHiddenFields?: ValidHiddenFieldKeys[]
90
- /**
91
- * The base URL of your website, used for generating full URLs in the SEO preview.
92
- * Defaults to 'https://www.example.com' if not provided.
93
- */
94
- baseUrl?: string
95
- /**
96
- * Enable or configure the SEO Health Dashboard tool.
97
- * If set to `true`, the dashboard is enabled with all defaults.
98
- * If set to an object, you can customise the tool and dashboard settings.
99
- * Defaults to `true`.
100
- * Example:
101
- * ```
102
- * healthDashboard: {
103
- * toolTitle: 'SEO Overview', // Studio nav tab label
104
- * content: {
105
- * icon: '🔍', // Emoji icon shown before the page heading
106
- * title: 'My SEO Dashboard',// Page heading inside the tool (no emoji)
107
- * description: 'Track SEO across all documents', // Subtitle under the heading
108
- * },
109
- * display: {
110
- * typeColumn: false, // Hide the document type column (default: true)
111
- * documentId: false, // Hide the document ID under titles (default: true)
112
- * },
113
- * query: {
114
- * // Option 1 – filter by specific document types
115
- * types: ['post', 'page'],
116
- * // Option 2 – provide a full custom GROQ query (takes precedence over `types`)
117
- * // Must return documents with at least: _id, _type, title, seo, _updatedAt
118
- * groq: `*[seo != null && defined(slug.current)]{ _id, _type, title, slug, seo, _updatedAt }`,
119
- * },
120
- * }
121
- * ```
122
- */
123
- healthDashboard?:
124
- | boolean
125
- | {
126
- tool?: {
127
- title?: string
128
- name?: string
129
- }
130
- toolTitle?: string
131
- content?: {
132
- icon?: string
133
- title?: string
134
- description?: string
135
- /** Text shown while the license key is being verified. Defaults to "Verifying license…" */
136
- loadingLicense?: string
137
- /** Text shown while documents are being fetched. Defaults to "Loading documents…" */
138
- loadingDocuments?: string
139
- /** Text shown when the query returns zero results. Defaults to "No documents found" */
140
- noDocuments?: string
141
- }
142
- display?: {
143
- typeColumn?: boolean
144
- documentId?: boolean
145
- }
146
- query?: {
147
- /**
148
- * Limit the dashboard to specific document types.
149
- * Example: `['post', 'page']`
150
- */
151
- types?: string[]
152
- /**
153
- * When using `types`, also require the `seo` field to be non-null.
154
- * Set to `false` to include documents of those types even if `seo` is missing.
155
- * Defaults to `true`.
156
- */
157
- requireSeo?: boolean
158
- /**
159
- * Provide a fully custom GROQ query. Takes precedence over `types`.
160
- * The query must return documents with at least: _id, _type, title, seo, _updatedAt
161
- */
162
- groq?: string
163
- }
164
- /**
165
- * The Sanity API version to use for the client (e.g. '2023-01-01').
166
- * Defaults to '2023-01-01'.
167
- */
168
- apiVersion?: string
169
- /**
170
- * License key for the SEO Health Dashboard pro feature.
171
- * Obtain a license at https://sanity-plugin-seofields.thehardik.in
172
- */
173
- licenseKey?: string
174
- /**
175
- * Map raw `_type` values to human-readable display labels.
176
- * Used in both the Type column and the Type filter dropdown.
177
- * Any type without an entry falls back to the raw `_type` string.
178
- *
179
- * @example
180
- * typeLabels: { productDrug: 'Products', singleCondition: 'Condition' }
181
- */
182
- typeLabels?: Record<string, string>
183
- /**
184
- * Controls how the document type is rendered in the Type column.
185
- * - `'badge'` (default) — coloured pill
186
- * - `'text'` — plain text, useful for dense layouts
187
- */
188
- typeColumnMode?: 'badge' | 'text'
189
- /**
190
- * The document field to use as the display title in the dashboard.
191
- *
192
- * - `string` — use this field for every document type (e.g. `'name'`)
193
- * - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
194
- *
195
- * @example
196
- * titleField: 'name'
197
- *
198
- * @example
199
- * titleField: { post: 'title', product: 'name', category: 'label' }
200
- */
201
- titleField?: string | Record<string, string>
202
- /**
203
- * Callback function to render a custom badge next to the document title.
204
- * Receives the full document and should return badge data or undefined.
205
- *
206
- * @example
207
- * docBadge: (doc) => {
208
- * if (doc.services === 'NHS')
209
- * return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
210
- * if (doc.services === 'Private')
211
- * return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
212
- * }
213
- */
214
- docBadge?: (
215
- doc: DocumentWithSeoHealth & Record<string, unknown>,
216
- ) => {label: string; bgColor?: string; textColor?: string; fontSize?: string} | undefined
217
- /**
218
- * The `name` of the Sanity structure tool that contains the monitored documents.
219
- * Required when you have multiple structure tools and the documents live in a
220
- * non-default one. Clicking a title will navigate to
221
- * `/{basePath}/{structureTool}/intent/edit/…` directly.
222
- *
223
- * @example
224
- * structureTool: 'common'
225
- */
226
- structureTool?: string
227
- /**
228
- * Enable preview/demo mode to show dummy data.
229
- * Useful for testing, documentation, or showcasing the dashboard.
230
- * When enabled, displays realistic sample documents with various SEO scores.
231
- * Defaults to `false`.
232
- *
233
- * @example
234
- * previewMode: true
235
- */
236
- previewMode?: boolean
237
- }
238
- }
239
-
240
- interface ResolvedDashboardConfig {
241
- enabled: boolean
242
- toolTitle: string
243
- toolName: string
244
- icon: string | undefined
245
- title: string | undefined
246
- description: string | undefined
247
- showTypeColumn: boolean | undefined
248
- showDocumentId: boolean | undefined
249
- queryTypes: string[] | undefined
250
- queryRequireSeo: boolean | undefined
251
- queryGroq: string | undefined
252
- apiVersion: string | undefined
253
- licenseKey: string | undefined
254
- typeLabels: Record<string, string> | undefined
255
- typeColumnMode: 'badge' | 'text' | undefined
256
- titleField: string | Record<string, string> | undefined
257
- docBadge:
258
- | ((
259
- doc: DocumentWithSeoHealth & Record<string, unknown>,
260
- ) => {label: string; bgColor?: string; textColor?: string; fontSize?: string} | undefined)
261
- | undefined
262
- loadingLicense: string | undefined
263
- loadingDocuments: string | undefined
264
- noDocuments: string | undefined
265
- previewMode: boolean | undefined
266
- structureTool: string | undefined
267
- }
268
-
269
- const resolveDashboardConfig = (
270
- healthDashboard: SeoFieldsPluginConfig['healthDashboard'],
271
- ): ResolvedDashboardConfig => {
272
- const cfg = typeof healthDashboard === 'object' ? healthDashboard : undefined
273
- return {
274
- enabled: healthDashboard !== false,
275
- toolTitle: cfg?.tool?.title ?? 'SEO Health',
276
- toolName: cfg?.tool?.name ?? 'seo-health-dashboard',
277
- icon: cfg?.content?.icon,
278
- title: cfg?.content?.title,
279
- description: cfg?.content?.description,
280
- showTypeColumn: cfg?.display?.typeColumn,
281
- showDocumentId: cfg?.display?.documentId,
282
- queryTypes: cfg?.query?.types,
283
- queryRequireSeo: cfg?.query?.requireSeo,
284
- queryGroq: cfg?.query?.groq,
285
- apiVersion: cfg?.apiVersion,
286
- licenseKey: cfg?.licenseKey,
287
- typeLabels: cfg?.typeLabels,
288
- typeColumnMode: cfg?.typeColumnMode,
289
- titleField: cfg?.titleField,
290
- docBadge: cfg?.docBadge,
291
- loadingLicense: cfg?.content?.loadingLicense,
292
- loadingDocuments: cfg?.content?.loadingDocuments,
293
- noDocuments: cfg?.content?.noDocuments,
294
- previewMode: cfg?.previewMode,
295
- structureTool: cfg?.structureTool,
296
- }
297
- }
298
-
299
- const seofields = definePlugin<SeoFieldsPluginConfig | void>((config = {}) => {
300
- const {healthDashboard = true} = config as SeoFieldsPluginConfig
301
- const dash = resolveDashboardConfig(healthDashboard)
302
-
303
- const BoundSeoHealthTool = () =>
304
- React.createElement(SeoHealthTool, {
305
- icon: dash.icon,
306
- title: dash.title,
307
- description: dash.description,
308
- showTypeColumn: dash.showTypeColumn,
309
- showDocumentId: dash.showDocumentId,
310
- queryTypes: dash.queryTypes,
311
- queryRequireSeo: dash.queryRequireSeo,
312
- customQuery: dash.queryGroq,
313
- apiVersion: dash.apiVersion,
314
- licenseKey: dash.licenseKey,
315
- typeLabels: dash.typeLabels,
316
- typeColumnMode: dash.typeColumnMode,
317
- titleField: dash.titleField,
318
- docBadge: dash.docBadge,
319
- loadingLicense: dash.loadingLicense,
320
- loadingDocuments: dash.loadingDocuments,
321
- noDocuments: dash.noDocuments,
322
- previewMode: dash.previewMode,
323
- structureTool: dash.structureTool,
324
- })
325
-
326
- return {
327
- name: 'sanity-plugin-seofields',
328
- schema: {
329
- types: types(config as SeoFieldsPluginConfig),
330
- },
331
- ...(dash.enabled && {
332
- tools: [
333
- {
334
- name: dash.toolName,
335
- title: dash.toolTitle,
336
- component: BoundSeoHealthTool,
337
- icon: () => '📊',
338
- },
339
- ],
340
- }),
341
- }
342
- })
343
-
344
- export default seofields