sanity-plugin-seofields 1.2.1 → 1.2.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/README.md +60 -0
- package/dist/index.d.mts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +47 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/SeoHealthDashboard.tsx +76 -13
- package/src/components/SeoHealthPane.tsx +81 -0
- package/src/index.ts +2 -0
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ A comprehensive Sanity Studio v3 plugin to manage SEO fields like meta titles, d
|
|
|
21
21
|
- ✅ **Validation**: Built-in character limits and best practices
|
|
22
22
|
- 🎛️ **Field Visibility**: Hide sitewide fields on specific content types
|
|
23
23
|
- 📊 **SEO Health Dashboard**: Studio-wide overview of SEO completeness with scores, issue highlights, and direct document links
|
|
24
|
+
- 🗂️ **Desk Structure Pane**: Embed the dashboard inside the Structure tool with `createSeoHealthPane` — supports split-pane document editing
|
|
24
25
|
|
|
25
26
|
## 📦 Installation
|
|
26
27
|
|
|
@@ -531,6 +532,65 @@ seofields({
|
|
|
531
532
|
|
|
532
533
|
> **Scoring logic:** each field earns its full points when a non-empty value is present, zero when missing. `query.groq` lets you control exactly which documents are included in the audit.
|
|
533
534
|
|
|
535
|
+
## 🗂️ Desk Structure Pane
|
|
536
|
+
|
|
537
|
+
Embed the SEO Health Dashboard **directly inside the Structure tool** as a pane with split-pane document editing — clicking any row opens the document editor to the right.
|
|
538
|
+
|
|
539
|
+
### Import
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import {createSeoHealthPane} from 'sanity-plugin-seofields'
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Usage
|
|
546
|
+
|
|
547
|
+
`createSeoHealthPane(S, options)` requires both arguments: Sanity's structure builder `S` and an options object with a required `licenseKey`. It returns a **`ComponentBuilder`** — use it **directly** as the `.child()` value.
|
|
548
|
+
|
|
549
|
+
> ⚠️ **Do NOT wrap in `S.component()`.** The function already calls `S.component()` internally. Wrapping it again causes: _"component is required for component structure item"_.
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// sanity.config.ts
|
|
553
|
+
import {defineConfig} from 'sanity'
|
|
554
|
+
import {structureTool} from 'sanity/structure'
|
|
555
|
+
import seofields, {createSeoHealthPane} from 'sanity-plugin-seofields'
|
|
556
|
+
|
|
557
|
+
export default defineConfig({
|
|
558
|
+
plugins: [
|
|
559
|
+
seofields({healthDashboard: false}), // optional: hide the top-level tool tab
|
|
560
|
+
structureTool({
|
|
561
|
+
structure: (S) =>
|
|
562
|
+
S.list()
|
|
563
|
+
.title('Content')
|
|
564
|
+
.items([
|
|
565
|
+
S.documentTypeListItem('post').title('Posts'),
|
|
566
|
+
S.divider(),
|
|
567
|
+
S.listItem()
|
|
568
|
+
.title('SEO Health')
|
|
569
|
+
.child(
|
|
570
|
+
createSeoHealthPane(S, {
|
|
571
|
+
licenseKey: 'SEOF-XXXX-XXXX-XXXX',
|
|
572
|
+
query: `*[_type == "post" && defined(seo)]{
|
|
573
|
+
_id, _type, title, slug, seo, _updatedAt
|
|
574
|
+
}`,
|
|
575
|
+
title: 'Posts SEO Health',
|
|
576
|
+
}),
|
|
577
|
+
),
|
|
578
|
+
]),
|
|
579
|
+
}),
|
|
580
|
+
],
|
|
581
|
+
})
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### `createSeoHealthPane` options
|
|
585
|
+
|
|
586
|
+
| Option | Type | Default | Description |
|
|
587
|
+
| ------------ | --------- | -------------- | --------------------------------------------------------------------- |
|
|
588
|
+
| `licenseKey` | `string` | **required** | License key (format `SEOF-XXXX-XXXX-XXXX`). |
|
|
589
|
+
| `query` | `string` | — | GROQ query. Must return `_id`, `_type`, `title`, `seo`, `_updatedAt`. |
|
|
590
|
+
| `title` | `string` | `'SEO Health'` | Pane title shown in breadcrumb |
|
|
591
|
+
| `openInPane` | `boolean` | `true` | Enable row links that open the document editor as a pane to the right. |
|
|
592
|
+
| `...rest` | — | — | All other `SeoHealthDashboardProps` |
|
|
593
|
+
|
|
534
594
|
## 🌐 Frontend Integration
|
|
535
595
|
|
|
536
596
|
### Next.js Example
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
+
import type {ComponentBuilder} from 'sanity/structure'
|
|
1
2
|
import {JSX} from 'react'
|
|
2
3
|
import {ObjectDefinition} from 'sanity'
|
|
3
4
|
import {Plugin as Plugin_2} from 'sanity'
|
|
4
5
|
import {PreviewConfig} from 'sanity'
|
|
5
6
|
import {default as React_2} from 'react'
|
|
6
7
|
import {SchemaTypeDefinition} from 'sanity'
|
|
8
|
+
import type {StructureBuilder} from 'sanity/structure'
|
|
7
9
|
|
|
8
10
|
export declare type AllFieldKeys = SeoFieldKeys | openGraphFieldKeys | twitterFieldKeys
|
|
9
11
|
|
|
10
12
|
export declare function allSchemas(config?: SeoFieldsPluginConfig): SchemaTypeDefinition[]
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Creates a desk-structure pane for the SEO Health Dashboard.
|
|
16
|
+
*
|
|
17
|
+
* Returns a **`ComponentBuilder`** with a built-in `.child()` resolver so that
|
|
18
|
+
* clicking any document row opens the document editor as a split pane to the right.
|
|
19
|
+
*
|
|
20
|
+
* Use it **directly** as the `.child()` value — do **not** wrap it in `S.component()`.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // sanity.config.ts
|
|
24
|
+
* structure: (S) =>
|
|
25
|
+
* S.list().items([
|
|
26
|
+
* S.listItem()
|
|
27
|
+
* .title('SEO Health')
|
|
28
|
+
* .child(
|
|
29
|
+
* createSeoHealthPane(S, {
|
|
30
|
+
* licenseKey: 'SEOF-XXXX-XXXX-XXXX',
|
|
31
|
+
* query: `*[_type == "post" && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`,
|
|
32
|
+
* })
|
|
33
|
+
* ),
|
|
34
|
+
* ])
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSeoHealthPane(
|
|
38
|
+
optionsOrS: StructureBuilder,
|
|
39
|
+
optionsWhenS: SeoHealthPaneOptions,
|
|
40
|
+
): ComponentBuilder
|
|
41
|
+
|
|
12
42
|
export declare interface DocumentWithSeoHealth {
|
|
13
43
|
_id: string
|
|
14
44
|
_type: string
|
|
@@ -430,6 +460,18 @@ declare interface SeoHealthDashboardProps {
|
|
|
430
460
|
* Defaults to `false`.
|
|
431
461
|
*/
|
|
432
462
|
previewMode?: boolean
|
|
463
|
+
/**
|
|
464
|
+
* When `true`, clicking a document title opens the document editor as a split
|
|
465
|
+
* pane to the right, keeping the SEO Health pane visible on the left.
|
|
466
|
+
* This uses Sanity's pane router and requires the component to be rendered
|
|
467
|
+
* inside a desk-structure pane context (i.e. via `createSeoHealthPane`).
|
|
468
|
+
*
|
|
469
|
+
* When `false` (default), clicking navigates to the document via the standard
|
|
470
|
+
* intent-link system (full navigation).
|
|
471
|
+
*
|
|
472
|
+
* This is set to `true` automatically by `createSeoHealthPane`.
|
|
473
|
+
*/
|
|
474
|
+
openInPane?: boolean
|
|
433
475
|
}
|
|
434
476
|
|
|
435
477
|
export declare interface SeoHealthMetrics {
|
|
@@ -438,6 +480,27 @@ export declare interface SeoHealthMetrics {
|
|
|
438
480
|
issues: string[]
|
|
439
481
|
}
|
|
440
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Options accepted by `createSeoHealthPane`.
|
|
485
|
+
* All props from `SeoHealthDashboardProps` are supported.
|
|
486
|
+
*
|
|
487
|
+
* `licenseKey` is **required** — the dashboard will not render without it.
|
|
488
|
+
*/
|
|
489
|
+
export declare interface SeoHealthPaneOptions extends Omit<SeoHealthDashboardProps, 'customQuery'> {
|
|
490
|
+
/** Required license key (format: `SEOF-XXXX-XXXX-XXXX`). */
|
|
491
|
+
licenseKey: string
|
|
492
|
+
/**
|
|
493
|
+
* A fully custom GROQ query used to fetch documents for the dashboard.
|
|
494
|
+
* The query must return documents with at least: `_id`, `_type`, `title`, `seo`, `_updatedAt`.
|
|
495
|
+
*
|
|
496
|
+
* Takes precedence over `queryTypes` when both are provided.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* query: `*[_type in ["post","page"] && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`
|
|
500
|
+
*/
|
|
501
|
+
query?: string
|
|
502
|
+
}
|
|
503
|
+
|
|
441
504
|
export declare type SeoHealthStatus = 'excellent' | 'good' | 'fair' | 'poor' | 'missing'
|
|
442
505
|
|
|
443
506
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
+
import type {ComponentBuilder} from 'sanity/structure'
|
|
1
2
|
import {JSX} from 'react'
|
|
2
3
|
import {ObjectDefinition} from 'sanity'
|
|
3
4
|
import {Plugin as Plugin_2} from 'sanity'
|
|
4
5
|
import {PreviewConfig} from 'sanity'
|
|
5
6
|
import {default as React_2} from 'react'
|
|
6
7
|
import {SchemaTypeDefinition} from 'sanity'
|
|
8
|
+
import type {StructureBuilder} from 'sanity/structure'
|
|
7
9
|
|
|
8
10
|
export declare type AllFieldKeys = SeoFieldKeys | openGraphFieldKeys | twitterFieldKeys
|
|
9
11
|
|
|
10
12
|
export declare function allSchemas(config?: SeoFieldsPluginConfig): SchemaTypeDefinition[]
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Creates a desk-structure pane for the SEO Health Dashboard.
|
|
16
|
+
*
|
|
17
|
+
* Returns a **`ComponentBuilder`** with a built-in `.child()` resolver so that
|
|
18
|
+
* clicking any document row opens the document editor as a split pane to the right.
|
|
19
|
+
*
|
|
20
|
+
* Use it **directly** as the `.child()` value — do **not** wrap it in `S.component()`.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // sanity.config.ts
|
|
24
|
+
* structure: (S) =>
|
|
25
|
+
* S.list().items([
|
|
26
|
+
* S.listItem()
|
|
27
|
+
* .title('SEO Health')
|
|
28
|
+
* .child(
|
|
29
|
+
* createSeoHealthPane(S, {
|
|
30
|
+
* licenseKey: 'SEOF-XXXX-XXXX-XXXX',
|
|
31
|
+
* query: `*[_type == "post" && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`,
|
|
32
|
+
* })
|
|
33
|
+
* ),
|
|
34
|
+
* ])
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSeoHealthPane(
|
|
38
|
+
optionsOrS: StructureBuilder,
|
|
39
|
+
optionsWhenS: SeoHealthPaneOptions,
|
|
40
|
+
): ComponentBuilder
|
|
41
|
+
|
|
12
42
|
export declare interface DocumentWithSeoHealth {
|
|
13
43
|
_id: string
|
|
14
44
|
_type: string
|
|
@@ -430,6 +460,18 @@ declare interface SeoHealthDashboardProps {
|
|
|
430
460
|
* Defaults to `false`.
|
|
431
461
|
*/
|
|
432
462
|
previewMode?: boolean
|
|
463
|
+
/**
|
|
464
|
+
* When `true`, clicking a document title opens the document editor as a split
|
|
465
|
+
* pane to the right, keeping the SEO Health pane visible on the left.
|
|
466
|
+
* This uses Sanity's pane router and requires the component to be rendered
|
|
467
|
+
* inside a desk-structure pane context (i.e. via `createSeoHealthPane`).
|
|
468
|
+
*
|
|
469
|
+
* When `false` (default), clicking navigates to the document via the standard
|
|
470
|
+
* intent-link system (full navigation).
|
|
471
|
+
*
|
|
472
|
+
* This is set to `true` automatically by `createSeoHealthPane`.
|
|
473
|
+
*/
|
|
474
|
+
openInPane?: boolean
|
|
433
475
|
}
|
|
434
476
|
|
|
435
477
|
export declare interface SeoHealthMetrics {
|
|
@@ -438,6 +480,27 @@ export declare interface SeoHealthMetrics {
|
|
|
438
480
|
issues: string[]
|
|
439
481
|
}
|
|
440
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Options accepted by `createSeoHealthPane`.
|
|
485
|
+
* All props from `SeoHealthDashboardProps` are supported.
|
|
486
|
+
*
|
|
487
|
+
* `licenseKey` is **required** — the dashboard will not render without it.
|
|
488
|
+
*/
|
|
489
|
+
export declare interface SeoHealthPaneOptions extends Omit<SeoHealthDashboardProps, 'customQuery'> {
|
|
490
|
+
/** Required license key (format: `SEOF-XXXX-XXXX-XXXX`). */
|
|
491
|
+
licenseKey: string
|
|
492
|
+
/**
|
|
493
|
+
* A fully custom GROQ query used to fetch documents for the dashboard.
|
|
494
|
+
* The query must return documents with at least: `_id`, `_type`, `title`, `seo`, `_updatedAt`.
|
|
495
|
+
*
|
|
496
|
+
* Takes precedence over `queryTypes` when both are provided.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* query: `*[_type in ["post","page"] && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`
|
|
500
|
+
*/
|
|
501
|
+
query?: string
|
|
502
|
+
}
|
|
503
|
+
|
|
441
504
|
export declare type SeoHealthStatus = 'excellent' | 'good' | 'fair' | 'poor' | 'missing'
|
|
442
505
|
|
|
443
506
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
-
var o = require("react"), sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), router = require("sanity/router"), ui = require("@sanity/ui");
|
|
3
|
+
var o = require("react"), sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), router = require("sanity/router"), structure = require("sanity/structure"), ui = require("@sanity/ui");
|
|
4
4
|
function _interopDefaultCompat(e) {
|
|
5
5
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
6
6
|
}
|
|
@@ -1192,16 +1192,9 @@ const DashboardContainer = dt.div`
|
|
|
1192
1192
|
color: #6b7280;
|
|
1193
1193
|
`, StatsGrid = dt.div`
|
|
1194
1194
|
display: grid;
|
|
1195
|
-
grid-template-columns: repeat(
|
|
1195
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
1196
1196
|
gap: 14px;
|
|
1197
1197
|
margin-bottom: 20px;
|
|
1198
|
-
|
|
1199
|
-
@media (max-width: 1100px) {
|
|
1200
|
-
grid-template-columns: repeat(3, 1fr);
|
|
1201
|
-
}
|
|
1202
|
-
@media (max-width: 600px) {
|
|
1203
|
-
grid-template-columns: repeat(2, 1fr);
|
|
1204
|
-
}
|
|
1205
1198
|
`, StatCard = dt.div`
|
|
1206
1199
|
background: #ffffff;
|
|
1207
1200
|
border-radius: 10px;
|
|
@@ -1334,6 +1327,11 @@ const DashboardContainer = dt.div`
|
|
|
1334
1327
|
align-items: center;
|
|
1335
1328
|
gap: 4px;
|
|
1336
1329
|
flex-wrap: wrap;
|
|
1330
|
+
min-width: 0;
|
|
1331
|
+
`, TitleCell = dt.div`
|
|
1332
|
+
min-width: 0;
|
|
1333
|
+
overflow: hidden;
|
|
1334
|
+
flex: 1;
|
|
1337
1335
|
`, ColType = dt.div`
|
|
1338
1336
|
flex: 0.8;
|
|
1339
1337
|
min-width: 80px;
|
|
@@ -1532,6 +1530,34 @@ const DashboardContainer = dt.div`
|
|
|
1532
1530
|
}) => {
|
|
1533
1531
|
const { onClick, href } = router.useIntentLink({ intent: "edit", params: { id, type } });
|
|
1534
1532
|
return /* @__PURE__ */ jsxRuntime.jsx(DocTitleLink, { href, onClick, title: "Open document", children });
|
|
1533
|
+
}, PaneLinkWrapper = dt.span`
|
|
1534
|
+
display: block;
|
|
1535
|
+
min-width: 0;
|
|
1536
|
+
overflow: hidden;
|
|
1537
|
+
|
|
1538
|
+
a {
|
|
1539
|
+
font-size: 13px;
|
|
1540
|
+
font-weight: 600;
|
|
1541
|
+
color: #4f46e5;
|
|
1542
|
+
white-space: nowrap;
|
|
1543
|
+
overflow: hidden;
|
|
1544
|
+
text-overflow: ellipsis;
|
|
1545
|
+
text-decoration: none;
|
|
1546
|
+
display: block;
|
|
1547
|
+
transition: color 0.15s;
|
|
1548
|
+
|
|
1549
|
+
&:hover {
|
|
1550
|
+
color: #4338ca;
|
|
1551
|
+
text-decoration: underline;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
`, DocTitleAnchorPane = ({
|
|
1555
|
+
id,
|
|
1556
|
+
type,
|
|
1557
|
+
children
|
|
1558
|
+
}) => {
|
|
1559
|
+
const { ChildLink } = structure.usePaneRouter();
|
|
1560
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PaneLinkWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(ChildLink, { childId: id, childParameters: { type }, children }) });
|
|
1535
1561
|
}, DocBadgeRenderer = ({ doc, docBadge }) => {
|
|
1536
1562
|
const badge = docBadge(doc);
|
|
1537
1563
|
return badge ? /* @__PURE__ */ jsxRuntime.jsx(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label }) : null;
|
|
@@ -1773,7 +1799,8 @@ const DashboardContainer = dt.div`
|
|
|
1773
1799
|
loadingLicense,
|
|
1774
1800
|
loadingDocuments,
|
|
1775
1801
|
noDocuments,
|
|
1776
|
-
previewMode = !1
|
|
1802
|
+
previewMode = !1,
|
|
1803
|
+
openInPane = !1
|
|
1777
1804
|
}) => {
|
|
1778
1805
|
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(
|
|
1779
1806
|
async (forceRefresh = !1) => {
|
|
@@ -2058,8 +2085,8 @@ export default defineConfig({
|
|
|
2058
2085
|
] }),
|
|
2059
2086
|
filteredAndSortedDocs.map((doc) => /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
|
|
2060
2087
|
/* @__PURE__ */ jsxRuntime.jsx(ColTitle, { children: /* @__PURE__ */ jsxRuntime.jsxs(TitleWrapper, { children: [
|
|
2061
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2062
|
-
/* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchor, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }),
|
|
2088
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TitleCell, { children: [
|
|
2089
|
+
openInPane ? /* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchorPane, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }) : /* @__PURE__ */ jsxRuntime.jsx(DocTitleAnchor, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }),
|
|
2063
2090
|
showDocumentId && /* @__PURE__ */ jsxRuntime.jsx(DocId, { children: doc._id })
|
|
2064
2091
|
] }),
|
|
2065
2092
|
docBadge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3121,9 +3148,17 @@ const resolveDashboardConfig = (healthDashboard) => {
|
|
|
3121
3148
|
}
|
|
3122
3149
|
};
|
|
3123
3150
|
});
|
|
3151
|
+
function createSeoHealthPane(optionsOrS, optionsWhenS) {
|
|
3152
|
+
const S2 = optionsOrS, { query, openInPane = !0, title: paneTitle, ...rest } = optionsWhenS ?? {}, SeoHealthPane = () => /* @__PURE__ */ jsxRuntime.jsx(SeoHealthDashboard, { customQuery: query, openInPane, title: paneTitle, ...rest });
|
|
3153
|
+
return SeoHealthPane.displayName = "SeoHealthPane", S2.component(SeoHealthPane).title(paneTitle ?? "SEO Health").child((docId, { params }) => {
|
|
3154
|
+
const builder = S2.document().documentId(docId);
|
|
3155
|
+
return params?.type ? builder.schemaType(params.type) : builder;
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3124
3158
|
exports.SeoHealthDashboard = SeoHealthDashboard;
|
|
3125
3159
|
exports.SeoHealthTool = SeoHealthTool;
|
|
3126
3160
|
exports.allSchemas = types;
|
|
3161
|
+
exports.createSeoHealthPane = createSeoHealthPane;
|
|
3127
3162
|
exports.default = seofields;
|
|
3128
3163
|
exports.metaAttributeSchema = metaAttribute;
|
|
3129
3164
|
exports.metaTagSchema = metaTag;
|