sanity-plugin-seofields 1.1.1 → 1.2.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.
- package/dist/index.d.mts +119 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +108 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/SeoHealthDashboard.tsx +154 -12
- package/src/plugin.ts +127 -51
- package/src/utils/seoUtils.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-seofields",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A Sanity Studio plugin to manage SEO fields like meta titles, descriptions, and Open Graph tags for structured, search-optimized content.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -197,6 +197,13 @@ const ColTitle = styled.div`
|
|
|
197
197
|
min-width: 0;
|
|
198
198
|
`
|
|
199
199
|
|
|
200
|
+
const TitleWrapper = styled.div`
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 4px;
|
|
204
|
+
flex-wrap: wrap;
|
|
205
|
+
`
|
|
206
|
+
|
|
200
207
|
const ColType = styled.div`
|
|
201
208
|
flex: 0.8;
|
|
202
209
|
min-width: 80px;
|
|
@@ -248,6 +255,24 @@ const TypeBadge = styled.span`
|
|
|
248
255
|
color: #5b21b6;
|
|
249
256
|
`
|
|
250
257
|
|
|
258
|
+
const TypeText = styled.span`
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
font-weight: 500;
|
|
261
|
+
color: #374151;
|
|
262
|
+
`
|
|
263
|
+
|
|
264
|
+
const CustomBadge = styled.span<{$bgColor?: string; $textColor?: string; $fontSize?: string}>`
|
|
265
|
+
display: inline-block;
|
|
266
|
+
padding: 2px 6px;
|
|
267
|
+
border-radius: 4px;
|
|
268
|
+
font-size: ${(p) => p.$fontSize || '10px'};
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
margin-left: 6px;
|
|
271
|
+
background: ${(p) => p.$bgColor || '#e0e7ff'};
|
|
272
|
+
color: ${(p) => p.$textColor || '#3730a3'};
|
|
273
|
+
white-space: nowrap;
|
|
274
|
+
`
|
|
275
|
+
|
|
251
276
|
const ScoreBadge = styled.span<{$score: number}>`
|
|
252
277
|
display: inline-block;
|
|
253
278
|
padding: 4px 10px;
|
|
@@ -440,6 +465,22 @@ const DocTitleAnchor: React.FC<{id: string; type: string; children: React.ReactN
|
|
|
440
465
|
)
|
|
441
466
|
}
|
|
442
467
|
|
|
468
|
+
// Sub-component to safely call docBadge outside a .map expression
|
|
469
|
+
const DocBadgeRenderer: React.FC<{
|
|
470
|
+
doc: DocumentWithSeoHealth & Record<string, unknown>
|
|
471
|
+
docBadge: (
|
|
472
|
+
doc: DocumentWithSeoHealth & Record<string, unknown>,
|
|
473
|
+
) => {label: string; bgColor?: string; textColor?: string; fontSize?: string} | undefined
|
|
474
|
+
}> = ({doc, docBadge}) => {
|
|
475
|
+
const badge = docBadge(doc)
|
|
476
|
+
if (!badge) return null
|
|
477
|
+
return (
|
|
478
|
+
<CustomBadge $bgColor={badge.bgColor} $textColor={badge.textColor} $fontSize={badge.fontSize}>
|
|
479
|
+
{badge.label}
|
|
480
|
+
</CustomBadge>
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
443
484
|
const spin = keyframes`
|
|
444
485
|
to { transform: rotate(360deg); }
|
|
445
486
|
`
|
|
@@ -608,6 +649,24 @@ const calculateHealthScore = (doc: any): SeoHealthMetrics => {
|
|
|
608
649
|
return {score: totalScore, status, issues: allIssues}
|
|
609
650
|
}
|
|
610
651
|
|
|
652
|
+
const resolveTypeLabel = (type: string, typeLabels?: Record<string, string>): string =>
|
|
653
|
+
typeLabels?.[type] ?? type
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Builds the GROQ projection snippet for the title field.
|
|
657
|
+
* - undefined / 'title' → `title`
|
|
658
|
+
* - 'name' → `"title": name`
|
|
659
|
+
* - { post: 'title', product: 'name' } → `"title": select(_type == "post" => title, _type == "product" => name, title)`
|
|
660
|
+
*/
|
|
661
|
+
const buildTitleProjection = (titleField?: string | Record<string, string>): string => {
|
|
662
|
+
if (!titleField || titleField === 'title') return 'title'
|
|
663
|
+
if (typeof titleField === 'string') return `"title": ${titleField}`
|
|
664
|
+
const cases = Object.entries(titleField)
|
|
665
|
+
.map(([type, field]) => `_type == "${type}" => ${field}`)
|
|
666
|
+
.join(', ')
|
|
667
|
+
return `"title": select(${cases}, title)`
|
|
668
|
+
}
|
|
669
|
+
|
|
611
670
|
export interface SeoHealthDashboardProps {
|
|
612
671
|
icon?: string
|
|
613
672
|
title?: string
|
|
@@ -641,6 +700,66 @@ export interface SeoHealthDashboardProps {
|
|
|
641
700
|
* Obtain a key at https://sanity-plugin-seofields.thehardik.in
|
|
642
701
|
*/
|
|
643
702
|
licenseKey?: string
|
|
703
|
+
/**
|
|
704
|
+
* Map raw `_type` values to human-readable display labels used in the
|
|
705
|
+
* Type column and the Type filter dropdown.
|
|
706
|
+
* Any type without an entry falls back to the raw `_type` string.
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* typeLabels={{ productDrug: 'Products', singleCondition: 'Condition' }}
|
|
710
|
+
*/
|
|
711
|
+
typeLabels?: Record<string, string>
|
|
712
|
+
/**
|
|
713
|
+
* Controls how the type is rendered in the Type column.
|
|
714
|
+
* - `'badge'` (default) — coloured pill, consistent with score badges
|
|
715
|
+
* - `'text'` — plain text, useful for dense layouts
|
|
716
|
+
*/
|
|
717
|
+
typeColumnMode?: 'badge' | 'text'
|
|
718
|
+
/**
|
|
719
|
+
* The document field to use as the display title.
|
|
720
|
+
*
|
|
721
|
+
* - `string` — use this field for every document type (e.g. `'name'`)
|
|
722
|
+
* - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* // Same field for all types
|
|
726
|
+
* titleField: 'name'
|
|
727
|
+
*
|
|
728
|
+
* @example
|
|
729
|
+
* // Different field per type
|
|
730
|
+
* titleField: { post: 'title', product: 'name', category: 'label' }
|
|
731
|
+
*/
|
|
732
|
+
titleField?: string | Record<string, string>
|
|
733
|
+
/**
|
|
734
|
+
* Callback function to render a custom badge next to the document title.
|
|
735
|
+
* Receives the full document and should return badge data or undefined.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* docBadge: (doc) => {
|
|
739
|
+
* if (doc.services === 'NHS')
|
|
740
|
+
* return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
|
|
741
|
+
* if (doc.services === 'Private')
|
|
742
|
+
* return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
|
|
743
|
+
* }
|
|
744
|
+
*/
|
|
745
|
+
docBadge?: (
|
|
746
|
+
doc: DocumentWithSeoHealth & Record<string, unknown>,
|
|
747
|
+
) => {label: string; bgColor?: string; textColor?: string; fontSize?: string} | undefined
|
|
748
|
+
/**
|
|
749
|
+
* Custom text shown while the license key is being verified.
|
|
750
|
+
* Defaults to `"Verifying license…"`.
|
|
751
|
+
*/
|
|
752
|
+
loadingLicense?: React.ReactNode
|
|
753
|
+
/**
|
|
754
|
+
* Custom text shown while documents are being fetched.
|
|
755
|
+
* Defaults to `"Loading documents…"`.
|
|
756
|
+
*/
|
|
757
|
+
loadingDocuments?: React.ReactNode
|
|
758
|
+
/**
|
|
759
|
+
* Custom text shown when the query returns zero results.
|
|
760
|
+
* Defaults to `"No documents found"`.
|
|
761
|
+
*/
|
|
762
|
+
noDocuments?: React.ReactNode
|
|
644
763
|
}
|
|
645
764
|
|
|
646
765
|
const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
@@ -654,6 +773,13 @@ const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
|
654
773
|
customQuery,
|
|
655
774
|
apiVersion = '2023-01-01',
|
|
656
775
|
licenseKey,
|
|
776
|
+
typeLabels,
|
|
777
|
+
typeColumnMode = 'badge',
|
|
778
|
+
titleField,
|
|
779
|
+
docBadge,
|
|
780
|
+
loadingLicense,
|
|
781
|
+
loadingDocuments,
|
|
782
|
+
noDocuments,
|
|
657
783
|
}) => {
|
|
658
784
|
const client = useClient({apiVersion})
|
|
659
785
|
const [licenseStatus, setLicenseStatus] = useState<'loading' | 'valid' | 'invalid'>('loading')
|
|
@@ -764,10 +890,11 @@ const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
|
764
890
|
} else if (queryTypes && queryTypes.length > 0) {
|
|
765
891
|
// Mode 2: filter by specific document types (excluding drafts)
|
|
766
892
|
const seoFilter = queryRequireSeo ? ' && seo != null' : ''
|
|
893
|
+
const titleProj = buildTitleProjection(titleField)
|
|
767
894
|
groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
|
|
768
895
|
_id,
|
|
769
896
|
_type,
|
|
770
|
-
|
|
897
|
+
${titleProj},
|
|
771
898
|
slug,
|
|
772
899
|
seo,
|
|
773
900
|
_updatedAt
|
|
@@ -775,10 +902,11 @@ const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
|
775
902
|
params = {types: queryTypes}
|
|
776
903
|
} else {
|
|
777
904
|
// Mode 1: default — all documents with an seo field (excluding drafts)
|
|
905
|
+
const titleProj = buildTitleProjection(titleField)
|
|
778
906
|
groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
|
|
779
907
|
_id,
|
|
780
908
|
_type,
|
|
781
|
-
|
|
909
|
+
${titleProj},
|
|
782
910
|
slug,
|
|
783
911
|
seo,
|
|
784
912
|
_updatedAt
|
|
@@ -804,7 +932,7 @@ const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
|
804
932
|
|
|
805
933
|
fetchDocuments()
|
|
806
934
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
807
|
-
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes)])
|
|
935
|
+
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes), JSON.stringify(titleField)])
|
|
808
936
|
|
|
809
937
|
const uniqueDocumentTypes = useMemo(() => {
|
|
810
938
|
const types = new Set(documents.map((doc) => doc._type))
|
|
@@ -863,7 +991,7 @@ const SeoHealthDashboard: React.FC<SeoHealthDashboardProps> = ({
|
|
|
863
991
|
{licenseStatus === 'loading' && (
|
|
864
992
|
<LoadingState style={{padding: '80px 24px'}}>
|
|
865
993
|
<Spinner />
|
|
866
|
-
Verifying license…
|
|
994
|
+
{loadingLicense ?? 'Verifying license…'}
|
|
867
995
|
</LoadingState>
|
|
868
996
|
)}
|
|
869
997
|
{licenseStatus === 'invalid' && (
|
|
@@ -1005,7 +1133,7 @@ export default defineConfig({
|
|
|
1005
1133
|
<option value="all">All Types</option>
|
|
1006
1134
|
{uniqueDocumentTypes.map((type) => (
|
|
1007
1135
|
<option key={type} value={type}>
|
|
1008
|
-
{type}
|
|
1136
|
+
{resolveTypeLabel(type, typeLabels)}
|
|
1009
1137
|
</option>
|
|
1010
1138
|
))}
|
|
1011
1139
|
</StyledSelect>
|
|
@@ -1024,12 +1152,12 @@ export default defineConfig({
|
|
|
1024
1152
|
{loading && (
|
|
1025
1153
|
<LoadingState>
|
|
1026
1154
|
<Spinner />
|
|
1027
|
-
Loading documents…
|
|
1155
|
+
{loadingDocuments ?? 'Loading documents…'}
|
|
1028
1156
|
</LoadingState>
|
|
1029
1157
|
)}
|
|
1030
1158
|
{!loading &&
|
|
1031
1159
|
(filteredAndSortedDocs.length === 0 ? (
|
|
1032
|
-
<EmptyState>No documents found</EmptyState>
|
|
1160
|
+
<EmptyState>{noDocuments ?? 'No documents found'}</EmptyState>
|
|
1033
1161
|
) : (
|
|
1034
1162
|
<>
|
|
1035
1163
|
<TableHeader>
|
|
@@ -1042,14 +1170,28 @@ export default defineConfig({
|
|
|
1042
1170
|
return (
|
|
1043
1171
|
<TableRow key={doc._id}>
|
|
1044
1172
|
<ColTitle>
|
|
1045
|
-
<
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1173
|
+
<TitleWrapper>
|
|
1174
|
+
<div>
|
|
1175
|
+
<DocTitleAnchor id={doc._id} type={doc._type}>
|
|
1176
|
+
{doc.title || 'Untitled'}
|
|
1177
|
+
</DocTitleAnchor>
|
|
1178
|
+
{showDocumentId && <DocId>{doc._id}</DocId>}
|
|
1179
|
+
</div>
|
|
1180
|
+
{docBadge && (
|
|
1181
|
+
<DocBadgeRenderer
|
|
1182
|
+
doc={doc as DocumentWithSeoHealth & Record<string, unknown>}
|
|
1183
|
+
docBadge={docBadge}
|
|
1184
|
+
/>
|
|
1185
|
+
)}
|
|
1186
|
+
</TitleWrapper>
|
|
1049
1187
|
</ColTitle>
|
|
1050
1188
|
{showTypeColumn && (
|
|
1051
1189
|
<ColType>
|
|
1052
|
-
|
|
1190
|
+
{typeColumnMode === 'text' ? (
|
|
1191
|
+
<TypeText>{resolveTypeLabel(doc._type, typeLabels)}</TypeText>
|
|
1192
|
+
) : (
|
|
1193
|
+
<TypeBadge>{resolveTypeLabel(doc._type, typeLabels)}</TypeBadge>
|
|
1194
|
+
)}
|
|
1053
1195
|
</ColType>
|
|
1054
1196
|
)}
|
|
1055
1197
|
<ColScore>
|
package/src/plugin.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {definePlugin} from 'sanity'
|
|
|
4
4
|
|
|
5
5
|
import SeoHealthTool from './components/SeoHealthTool'
|
|
6
6
|
import types from './schemas/types'
|
|
7
|
+
import type {DocumentWithSeoHealth} from './types'
|
|
7
8
|
|
|
8
9
|
export interface SeoFieldConfig {
|
|
9
10
|
title?: string
|
|
@@ -131,6 +132,12 @@ export interface SeoFieldsPluginConfig {
|
|
|
131
132
|
icon?: string
|
|
132
133
|
title?: string
|
|
133
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
|
|
134
141
|
}
|
|
135
142
|
display?: {
|
|
136
143
|
typeColumn?: boolean
|
|
@@ -164,73 +171,142 @@ export interface SeoFieldsPluginConfig {
|
|
|
164
171
|
* Obtain a license at https://sanity-plugin-seofields.thehardik.in
|
|
165
172
|
*/
|
|
166
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
|
|
167
217
|
}
|
|
168
218
|
}
|
|
169
219
|
|
|
220
|
+
interface ResolvedDashboardConfig {
|
|
221
|
+
enabled: boolean
|
|
222
|
+
toolTitle: string
|
|
223
|
+
toolName: string
|
|
224
|
+
icon: string | undefined
|
|
225
|
+
title: string | undefined
|
|
226
|
+
description: string | undefined
|
|
227
|
+
showTypeColumn: boolean | undefined
|
|
228
|
+
showDocumentId: boolean | undefined
|
|
229
|
+
queryTypes: string[] | undefined
|
|
230
|
+
queryRequireSeo: boolean | undefined
|
|
231
|
+
queryGroq: string | undefined
|
|
232
|
+
apiVersion: string | undefined
|
|
233
|
+
licenseKey: string | undefined
|
|
234
|
+
typeLabels: Record<string, string> | undefined
|
|
235
|
+
typeColumnMode: 'badge' | 'text' | undefined
|
|
236
|
+
titleField: string | Record<string, string> | undefined
|
|
237
|
+
docBadge:
|
|
238
|
+
| ((
|
|
239
|
+
doc: DocumentWithSeoHealth & Record<string, unknown>,
|
|
240
|
+
) => {label: string; bgColor?: string; textColor?: string; fontSize?: string} | undefined)
|
|
241
|
+
| undefined
|
|
242
|
+
loadingLicense: string | undefined
|
|
243
|
+
loadingDocuments: string | undefined
|
|
244
|
+
noDocuments: string | undefined
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const resolveDashboardConfig = (
|
|
248
|
+
healthDashboard: SeoFieldsPluginConfig['healthDashboard'],
|
|
249
|
+
): ResolvedDashboardConfig => {
|
|
250
|
+
const cfg = typeof healthDashboard === 'object' ? healthDashboard : undefined
|
|
251
|
+
return {
|
|
252
|
+
enabled: healthDashboard !== false,
|
|
253
|
+
toolTitle: cfg?.tool?.title ?? 'SEO Health',
|
|
254
|
+
toolName: cfg?.tool?.name ?? 'seo-health-dashboard',
|
|
255
|
+
icon: cfg?.content?.icon,
|
|
256
|
+
title: cfg?.content?.title,
|
|
257
|
+
description: cfg?.content?.description,
|
|
258
|
+
showTypeColumn: cfg?.display?.typeColumn,
|
|
259
|
+
showDocumentId: cfg?.display?.documentId,
|
|
260
|
+
queryTypes: cfg?.query?.types,
|
|
261
|
+
queryRequireSeo: cfg?.query?.requireSeo,
|
|
262
|
+
queryGroq: cfg?.query?.groq,
|
|
263
|
+
apiVersion: cfg?.apiVersion,
|
|
264
|
+
licenseKey: cfg?.licenseKey,
|
|
265
|
+
typeLabels: cfg?.typeLabels,
|
|
266
|
+
typeColumnMode: cfg?.typeColumnMode,
|
|
267
|
+
titleField: cfg?.titleField,
|
|
268
|
+
docBadge: cfg?.docBadge,
|
|
269
|
+
loadingLicense: cfg?.content?.loadingLicense,
|
|
270
|
+
loadingDocuments: cfg?.content?.loadingDocuments,
|
|
271
|
+
noDocuments: cfg?.content?.noDocuments,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
170
275
|
const seofields = definePlugin<SeoFieldsPluginConfig | void>((config = {}) => {
|
|
171
276
|
const {healthDashboard = true} = config as SeoFieldsPluginConfig
|
|
172
|
-
const
|
|
173
|
-
const dashboardToolTitle =
|
|
174
|
-
typeof healthDashboard === 'object'
|
|
175
|
-
? (healthDashboard.tool?.title ?? 'SEO Health')
|
|
176
|
-
: 'SEO Health'
|
|
177
|
-
const dashboardName =
|
|
178
|
-
typeof healthDashboard === 'object'
|
|
179
|
-
? (healthDashboard.tool?.name ?? 'seo-health-dashboard')
|
|
180
|
-
: 'seo-health-dashboard'
|
|
181
|
-
const dashboardPageIcon =
|
|
182
|
-
typeof healthDashboard === 'object' ? (healthDashboard.content?.icon ?? undefined) : undefined
|
|
183
|
-
const dashboardPageTitle =
|
|
184
|
-
typeof healthDashboard === 'object' ? (healthDashboard.content?.title ?? undefined) : undefined
|
|
185
|
-
const dashboardDescription =
|
|
186
|
-
typeof healthDashboard === 'object'
|
|
187
|
-
? (healthDashboard.content?.description ?? undefined)
|
|
188
|
-
: undefined
|
|
189
|
-
const dashboardShowTypeColumn =
|
|
190
|
-
typeof healthDashboard === 'object'
|
|
191
|
-
? (healthDashboard.display?.typeColumn ?? undefined)
|
|
192
|
-
: undefined
|
|
193
|
-
const dashboardShowDocumentId =
|
|
194
|
-
typeof healthDashboard === 'object'
|
|
195
|
-
? (healthDashboard.display?.documentId ?? undefined)
|
|
196
|
-
: undefined
|
|
197
|
-
const dashboardQueryTypes =
|
|
198
|
-
typeof healthDashboard === 'object' ? (healthDashboard.query?.types ?? undefined) : undefined
|
|
199
|
-
const dashboardQueryRequireSeo =
|
|
200
|
-
typeof healthDashboard === 'object'
|
|
201
|
-
? (healthDashboard.query?.requireSeo ?? undefined)
|
|
202
|
-
: undefined
|
|
203
|
-
const dashboardQueryGroq =
|
|
204
|
-
typeof healthDashboard === 'object' ? (healthDashboard.query?.groq ?? undefined) : undefined
|
|
205
|
-
const dashboardApiVersion =
|
|
206
|
-
typeof healthDashboard === 'object' ? (healthDashboard.apiVersion ?? undefined) : undefined
|
|
207
|
-
const dashboardLicenseKey =
|
|
208
|
-
typeof healthDashboard === 'object' ? (healthDashboard.licenseKey ?? undefined) : undefined
|
|
277
|
+
const dash = resolveDashboardConfig(healthDashboard)
|
|
209
278
|
|
|
210
279
|
const BoundSeoHealthTool = () =>
|
|
211
280
|
React.createElement(SeoHealthTool, {
|
|
212
|
-
icon:
|
|
213
|
-
title:
|
|
214
|
-
description:
|
|
215
|
-
showTypeColumn:
|
|
216
|
-
showDocumentId:
|
|
217
|
-
queryTypes:
|
|
218
|
-
queryRequireSeo:
|
|
219
|
-
customQuery:
|
|
220
|
-
apiVersion:
|
|
221
|
-
licenseKey:
|
|
281
|
+
icon: dash.icon,
|
|
282
|
+
title: dash.title,
|
|
283
|
+
description: dash.description,
|
|
284
|
+
showTypeColumn: dash.showTypeColumn,
|
|
285
|
+
showDocumentId: dash.showDocumentId,
|
|
286
|
+
queryTypes: dash.queryTypes,
|
|
287
|
+
queryRequireSeo: dash.queryRequireSeo,
|
|
288
|
+
customQuery: dash.queryGroq,
|
|
289
|
+
apiVersion: dash.apiVersion,
|
|
290
|
+
licenseKey: dash.licenseKey,
|
|
291
|
+
typeLabels: dash.typeLabels,
|
|
292
|
+
typeColumnMode: dash.typeColumnMode,
|
|
293
|
+
titleField: dash.titleField,
|
|
294
|
+
docBadge: dash.docBadge,
|
|
295
|
+
loadingLicense: dash.loadingLicense,
|
|
296
|
+
loadingDocuments: dash.loadingDocuments,
|
|
297
|
+
noDocuments: dash.noDocuments,
|
|
222
298
|
})
|
|
223
299
|
|
|
224
300
|
return {
|
|
225
301
|
name: 'sanity-plugin-seofields',
|
|
226
302
|
schema: {
|
|
227
|
-
types: types(config as SeoFieldsPluginConfig),
|
|
303
|
+
types: types(config as SeoFieldsPluginConfig),
|
|
228
304
|
},
|
|
229
|
-
...(
|
|
305
|
+
...(dash.enabled && {
|
|
230
306
|
tools: [
|
|
231
307
|
{
|
|
232
|
-
name:
|
|
233
|
-
title:
|
|
308
|
+
name: dash.toolName,
|
|
309
|
+
title: dash.toolTitle,
|
|
234
310
|
component: BoundSeoHealthTool,
|
|
235
311
|
icon: () => '📊',
|
|
236
312
|
},
|
package/src/utils/seoUtils.ts
CHANGED