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.mjs
CHANGED
|
@@ -1314,6 +1314,11 @@ const DashboardContainer = dt.div`
|
|
|
1314
1314
|
`, ColTitle = dt.div`
|
|
1315
1315
|
flex: 2;
|
|
1316
1316
|
min-width: 0;
|
|
1317
|
+
`, TitleWrapper = dt.div`
|
|
1318
|
+
display: flex;
|
|
1319
|
+
align-items: center;
|
|
1320
|
+
gap: 4px;
|
|
1321
|
+
flex-wrap: wrap;
|
|
1317
1322
|
`, ColType = dt.div`
|
|
1318
1323
|
flex: 0.8;
|
|
1319
1324
|
min-width: 80px;
|
|
@@ -1353,6 +1358,20 @@ const DashboardContainer = dt.div`
|
|
|
1353
1358
|
font-weight: 500;
|
|
1354
1359
|
background: #ede9fe;
|
|
1355
1360
|
color: #5b21b6;
|
|
1361
|
+
`, TypeText = dt.span`
|
|
1362
|
+
font-size: 12px;
|
|
1363
|
+
font-weight: 500;
|
|
1364
|
+
color: #374151;
|
|
1365
|
+
`, CustomBadge = dt.span`
|
|
1366
|
+
display: inline-block;
|
|
1367
|
+
padding: 2px 6px;
|
|
1368
|
+
border-radius: 4px;
|
|
1369
|
+
font-size: ${(p) => p.$fontSize || "10px"};
|
|
1370
|
+
font-weight: 600;
|
|
1371
|
+
margin-left: 6px;
|
|
1372
|
+
background: ${(p) => p.$bgColor || "#e0e7ff"};
|
|
1373
|
+
color: ${(p) => p.$textColor || "#3730a3"};
|
|
1374
|
+
white-space: nowrap;
|
|
1356
1375
|
`, ScoreBadge = dt.span`
|
|
1357
1376
|
display: inline-block;
|
|
1358
1377
|
padding: 4px 10px;
|
|
@@ -1498,6 +1517,9 @@ const DashboardContainer = dt.div`
|
|
|
1498
1517
|
}) => {
|
|
1499
1518
|
const { onClick, href } = useIntentLink({ intent: "edit", params: { id, type } });
|
|
1500
1519
|
return /* @__PURE__ */ jsx(DocTitleLink, { href, onClick, title: "Open document", children });
|
|
1520
|
+
}, DocBadgeRenderer = ({ doc, docBadge }) => {
|
|
1521
|
+
const badge = docBadge(doc);
|
|
1522
|
+
return badge ? /* @__PURE__ */ jsx(CustomBadge, { $bgColor: badge.bgColor, $textColor: badge.textColor, $fontSize: badge.fontSize, children: badge.label }) : null;
|
|
1501
1523
|
}, spin = mt`
|
|
1502
1524
|
to { transform: rotate(360deg); }
|
|
1503
1525
|
`, Spinner = dt.div`
|
|
@@ -1549,7 +1571,7 @@ const DashboardContainer = dt.div`
|
|
|
1549
1571
|
totalScore += twitterScore.score, allIssues.push(...twitterScore.issues), robots2 && !robots2.noIndex && (totalScore += 5);
|
|
1550
1572
|
const status = getStatusCategory(totalScore);
|
|
1551
1573
|
return { score: totalScore, status, issues: allIssues };
|
|
1552
|
-
}, SeoHealthDashboard = ({
|
|
1574
|
+
}, 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 = ({
|
|
1553
1575
|
icon = "\u{1F4CA}",
|
|
1554
1576
|
title = "SEO Health Dashboard",
|
|
1555
1577
|
description = "Monitor and optimize SEO fields across all your documents",
|
|
@@ -1559,7 +1581,14 @@ const DashboardContainer = dt.div`
|
|
|
1559
1581
|
queryRequireSeo = !0,
|
|
1560
1582
|
customQuery,
|
|
1561
1583
|
apiVersion = "2023-01-01",
|
|
1562
|
-
licenseKey
|
|
1584
|
+
licenseKey,
|
|
1585
|
+
typeLabels,
|
|
1586
|
+
typeColumnMode = "badge",
|
|
1587
|
+
titleField,
|
|
1588
|
+
docBadge,
|
|
1589
|
+
loadingLicense,
|
|
1590
|
+
loadingDocuments,
|
|
1591
|
+
noDocuments
|
|
1563
1592
|
}) => {
|
|
1564
1593
|
const client = useClient({ apiVersion }), [licenseStatus, setLicenseStatus] = useState("loading"), [documents, setDocuments] = useState([]), [loading, setLoading] = useState(!0), [searchQuery, setSearchQuery] = useState(""), [filterStatus, setFilterStatus] = useState("all"), [filterType, setFilterType] = useState("all"), [sortBy, setSortBy] = useState("score"), [activePopover, setActivePopover] = useState(null), VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license", CACHE_TTL_MS = 3600 * 1e3, validateLicense = useCallback(
|
|
1565
1594
|
async (forceRefresh = !1) => {
|
|
@@ -1618,17 +1647,23 @@ const DashboardContainer = dt.div`
|
|
|
1618
1647
|
try {
|
|
1619
1648
|
setLoading(!0);
|
|
1620
1649
|
let groqQuery, params = {};
|
|
1621
|
-
|
|
1650
|
+
if (customQuery)
|
|
1651
|
+
groqQuery = customQuery;
|
|
1652
|
+
else if (queryTypes && queryTypes.length > 0) {
|
|
1653
|
+
const seoFilter = queryRequireSeo ? " && seo != null" : "", titleProj = buildTitleProjection(titleField);
|
|
1654
|
+
groqQuery = `*[_type in $types${seoFilter} && !(_id in path("drafts.**"))]{
|
|
1622
1655
|
_id,
|
|
1623
1656
|
_type,
|
|
1624
|
-
|
|
1657
|
+
${titleProj},
|
|
1625
1658
|
slug,
|
|
1626
1659
|
seo,
|
|
1627
1660
|
_updatedAt
|
|
1628
|
-
}`, params = { types: queryTypes }
|
|
1661
|
+
}`, params = { types: queryTypes };
|
|
1662
|
+
} else
|
|
1663
|
+
groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
|
|
1629
1664
|
_id,
|
|
1630
1665
|
_type,
|
|
1631
|
-
|
|
1666
|
+
${buildTitleProjection(titleField)},
|
|
1632
1667
|
slug,
|
|
1633
1668
|
seo,
|
|
1634
1669
|
_updatedAt
|
|
@@ -1644,7 +1679,7 @@ const DashboardContainer = dt.div`
|
|
|
1644
1679
|
setLoading(!1);
|
|
1645
1680
|
}
|
|
1646
1681
|
})();
|
|
1647
|
-
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes)]);
|
|
1682
|
+
}, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes), JSON.stringify(titleField)]);
|
|
1648
1683
|
const uniqueDocumentTypes = useMemo(() => {
|
|
1649
1684
|
const types2 = new Set(documents.map((doc) => doc._type));
|
|
1650
1685
|
return Array.from(types2).sort();
|
|
@@ -1662,7 +1697,7 @@ const DashboardContainer = dt.div`
|
|
|
1662
1697
|
return /* @__PURE__ */ jsxs(DashboardContainer, { children: [
|
|
1663
1698
|
licenseStatus === "loading" && /* @__PURE__ */ jsxs(LoadingState, { style: { padding: "80px 24px" }, children: [
|
|
1664
1699
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
1665
|
-
"Verifying license\u2026"
|
|
1700
|
+
loadingLicense ?? "Verifying license\u2026"
|
|
1666
1701
|
] }),
|
|
1667
1702
|
licenseStatus === "invalid" && /* @__PURE__ */ jsx(UpgradeContainer, { children: /* @__PURE__ */ jsx(UpgradeBox, { children: licenseKey ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1668
1703
|
/* @__PURE__ */ jsx(UpgradeLock, { children: "\u274C" }),
|
|
@@ -1789,7 +1824,7 @@ export default defineConfig({
|
|
|
1789
1824
|
onChange: (e) => setFilterType(e.currentTarget.value),
|
|
1790
1825
|
children: [
|
|
1791
1826
|
/* @__PURE__ */ jsx("option", { value: "all", children: "All Types" }),
|
|
1792
|
-
uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: type }, type))
|
|
1827
|
+
uniqueDocumentTypes.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: resolveTypeLabel(type, typeLabels) }, type))
|
|
1793
1828
|
]
|
|
1794
1829
|
}
|
|
1795
1830
|
),
|
|
@@ -1808,9 +1843,9 @@ export default defineConfig({
|
|
|
1808
1843
|
/* @__PURE__ */ jsxs(TableCard, { children: [
|
|
1809
1844
|
loading && /* @__PURE__ */ jsxs(LoadingState, { children: [
|
|
1810
1845
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
1811
|
-
"Loading documents\u2026"
|
|
1846
|
+
loadingDocuments ?? "Loading documents\u2026"
|
|
1812
1847
|
] }),
|
|
1813
|
-
!loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { children: "No documents found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1848
|
+
!loading && (filteredAndSortedDocs.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { children: noDocuments ?? "No documents found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1814
1849
|
/* @__PURE__ */ jsxs(TableHeader, { children: [
|
|
1815
1850
|
/* @__PURE__ */ jsx(ColTitle, { children: "Title" }),
|
|
1816
1851
|
showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: "Type" }),
|
|
@@ -1818,11 +1853,20 @@ export default defineConfig({
|
|
|
1818
1853
|
/* @__PURE__ */ jsx(ColIssues, { children: "Top Issues" })
|
|
1819
1854
|
] }),
|
|
1820
1855
|
filteredAndSortedDocs.map((doc) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
1821
|
-
/* @__PURE__ */
|
|
1822
|
-
/* @__PURE__ */
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1856
|
+
/* @__PURE__ */ jsx(ColTitle, { children: /* @__PURE__ */ jsxs(TitleWrapper, { children: [
|
|
1857
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1858
|
+
/* @__PURE__ */ jsx(DocTitleAnchor, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }),
|
|
1859
|
+
showDocumentId && /* @__PURE__ */ jsx(DocId, { children: doc._id })
|
|
1860
|
+
] }),
|
|
1861
|
+
docBadge && /* @__PURE__ */ jsx(
|
|
1862
|
+
DocBadgeRenderer,
|
|
1863
|
+
{
|
|
1864
|
+
doc,
|
|
1865
|
+
docBadge
|
|
1866
|
+
}
|
|
1867
|
+
)
|
|
1868
|
+
] }) }),
|
|
1869
|
+
showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : /* @__PURE__ */ jsx(TypeBadge, { children: resolveTypeLabel(doc._type, typeLabels) }) }),
|
|
1826
1870
|
/* @__PURE__ */ jsx(ColScore, { children: /* @__PURE__ */ jsxs(ScoreBadge, { $score: doc.health.score, children: [
|
|
1827
1871
|
doc.health.score,
|
|
1828
1872
|
"%"
|
|
@@ -1917,8 +1961,8 @@ export default defineConfig({
|
|
|
1917
1961
|
const feedback = [], charCount = description?.length || 0;
|
|
1918
1962
|
if (!description?.trim())
|
|
1919
1963
|
return feedback.push({ text: "Meta description is empty. Add content to improve SEO.", color: "red" }), feedback;
|
|
1920
|
-
if (charCount <
|
|
1921
|
-
text: `Description is ${charCount} chars \u2014 below recommended
|
|
1964
|
+
if (charCount < 120 ? feedback.push({
|
|
1965
|
+
text: `Description is ${charCount} chars \u2014 below recommended 120.`,
|
|
1922
1966
|
color: "orange"
|
|
1923
1967
|
}) : charCount > 160 ? feedback.push({
|
|
1924
1968
|
text: `Description is ${charCount} chars \u2014 exceeds recommended 160.`,
|
|
@@ -2807,30 +2851,60 @@ function types(config = {}) {
|
|
|
2807
2851
|
robots
|
|
2808
2852
|
];
|
|
2809
2853
|
}
|
|
2810
|
-
const
|
|
2811
|
-
const
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2854
|
+
const resolveDashboardConfig = (healthDashboard) => {
|
|
2855
|
+
const cfg = typeof healthDashboard == "object" ? healthDashboard : void 0;
|
|
2856
|
+
return {
|
|
2857
|
+
enabled: healthDashboard !== !1,
|
|
2858
|
+
toolTitle: cfg?.tool?.title ?? "SEO Health",
|
|
2859
|
+
toolName: cfg?.tool?.name ?? "seo-health-dashboard",
|
|
2860
|
+
icon: cfg?.content?.icon,
|
|
2861
|
+
title: cfg?.content?.title,
|
|
2862
|
+
description: cfg?.content?.description,
|
|
2863
|
+
showTypeColumn: cfg?.display?.typeColumn,
|
|
2864
|
+
showDocumentId: cfg?.display?.documentId,
|
|
2865
|
+
queryTypes: cfg?.query?.types,
|
|
2866
|
+
queryRequireSeo: cfg?.query?.requireSeo,
|
|
2867
|
+
queryGroq: cfg?.query?.groq,
|
|
2868
|
+
apiVersion: cfg?.apiVersion,
|
|
2869
|
+
licenseKey: cfg?.licenseKey,
|
|
2870
|
+
typeLabels: cfg?.typeLabels,
|
|
2871
|
+
typeColumnMode: cfg?.typeColumnMode,
|
|
2872
|
+
titleField: cfg?.titleField,
|
|
2873
|
+
docBadge: cfg?.docBadge,
|
|
2874
|
+
loadingLicense: cfg?.content?.loadingLicense,
|
|
2875
|
+
loadingDocuments: cfg?.content?.loadingDocuments,
|
|
2876
|
+
noDocuments: cfg?.content?.noDocuments
|
|
2877
|
+
};
|
|
2878
|
+
}, seofields = definePlugin((config = {}) => {
|
|
2879
|
+
const { healthDashboard = !0 } = config, dash = resolveDashboardConfig(healthDashboard), BoundSeoHealthTool = () => o.createElement(SeoHealthTool, {
|
|
2880
|
+
icon: dash.icon,
|
|
2881
|
+
title: dash.title,
|
|
2882
|
+
description: dash.description,
|
|
2883
|
+
showTypeColumn: dash.showTypeColumn,
|
|
2884
|
+
showDocumentId: dash.showDocumentId,
|
|
2885
|
+
queryTypes: dash.queryTypes,
|
|
2886
|
+
queryRequireSeo: dash.queryRequireSeo,
|
|
2887
|
+
customQuery: dash.queryGroq,
|
|
2888
|
+
apiVersion: dash.apiVersion,
|
|
2889
|
+
licenseKey: dash.licenseKey,
|
|
2890
|
+
typeLabels: dash.typeLabels,
|
|
2891
|
+
typeColumnMode: dash.typeColumnMode,
|
|
2892
|
+
titleField: dash.titleField,
|
|
2893
|
+
docBadge: dash.docBadge,
|
|
2894
|
+
loadingLicense: dash.loadingLicense,
|
|
2895
|
+
loadingDocuments: dash.loadingDocuments,
|
|
2896
|
+
noDocuments: dash.noDocuments
|
|
2822
2897
|
});
|
|
2823
2898
|
return {
|
|
2824
2899
|
name: "sanity-plugin-seofields",
|
|
2825
2900
|
schema: {
|
|
2826
2901
|
types: types(config)
|
|
2827
|
-
// pass config down to schemas
|
|
2828
2902
|
},
|
|
2829
|
-
...
|
|
2903
|
+
...dash.enabled && {
|
|
2830
2904
|
tools: [
|
|
2831
2905
|
{
|
|
2832
|
-
name:
|
|
2833
|
-
title:
|
|
2906
|
+
name: dash.toolName,
|
|
2907
|
+
title: dash.toolTitle,
|
|
2834
2908
|
component: BoundSeoHealthTool,
|
|
2835
2909
|
icon: () => "\u{1F4CA}"
|
|
2836
2910
|
}
|