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/dist/index.d.mts
CHANGED
|
@@ -221,6 +221,12 @@ export declare interface SeoFieldsPluginConfig {
|
|
|
221
221
|
icon?: string
|
|
222
222
|
title?: string
|
|
223
223
|
description?: string
|
|
224
|
+
/** Text shown while the license key is being verified. Defaults to "Verifying license…" */
|
|
225
|
+
loadingLicense?: string
|
|
226
|
+
/** Text shown while documents are being fetched. Defaults to "Loading documents…" */
|
|
227
|
+
loadingDocuments?: string
|
|
228
|
+
/** Text shown when the query returns zero results. Defaults to "No documents found" */
|
|
229
|
+
noDocuments?: string
|
|
224
230
|
}
|
|
225
231
|
display?: {
|
|
226
232
|
typeColumn?: boolean
|
|
@@ -254,6 +260,54 @@ export declare interface SeoFieldsPluginConfig {
|
|
|
254
260
|
* Obtain a license at https://sanity-plugin-seofields.thehardik.in
|
|
255
261
|
*/
|
|
256
262
|
licenseKey?: string
|
|
263
|
+
/**
|
|
264
|
+
* Map raw `_type` values to human-readable display labels.
|
|
265
|
+
* Used in both the Type column and the Type filter dropdown.
|
|
266
|
+
* Any type without an entry falls back to the raw `_type` string.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* typeLabels: { productDrug: 'Products', singleCondition: 'Condition' }
|
|
270
|
+
*/
|
|
271
|
+
typeLabels?: Record<string, string>
|
|
272
|
+
/**
|
|
273
|
+
* Controls how the document type is rendered in the Type column.
|
|
274
|
+
* - `'badge'` (default) — coloured pill
|
|
275
|
+
* - `'text'` — plain text, useful for dense layouts
|
|
276
|
+
*/
|
|
277
|
+
typeColumnMode?: 'badge' | 'text'
|
|
278
|
+
/**
|
|
279
|
+
* The document field to use as the display title in the dashboard.
|
|
280
|
+
*
|
|
281
|
+
* - `string` — use this field for every document type (e.g. `'name'`)
|
|
282
|
+
* - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* titleField: 'name'
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* titleField: { post: 'title', product: 'name', category: 'label' }
|
|
289
|
+
*/
|
|
290
|
+
titleField?: string | Record<string, string>
|
|
291
|
+
/**
|
|
292
|
+
* Callback function to render a custom badge next to the document title.
|
|
293
|
+
* Receives the full document and should return badge data or undefined.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* docBadge: (doc) => {
|
|
297
|
+
* if (doc.services === 'NHS')
|
|
298
|
+
* return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
|
|
299
|
+
* if (doc.services === 'Private')
|
|
300
|
+
* return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
|
|
301
|
+
* }
|
|
302
|
+
*/
|
|
303
|
+
docBadge?: (doc: DocumentWithSeoHealth & Record<string, unknown>) =>
|
|
304
|
+
| {
|
|
305
|
+
label: string
|
|
306
|
+
bgColor?: string
|
|
307
|
+
textColor?: string
|
|
308
|
+
fontSize?: string
|
|
309
|
+
}
|
|
310
|
+
| undefined
|
|
257
311
|
}
|
|
258
312
|
}
|
|
259
313
|
|
|
@@ -294,6 +348,71 @@ declare interface SeoHealthDashboardProps {
|
|
|
294
348
|
* Obtain a key at https://sanity-plugin-seofields.thehardik.in
|
|
295
349
|
*/
|
|
296
350
|
licenseKey?: string
|
|
351
|
+
/**
|
|
352
|
+
* Map raw `_type` values to human-readable display labels used in the
|
|
353
|
+
* Type column and the Type filter dropdown.
|
|
354
|
+
* Any type without an entry falls back to the raw `_type` string.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* typeLabels={{ productDrug: 'Products', singleCondition: 'Condition' }}
|
|
358
|
+
*/
|
|
359
|
+
typeLabels?: Record<string, string>
|
|
360
|
+
/**
|
|
361
|
+
* Controls how the type is rendered in the Type column.
|
|
362
|
+
* - `'badge'` (default) — coloured pill, consistent with score badges
|
|
363
|
+
* - `'text'` — plain text, useful for dense layouts
|
|
364
|
+
*/
|
|
365
|
+
typeColumnMode?: 'badge' | 'text'
|
|
366
|
+
/**
|
|
367
|
+
* The document field to use as the display title.
|
|
368
|
+
*
|
|
369
|
+
* - `string` — use this field for every document type (e.g. `'name'`)
|
|
370
|
+
* - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* // Same field for all types
|
|
374
|
+
* titleField: 'name'
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* // Different field per type
|
|
378
|
+
* titleField: { post: 'title', product: 'name', category: 'label' }
|
|
379
|
+
*/
|
|
380
|
+
titleField?: string | Record<string, string>
|
|
381
|
+
/**
|
|
382
|
+
* Callback function to render a custom badge next to the document title.
|
|
383
|
+
* Receives the full document and should return badge data or undefined.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* docBadge: (doc) => {
|
|
387
|
+
* if (doc.services === 'NHS')
|
|
388
|
+
* return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
|
|
389
|
+
* if (doc.services === 'Private')
|
|
390
|
+
* return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
|
|
391
|
+
* }
|
|
392
|
+
*/
|
|
393
|
+
docBadge?: (doc: DocumentWithSeoHealth & Record<string, unknown>) =>
|
|
394
|
+
| {
|
|
395
|
+
label: string
|
|
396
|
+
bgColor?: string
|
|
397
|
+
textColor?: string
|
|
398
|
+
fontSize?: string
|
|
399
|
+
}
|
|
400
|
+
| undefined
|
|
401
|
+
/**
|
|
402
|
+
* Custom text shown while the license key is being verified.
|
|
403
|
+
* Defaults to `"Verifying license…"`.
|
|
404
|
+
*/
|
|
405
|
+
loadingLicense?: React_2.ReactNode
|
|
406
|
+
/**
|
|
407
|
+
* Custom text shown while documents are being fetched.
|
|
408
|
+
* Defaults to `"Loading documents…"`.
|
|
409
|
+
*/
|
|
410
|
+
loadingDocuments?: React_2.ReactNode
|
|
411
|
+
/**
|
|
412
|
+
* Custom text shown when the query returns zero results.
|
|
413
|
+
* Defaults to `"No documents found"`.
|
|
414
|
+
*/
|
|
415
|
+
noDocuments?: React_2.ReactNode
|
|
297
416
|
}
|
|
298
417
|
|
|
299
418
|
export declare interface SeoHealthMetrics {
|
package/dist/index.d.ts
CHANGED
|
@@ -221,6 +221,12 @@ export declare interface SeoFieldsPluginConfig {
|
|
|
221
221
|
icon?: string
|
|
222
222
|
title?: string
|
|
223
223
|
description?: string
|
|
224
|
+
/** Text shown while the license key is being verified. Defaults to "Verifying license…" */
|
|
225
|
+
loadingLicense?: string
|
|
226
|
+
/** Text shown while documents are being fetched. Defaults to "Loading documents…" */
|
|
227
|
+
loadingDocuments?: string
|
|
228
|
+
/** Text shown when the query returns zero results. Defaults to "No documents found" */
|
|
229
|
+
noDocuments?: string
|
|
224
230
|
}
|
|
225
231
|
display?: {
|
|
226
232
|
typeColumn?: boolean
|
|
@@ -254,6 +260,54 @@ export declare interface SeoFieldsPluginConfig {
|
|
|
254
260
|
* Obtain a license at https://sanity-plugin-seofields.thehardik.in
|
|
255
261
|
*/
|
|
256
262
|
licenseKey?: string
|
|
263
|
+
/**
|
|
264
|
+
* Map raw `_type` values to human-readable display labels.
|
|
265
|
+
* Used in both the Type column and the Type filter dropdown.
|
|
266
|
+
* Any type without an entry falls back to the raw `_type` string.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* typeLabels: { productDrug: 'Products', singleCondition: 'Condition' }
|
|
270
|
+
*/
|
|
271
|
+
typeLabels?: Record<string, string>
|
|
272
|
+
/**
|
|
273
|
+
* Controls how the document type is rendered in the Type column.
|
|
274
|
+
* - `'badge'` (default) — coloured pill
|
|
275
|
+
* - `'text'` — plain text, useful for dense layouts
|
|
276
|
+
*/
|
|
277
|
+
typeColumnMode?: 'badge' | 'text'
|
|
278
|
+
/**
|
|
279
|
+
* The document field to use as the display title in the dashboard.
|
|
280
|
+
*
|
|
281
|
+
* - `string` — use this field for every document type (e.g. `'name'`)
|
|
282
|
+
* - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* titleField: 'name'
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* titleField: { post: 'title', product: 'name', category: 'label' }
|
|
289
|
+
*/
|
|
290
|
+
titleField?: string | Record<string, string>
|
|
291
|
+
/**
|
|
292
|
+
* Callback function to render a custom badge next to the document title.
|
|
293
|
+
* Receives the full document and should return badge data or undefined.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* docBadge: (doc) => {
|
|
297
|
+
* if (doc.services === 'NHS')
|
|
298
|
+
* return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
|
|
299
|
+
* if (doc.services === 'Private')
|
|
300
|
+
* return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
|
|
301
|
+
* }
|
|
302
|
+
*/
|
|
303
|
+
docBadge?: (doc: DocumentWithSeoHealth & Record<string, unknown>) =>
|
|
304
|
+
| {
|
|
305
|
+
label: string
|
|
306
|
+
bgColor?: string
|
|
307
|
+
textColor?: string
|
|
308
|
+
fontSize?: string
|
|
309
|
+
}
|
|
310
|
+
| undefined
|
|
257
311
|
}
|
|
258
312
|
}
|
|
259
313
|
|
|
@@ -294,6 +348,71 @@ declare interface SeoHealthDashboardProps {
|
|
|
294
348
|
* Obtain a key at https://sanity-plugin-seofields.thehardik.in
|
|
295
349
|
*/
|
|
296
350
|
licenseKey?: string
|
|
351
|
+
/**
|
|
352
|
+
* Map raw `_type` values to human-readable display labels used in the
|
|
353
|
+
* Type column and the Type filter dropdown.
|
|
354
|
+
* Any type without an entry falls back to the raw `_type` string.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* typeLabels={{ productDrug: 'Products', singleCondition: 'Condition' }}
|
|
358
|
+
*/
|
|
359
|
+
typeLabels?: Record<string, string>
|
|
360
|
+
/**
|
|
361
|
+
* Controls how the type is rendered in the Type column.
|
|
362
|
+
* - `'badge'` (default) — coloured pill, consistent with score badges
|
|
363
|
+
* - `'text'` — plain text, useful for dense layouts
|
|
364
|
+
*/
|
|
365
|
+
typeColumnMode?: 'badge' | 'text'
|
|
366
|
+
/**
|
|
367
|
+
* The document field to use as the display title.
|
|
368
|
+
*
|
|
369
|
+
* - `string` — use this field for every document type (e.g. `'name'`)
|
|
370
|
+
* - `Record<string, string>` — per-type mapping; unmapped types fall back to `title`
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* // Same field for all types
|
|
374
|
+
* titleField: 'name'
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* // Different field per type
|
|
378
|
+
* titleField: { post: 'title', product: 'name', category: 'label' }
|
|
379
|
+
*/
|
|
380
|
+
titleField?: string | Record<string, string>
|
|
381
|
+
/**
|
|
382
|
+
* Callback function to render a custom badge next to the document title.
|
|
383
|
+
* Receives the full document and should return badge data or undefined.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* docBadge: (doc) => {
|
|
387
|
+
* if (doc.services === 'NHS')
|
|
388
|
+
* return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
|
|
389
|
+
* if (doc.services === 'Private')
|
|
390
|
+
* return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
|
|
391
|
+
* }
|
|
392
|
+
*/
|
|
393
|
+
docBadge?: (doc: DocumentWithSeoHealth & Record<string, unknown>) =>
|
|
394
|
+
| {
|
|
395
|
+
label: string
|
|
396
|
+
bgColor?: string
|
|
397
|
+
textColor?: string
|
|
398
|
+
fontSize?: string
|
|
399
|
+
}
|
|
400
|
+
| undefined
|
|
401
|
+
/**
|
|
402
|
+
* Custom text shown while the license key is being verified.
|
|
403
|
+
* Defaults to `"Verifying license…"`.
|
|
404
|
+
*/
|
|
405
|
+
loadingLicense?: React_2.ReactNode
|
|
406
|
+
/**
|
|
407
|
+
* Custom text shown while documents are being fetched.
|
|
408
|
+
* Defaults to `"Loading documents…"`.
|
|
409
|
+
*/
|
|
410
|
+
loadingDocuments?: React_2.ReactNode
|
|
411
|
+
/**
|
|
412
|
+
* Custom text shown when the query returns zero results.
|
|
413
|
+
* Defaults to `"No documents found"`.
|
|
414
|
+
*/
|
|
415
|
+
noDocuments?: React_2.ReactNode
|
|
297
416
|
}
|
|
298
417
|
|
|
299
418
|
export declare interface SeoHealthMetrics {
|
package/dist/index.js
CHANGED
|
@@ -1315,6 +1315,11 @@ const DashboardContainer = dt.div`
|
|
|
1315
1315
|
`, ColTitle = dt.div`
|
|
1316
1316
|
flex: 2;
|
|
1317
1317
|
min-width: 0;
|
|
1318
|
+
`, TitleWrapper = dt.div`
|
|
1319
|
+
display: flex;
|
|
1320
|
+
align-items: center;
|
|
1321
|
+
gap: 4px;
|
|
1322
|
+
flex-wrap: wrap;
|
|
1318
1323
|
`, ColType = dt.div`
|
|
1319
1324
|
flex: 0.8;
|
|
1320
1325
|
min-width: 80px;
|
|
@@ -1354,6 +1359,20 @@ const DashboardContainer = dt.div`
|
|
|
1354
1359
|
font-weight: 500;
|
|
1355
1360
|
background: #ede9fe;
|
|
1356
1361
|
color: #5b21b6;
|
|
1362
|
+
`, TypeText = dt.span`
|
|
1363
|
+
font-size: 12px;
|
|
1364
|
+
font-weight: 500;
|
|
1365
|
+
color: #374151;
|
|
1366
|
+
`, CustomBadge = dt.span`
|
|
1367
|
+
display: inline-block;
|
|
1368
|
+
padding: 2px 6px;
|
|
1369
|
+
border-radius: 4px;
|
|
1370
|
+
font-size: ${(p) => p.$fontSize || "10px"};
|
|
1371
|
+
font-weight: 600;
|
|
1372
|
+
margin-left: 6px;
|
|
1373
|
+
background: ${(p) => p.$bgColor || "#e0e7ff"};
|
|
1374
|
+
color: ${(p) => p.$textColor || "#3730a3"};
|
|
1375
|
+
white-space: nowrap;
|
|
1357
1376
|
`, ScoreBadge = dt.span`
|
|
1358
1377
|
display: inline-block;
|
|
1359
1378
|
padding: 4px 10px;
|
|
@@ -1499,6 +1518,9 @@ const DashboardContainer = dt.div`
|
|
|
1499
1518
|
}) => {
|
|
1500
1519
|
const { onClick, href } = router.useIntentLink({ intent: "edit", params: { id, type } });
|
|
1501
1520
|
return /* @__PURE__ */ jsxRuntime.jsx(DocTitleLink, { href, onClick, title: "Open document", children });
|
|
1521
|
+
}, DocBadgeRenderer = ({ doc, docBadge }) => {
|
|
1522
|
+
const badge = docBadge(doc);
|
|
1523
|
+
return badge ? /* @__PURE__ */ jsxRuntime.jsx(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label }) : null;
|
|
1502
1524
|
}, spin = mt`
|
|
1503
1525
|
to { transform: rotate(360deg); }
|
|
1504
1526
|
`, Spinner = dt.div`
|
|
@@ -1550,7 +1572,7 @@ const DashboardContainer = dt.div`
|
|
|
1550
1572
|
totalScore += twitterScore.score, allIssues.push(...twitterScore.issues), robots2 && !robots2.noIndex && (totalScore += 5);
|
|
1551
1573
|
const status = getStatusCategory(totalScore);
|
|
1552
1574
|
return { score: totalScore, status, issues: allIssues };
|
|
1553
|
-
}, SeoHealthDashboard = ({
|
|
1575
|
+
}, resolveTypeLabel = (type, typeLabels) => typeLabels?.[type] ?? type, buildTitleProjection = (titleField) => !titleField || titleField === "title" ? "title" : typeof titleField == "string" ? `"title": ${titleField}` : `"title": select(${Object.entries(titleField).map(([type, field]) => `_type == "${type}" => ${field}`).join(", ")}, title)`, SeoHealthDashboard = ({
|
|
1554
1576
|
icon = "\u{1F4CA}",
|
|
1555
1577
|
title = "SEO Health Dashboard",
|
|
1556
1578
|
description = "Monitor and optimize SEO fields across all your documents",
|
|
@@ -1560,7 +1582,14 @@ const DashboardContainer = dt.div`
|
|
|
1560
1582
|
queryRequireSeo = !0,
|
|
1561
1583
|
customQuery,
|
|
1562
1584
|
apiVersion = "2023-01-01",
|
|
1563
|
-
licenseKey
|
|
1585
|
+
licenseKey,
|
|
1586
|
+
typeLabels,
|
|
1587
|
+
typeColumnMode = "badge",
|
|
1588
|
+
titleField,
|
|
1589
|
+
docBadge,
|
|
1590
|
+
loadingLicense,
|
|
1591
|
+
loadingDocuments,
|
|
1592
|
+
noDocuments
|
|
1564
1593
|
}) => {
|
|
1565
1594
|
const client = sanity.useClient({ apiVersion }), [licenseStatus, setLicenseStatus] = o.useState("loading"), [documents, setDocuments] = o.useState([]), [loading, setLoading] = o.useState(!0), [searchQuery, setSearchQuery] = o.useState(""), [filterStatus, setFilterStatus] = o.useState("all"), [filterType, setFilterType] = o.useState("all"), [sortBy, setSortBy] = o.useState("score"), [activePopover, setActivePopover] = o.useState(null), VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license", CACHE_TTL_MS = 3600 * 1e3, validateLicense = o.useCallback(
|
|
1566
1595
|
async (forceRefresh = !1) => {
|
|
@@ -1619,17 +1648,23 @@ const DashboardContainer = dt.div`
|
|
|
1619
1648
|
try {
|
|
1620
1649
|
setLoading(!0);
|
|
1621
1650
|
let groqQuery, params = {};
|
|
1622
|
-
|
|
1651
|
+
if (customQuery)
|
|
1652
|
+
groqQuery = customQuery;
|
|
1653
|
+
else if (queryTypes && queryTypes.length > 0) {
|
|
1654
|
+
const seoFilter = queryRequireSeo ? " && seo != null" : "", titleProj = buildTitleProjection(titleField);
|
|
1655
|
+
groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
|
|
1623
1656
|
_id,
|
|
1624
1657
|
_type,
|
|
1625
|
-
|
|
1658
|
+
${titleProj},
|
|
1626
1659
|
slug,
|
|
1627
1660
|
seo,
|
|
1628
1661
|
_updatedAt
|
|
1629
|
-
}`, params = { types: queryTypes }
|
|
1662
|
+
}`, params = { types: queryTypes };
|
|
1663
|
+
} else
|
|
1664
|
+
groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
|
|
1630
1665
|
_id,
|
|
1631
1666
|
_type,
|
|
1632
|
-
|
|
1667
|
+
${buildTitleProjection(titleField)},
|
|
1633
1668
|
slug,
|
|
1634
1669
|
seo,
|
|
1635
1670
|
_updatedAt
|
|
@@ -1645,7 +1680,7 @@ const DashboardContainer = dt.div`
|
|
|
1645
1680
|
setLoading(!1);
|
|
1646
1681
|
}
|
|
1647
1682
|
})();
|
|
1648
|
-
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes)]);
|
|
1683
|
+
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes), JSON.stringify(titleField)]);
|
|
1649
1684
|
const uniqueDocumentTypes = o.useMemo(() => {
|
|
1650
1685
|
const types2 = new Set(documents.map((doc) => doc._type));
|
|
1651
1686
|
return Array.from(types2).sort();
|
|
@@ -1663,7 +1698,7 @@ const DashboardContainer = dt.div`
|
|
|
1663
1698
|
return /* @__PURE__ */ jsxRuntime.jsxs(DashboardContainer, { children: [
|
|
1664
1699
|
licenseStatus === "loading" && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
|
|
1665
1700
|
/* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
|
|
1666
|
-
"Verifying license\u2026"
|
|
1701
|
+
loadingLicense ?? "Verifying license\u2026"
|
|
1667
1702
|
] }),
|
|
1668
1703
|
licenseStatus === "invalid" && /* @__PURE__ */ jsxRuntime.jsx(UpgradeContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1669
1704
|
/* @__PURE__ */ jsxRuntime.jsx(UpgradeLock, { children: "\u274C" }),
|
|
@@ -1790,7 +1825,7 @@ export default defineConfig({
|
|
|
1790
1825
|
onChange: (e) => setFilterType(e.currentTarget.value),
|
|
1791
1826
|
children: [
|
|
1792
1827
|
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Types" }),
|
|
1793
|
-
uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: type }, type))
|
|
1828
|
+
uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: resolveTypeLabel(type, typeLabels) }, type))
|
|
1794
1829
|
]
|
|
1795
1830
|
}
|
|
1796
1831
|
),
|
|
@@ -1809,9 +1844,9 @@ export default defineConfig({
|
|
|
1809
1844
|
/* @__PURE__ */ jsxRuntime.jsxs(TableCard, { children: [
|
|
1810
1845
|
loading && /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
|
|
1811
1846
|
/* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
|
|
1812
|
-
"Loading documents\u2026"
|
|
1847
|
+
loadingDocuments ?? "Loading documents\u2026"
|
|
1813
1848
|
] }),
|
|
1814
|
-
!loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: "No documents found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1849
|
+
!loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: noDocuments ?? "No documents found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1815
1850
|
/* @__PURE__ */ jsxRuntime.jsxs(TableHeader, { children: [
|
|
1816
1851
|
/* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: "Title" }),
|
|
1817
1852
|
showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: "Type" }),
|
|
@@ -1819,11 +1854,20 @@ export default defineConfig({
|
|
|
1819
1854
|
/* @__PURE__ */ jsxRuntime.jsx(ColIssues, { children: "Top Issues" })
|
|
1820
1855
|
] }),
|
|
1821
1856
|
filteredAndSortedDocs.map((doc) => /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
|
|
1822
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1823
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1857
|
+
/* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: /* @__PURE__ */ jsxRuntime.jsxs(TitleWrapper, { children: [
|
|
1858
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1859
|
+
/* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchor, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }),
|
|
1860
|
+
showDocumentId && /* @__PURE__ */ jsxRuntime.jsx(DocId, { children: doc._id })
|
|
1861
|
+
] }),
|
|
1862
|
+
docBadge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1863
|
+
DocBadgeRenderer,
|
|
1864
|
+
{
|
|
1865
|
+
doc,
|
|
1866
|
+
docBadge
|
|
1867
|
+
}
|
|
1868
|
+
)
|
|
1869
|
+
] }) }),
|
|
1870
|
+
showTypeColumn && /* @__PURE__ */ jsxRuntime.jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsxRuntime.jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { children: resolveTypeLabel(doc._type, typeLabels) }) }),
|
|
1827
1871
|
/* @__PURE__ */ jsxRuntime.jsx(ColScore, { children: /* @__PURE__ */ jsxRuntime.jsxs(ScoreBadge, { $score: doc.health.score, children: [
|
|
1828
1872
|
doc.health.score,
|
|
1829
1873
|
"%"
|
|
@@ -1918,8 +1962,8 @@ export default defineConfig({
|
|
|
1918
1962
|
const feedback = [], charCount = description?.length || 0;
|
|
1919
1963
|
if (!description?.trim())
|
|
1920
1964
|
return feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" }), feedback;
|
|
1921
|
-
if (charCount <
|
|
1922
|
-
text: `Description is ${charCount} chars \u2014 below recommended
|
|
1965
|
+
if (charCount < 120 ? feedback.push({
|
|
1966
|
+
text: `Description is ${charCount} chars \u2014 below recommended 120.`,
|
|
1923
1967
|
color: "orange"
|
|
1924
1968
|
}) : charCount > 160 ? feedback.push({
|
|
1925
1969
|
text: `Description is ${charCount} chars \u2014 exceeds recommended 160.`,
|
|
@@ -2808,30 +2852,60 @@ function types(config = {}) {
|
|
|
2808
2852
|
robots
|
|
2809
2853
|
];
|
|
2810
2854
|
}
|
|
2811
|
-
const
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2855
|
+
const resolveDashboardConfig = (healthDashboard) => {
|
|
2856
|
+
const cfg = typeof healthDashboard == "object" ? healthDashboard : void 0;
|
|
2857
|
+
return {
|
|
2858
|
+
enabled: healthDashboard !== !1,
|
|
2859
|
+
toolTitle: cfg?.tool?.title ?? "SEO Health",
|
|
2860
|
+
toolName: cfg?.tool?.name ?? "seo-health-dashboard",
|
|
2861
|
+
icon: cfg?.content?.icon,
|
|
2862
|
+
title: cfg?.content?.title,
|
|
2863
|
+
description: cfg?.content?.description,
|
|
2864
|
+
showTypeColumn: cfg?.display?.typeColumn,
|
|
2865
|
+
showDocumentId: cfg?.display?.documentId,
|
|
2866
|
+
queryTypes: cfg?.query?.types,
|
|
2867
|
+
queryRequireSeo: cfg?.query?.requireSeo,
|
|
2868
|
+
queryGroq: cfg?.query?.groq,
|
|
2869
|
+
apiVersion: cfg?.apiVersion,
|
|
2870
|
+
licenseKey: cfg?.licenseKey,
|
|
2871
|
+
typeLabels: cfg?.typeLabels,
|
|
2872
|
+
typeColumnMode: cfg?.typeColumnMode,
|
|
2873
|
+
titleField: cfg?.titleField,
|
|
2874
|
+
docBadge: cfg?.docBadge,
|
|
2875
|
+
loadingLicense: cfg?.content?.loadingLicense,
|
|
2876
|
+
loadingDocuments: cfg?.content?.loadingDocuments,
|
|
2877
|
+
noDocuments: cfg?.content?.noDocuments
|
|
2878
|
+
};
|
|
2879
|
+
}, seofields = sanity.definePlugin((config = {}) => {
|
|
2880
|
+
const { healthDashboard = !0 } = config, dash = resolveDashboardConfig(healthDashboard), BoundSeoHealthTool = () => o__default.default.createElement(SeoHealthTool, {
|
|
2881
|
+
icon: dash.icon,
|
|
2882
|
+
title: dash.title,
|
|
2883
|
+
description: dash.description,
|
|
2884
|
+
showTypeColumn: dash.showTypeColumn,
|
|
2885
|
+
showDocumentId: dash.showDocumentId,
|
|
2886
|
+
queryTypes: dash.queryTypes,
|
|
2887
|
+
queryRequireSeo: dash.queryRequireSeo,
|
|
2888
|
+
customQuery: dash.queryGroq,
|
|
2889
|
+
apiVersion: dash.apiVersion,
|
|
2890
|
+
licenseKey: dash.licenseKey,
|
|
2891
|
+
typeLabels: dash.typeLabels,
|
|
2892
|
+
typeColumnMode: dash.typeColumnMode,
|
|
2893
|
+
titleField: dash.titleField,
|
|
2894
|
+
docBadge: dash.docBadge,
|
|
2895
|
+
loadingLicense: dash.loadingLicense,
|
|
2896
|
+
loadingDocuments: dash.loadingDocuments,
|
|
2897
|
+
noDocuments: dash.noDocuments
|
|
2823
2898
|
});
|
|
2824
2899
|
return {
|
|
2825
2900
|
name: "sanity-plugin-seofields",
|
|
2826
2901
|
schema: {
|
|
2827
2902
|
types: types(config)
|
|
2828
|
-
// pass config down to schemas
|
|
2829
2903
|
},
|
|
2830
|
-
...
|
|
2904
|
+
...dash.enabled && {
|
|
2831
2905
|
tools: [
|
|
2832
2906
|
{
|
|
2833
|
-
name:
|
|
2834
|
-
title:
|
|
2907
|
+
name: dash.toolName,
|
|
2908
|
+
title: dash.toolTitle,
|
|
2835
2909
|
component: BoundSeoHealthTool,
|
|
2836
2910
|
icon: () => "\u{1F4CA}"
|
|
2837
2911
|
}
|