sanity-plugin-seofields 1.2.0 → 1.2.1
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 +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +223 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +223 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/SeoHealthDashboard.tsx +246 -8
- package/src/plugin.ts +13 -0
package/dist/index.mjs
CHANGED
|
@@ -1171,6 +1171,20 @@ const DashboardContainer = dt.div`
|
|
|
1171
1171
|
font-weight: 700;
|
|
1172
1172
|
color: #111827;
|
|
1173
1173
|
letter-spacing: -0.3px;
|
|
1174
|
+
display: flex;
|
|
1175
|
+
align-items: center;
|
|
1176
|
+
gap: 10px;
|
|
1177
|
+
`, PreviewBadge = dt.span`
|
|
1178
|
+
display: inline-block;
|
|
1179
|
+
background: #fef3c7;
|
|
1180
|
+
color: #92400e;
|
|
1181
|
+
font-size: 11px;
|
|
1182
|
+
font-weight: 600;
|
|
1183
|
+
padding: 4px 8px;
|
|
1184
|
+
border-radius: 4px;
|
|
1185
|
+
text-transform: uppercase;
|
|
1186
|
+
letter-spacing: 0.5px;
|
|
1187
|
+
margin-left: 8px;
|
|
1174
1188
|
`, PageSubtitle = dt.p`
|
|
1175
1189
|
margin: 0;
|
|
1176
1190
|
font-size: 13px;
|
|
@@ -1356,8 +1370,8 @@ const DashboardContainer = dt.div`
|
|
|
1356
1370
|
border-radius: 5px;
|
|
1357
1371
|
font-size: 11px;
|
|
1358
1372
|
font-weight: 500;
|
|
1359
|
-
background: #ede9fe;
|
|
1360
|
-
color: #5b21b6;
|
|
1373
|
+
background: ${(p) => p.$bgColor || "#ede9fe"};
|
|
1374
|
+
color: ${(p) => p.$textColor || "#5b21b6"};
|
|
1361
1375
|
`, TypeText = dt.span`
|
|
1362
1376
|
font-size: 12px;
|
|
1363
1377
|
font-weight: 500;
|
|
@@ -1540,7 +1554,48 @@ const DashboardContainer = dt.div`
|
|
|
1540
1554
|
text-align: center;
|
|
1541
1555
|
color: #9ca3af;
|
|
1542
1556
|
font-size: 13px;
|
|
1543
|
-
`,
|
|
1557
|
+
`, TYPE_COLOR_PALETTE = [
|
|
1558
|
+
{ bg: "#dbeafe", text: "#0c4a6e" },
|
|
1559
|
+
// Blue
|
|
1560
|
+
{ bg: "#dcfce7", text: "#14532d" },
|
|
1561
|
+
// Green
|
|
1562
|
+
{ bg: "#fce7f3", text: "#500724" },
|
|
1563
|
+
// Pink
|
|
1564
|
+
{ bg: "#fed7aa", text: "#7c2d12" },
|
|
1565
|
+
// Orange
|
|
1566
|
+
{ bg: "#e9d5ff", text: "#581c87" },
|
|
1567
|
+
// Purple
|
|
1568
|
+
{ bg: "#f3e8ff", text: "#3f0f5c" },
|
|
1569
|
+
// Deep Purple
|
|
1570
|
+
{ bg: "#ccfbf1", text: "#134e4a" },
|
|
1571
|
+
// Teal
|
|
1572
|
+
{ bg: "#ddd6fe", text: "#3730a3" },
|
|
1573
|
+
// Indigo
|
|
1574
|
+
{ bg: "#fca5a5", text: "#7f1d1d" },
|
|
1575
|
+
// Red
|
|
1576
|
+
{ bg: "#a7f3d0", text: "#065f46" },
|
|
1577
|
+
// Emerald
|
|
1578
|
+
{ bg: "#fbbf24", text: "#78350f" },
|
|
1579
|
+
// Amber
|
|
1580
|
+
{ bg: "#c4b5fd", text: "#3b0764" },
|
|
1581
|
+
// Violet
|
|
1582
|
+
{ bg: "#f0fdf4", text: "#15803d" },
|
|
1583
|
+
// Light Green
|
|
1584
|
+
{ bg: "#fef2f2", text: "#991b1b" },
|
|
1585
|
+
// Light Red
|
|
1586
|
+
{ bg: "#f5f3ff", text: "#5b21b6" },
|
|
1587
|
+
// Light Purple
|
|
1588
|
+
{ bg: "#fffbeb", text: "#92400e" }
|
|
1589
|
+
// Light Amber
|
|
1590
|
+
], getTypeColor = (type) => {
|
|
1591
|
+
let hash2 = 0;
|
|
1592
|
+
for (let i = 0; i < type.length; i += 1) {
|
|
1593
|
+
const char2 = type.charCodeAt(i);
|
|
1594
|
+
hash2 = Math.abs(hash2 * 31 + char2);
|
|
1595
|
+
}
|
|
1596
|
+
const colorIndex = hash2 % TYPE_COLOR_PALETTE.length;
|
|
1597
|
+
return TYPE_COLOR_PALETTE[colorIndex];
|
|
1598
|
+
}, getStatusCategory = (score) => score >= 80 ? "excellent" : score >= 60 ? "good" : score >= 40 ? "fair" : score > 0 ? "poor" : "missing", scoreMetaTitle = (title) => {
|
|
1544
1599
|
const issues = [];
|
|
1545
1600
|
let score = 0;
|
|
1546
1601
|
return title && title.length >= 50 && title.length <= 60 ? score = 15 : title && title.length > 0 ? (score = 10, title.length < 50 && issues.push("Meta title too short (< 50 chars)"), title.length > 60 && issues.push("Meta title too long (> 60 chars)")) : issues.push("Missing meta title"), { score, issues };
|
|
@@ -1571,7 +1626,135 @@ const DashboardContainer = dt.div`
|
|
|
1571
1626
|
totalScore += twitterScore.score, allIssues.push(...twitterScore.issues), robots2 && !robots2.noIndex && (totalScore += 5);
|
|
1572
1627
|
const status = getStatusCategory(totalScore);
|
|
1573
1628
|
return { score: totalScore, status, issues: allIssues };
|
|
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)`,
|
|
1629
|
+
}, 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)`, generateDummyData = () => [
|
|
1630
|
+
{
|
|
1631
|
+
_id: "preview-post-1",
|
|
1632
|
+
_type: "post",
|
|
1633
|
+
title: "Getting Started with SEO Best Practices",
|
|
1634
|
+
slug: { current: "getting-started-seo" },
|
|
1635
|
+
_updatedAt: new Date(Date.now() - 1728e5).toISOString(),
|
|
1636
|
+
seo: {
|
|
1637
|
+
title: "Getting Started with SEO Best Practices | My Blog",
|
|
1638
|
+
description: "Learn the fundamentals of SEO optimization to improve your website visibility and search rankings.",
|
|
1639
|
+
keywords: ["seo", "best practices", "optimization"],
|
|
1640
|
+
metaImage: { _type: "image", asset: { _ref: "image-123", _type: "reference" } },
|
|
1641
|
+
openGraph: {
|
|
1642
|
+
title: "SEO Best Practices Guide",
|
|
1643
|
+
description: "Master SEO optimization",
|
|
1644
|
+
image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "SEO Guide" },
|
|
1645
|
+
type: "article"
|
|
1646
|
+
},
|
|
1647
|
+
twitter: {
|
|
1648
|
+
title: "SEO Best Practices",
|
|
1649
|
+
description: "Learn SEO optimization",
|
|
1650
|
+
image: { _type: "image", asset: { _ref: "image-123", _type: "reference" }, alt: "Guide" },
|
|
1651
|
+
card: "summary_large_image"
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
_id: "preview-post-2",
|
|
1657
|
+
_type: "post",
|
|
1658
|
+
title: "Advanced Analytics Strategy",
|
|
1659
|
+
slug: { current: "advanced-analytics" },
|
|
1660
|
+
_updatedAt: new Date(Date.now() - 432e6).toISOString(),
|
|
1661
|
+
seo: {
|
|
1662
|
+
title: "Advanced Analytics",
|
|
1663
|
+
description: "Strategy tips",
|
|
1664
|
+
keywords: ["analytics", "data"],
|
|
1665
|
+
openGraph: {
|
|
1666
|
+
title: "Analytics Guide"
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
_id: "preview-page-1",
|
|
1672
|
+
_type: "page",
|
|
1673
|
+
title: "About Us",
|
|
1674
|
+
slug: { current: "about" },
|
|
1675
|
+
_updatedAt: new Date(Date.now() - 864e6).toISOString(),
|
|
1676
|
+
seo: {
|
|
1677
|
+
title: "About",
|
|
1678
|
+
keywords: ["company", "team"],
|
|
1679
|
+
metaImage: { _type: "image", asset: { _ref: "image-456", _type: "reference" } }
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
_id: "preview-post-3",
|
|
1684
|
+
_type: "post",
|
|
1685
|
+
title: "Content Marketing Trends for 2024",
|
|
1686
|
+
slug: { current: "content-marketing-trends" },
|
|
1687
|
+
_updatedAt: new Date(Date.now() - 864e5).toISOString(),
|
|
1688
|
+
seo: {
|
|
1689
|
+
title: "Content Marketing Trends 2024",
|
|
1690
|
+
description: "Discover the latest content marketing trends and strategies to engage your audience effectively.",
|
|
1691
|
+
keywords: ["content marketing", "trends", "strategy", "engagement"],
|
|
1692
|
+
metaImage: { _type: "image", asset: { _ref: "image-789", _type: "reference" } },
|
|
1693
|
+
openGraph: {
|
|
1694
|
+
title: "Content Marketing Trends 2024",
|
|
1695
|
+
description: "Latest trends in content marketing",
|
|
1696
|
+
image: { _type: "image", asset: { _ref: "image-789", _type: "reference" }, alt: "Trends" },
|
|
1697
|
+
type: "article"
|
|
1698
|
+
},
|
|
1699
|
+
twitter: {
|
|
1700
|
+
title: "Content Marketing Trends",
|
|
1701
|
+
description: "Discover the latest trends",
|
|
1702
|
+
card: "summary"
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
_id: "preview-post-4",
|
|
1708
|
+
_type: "product",
|
|
1709
|
+
title: "Pro Plan",
|
|
1710
|
+
slug: { current: "pro-plan" },
|
|
1711
|
+
_updatedAt: new Date(Date.now() - 1296e6).toISOString(),
|
|
1712
|
+
seo: {
|
|
1713
|
+
title: "Pro",
|
|
1714
|
+
keywords: ["pricing"]
|
|
1715
|
+
}
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
_id: "preview-page-2",
|
|
1719
|
+
_type: "page",
|
|
1720
|
+
title: "Contact",
|
|
1721
|
+
slug: { current: "contact" },
|
|
1722
|
+
_updatedAt: new Date(Date.now() - 6912e5).toISOString(),
|
|
1723
|
+
seo: {
|
|
1724
|
+
openGraph: {
|
|
1725
|
+
title: "Get in Touch"
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
_id: "preview-post-5",
|
|
1731
|
+
_type: "post",
|
|
1732
|
+
title: "Mobile Optimization Guide",
|
|
1733
|
+
slug: { current: "mobile-optimization" },
|
|
1734
|
+
_updatedAt: new Date(Date.now() - 2592e5).toISOString(),
|
|
1735
|
+
seo: {
|
|
1736
|
+
title: "Mobile Optimization Guide: Best Practices for Responsive Design",
|
|
1737
|
+
description: "Complete guide to mobile optimization including responsive design, performance tips, and user experience best practices for modern web development.",
|
|
1738
|
+
keywords: ["mobile", "optimization", "responsive", "performance"],
|
|
1739
|
+
metaImage: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" } },
|
|
1740
|
+
openGraph: {
|
|
1741
|
+
title: "Mobile Optimization Best Practices",
|
|
1742
|
+
description: "Master mobile web optimization",
|
|
1743
|
+
image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
|
|
1744
|
+
type: "article"
|
|
1745
|
+
},
|
|
1746
|
+
twitter: {
|
|
1747
|
+
title: "Mobile Optimization Tips",
|
|
1748
|
+
description: "Responsive design best practices",
|
|
1749
|
+
image: { _type: "image", asset: { _ref: "image-mobile", _type: "reference" }, alt: "Mobile" },
|
|
1750
|
+
card: "summary_large_image"
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
].map((doc) => ({
|
|
1755
|
+
...doc,
|
|
1756
|
+
health: calculateHealthScore(doc)
|
|
1757
|
+
})), SeoHealthDashboard = ({
|
|
1575
1758
|
icon = "\u{1F4CA}",
|
|
1576
1759
|
title = "SEO Health Dashboard",
|
|
1577
1760
|
description = "Monitor and optimize SEO fields across all your documents",
|
|
@@ -1588,10 +1771,15 @@ const DashboardContainer = dt.div`
|
|
|
1588
1771
|
docBadge,
|
|
1589
1772
|
loadingLicense,
|
|
1590
1773
|
loadingDocuments,
|
|
1591
|
-
noDocuments
|
|
1774
|
+
noDocuments,
|
|
1775
|
+
previewMode = !1
|
|
1592
1776
|
}) => {
|
|
1593
1777
|
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(
|
|
1594
1778
|
async (forceRefresh = !1) => {
|
|
1779
|
+
if (previewMode) {
|
|
1780
|
+
setLicenseStatus("valid");
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1595
1783
|
if (!licenseKey) {
|
|
1596
1784
|
setLicenseStatus("invalid");
|
|
1597
1785
|
return;
|
|
@@ -1631,11 +1819,11 @@ const DashboardContainer = dt.div`
|
|
|
1631
1819
|
}
|
|
1632
1820
|
},
|
|
1633
1821
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1634
|
-
[licenseKey]
|
|
1822
|
+
[licenseKey, previewMode]
|
|
1635
1823
|
);
|
|
1636
1824
|
useEffect(() => {
|
|
1637
1825
|
validateLicense();
|
|
1638
|
-
}, [licenseKey]);
|
|
1826
|
+
}, [licenseKey, previewMode]);
|
|
1639
1827
|
const handleMouseEnterIssues = (el, issues) => {
|
|
1640
1828
|
if (!el) return;
|
|
1641
1829
|
const rect = el.getBoundingClientRect(), popoverWidth = 280, viewportWidth = window.innerWidth;
|
|
@@ -1645,7 +1833,10 @@ const DashboardContainer = dt.div`
|
|
|
1645
1833
|
useEffect(() => {
|
|
1646
1834
|
(async () => {
|
|
1647
1835
|
try {
|
|
1648
|
-
setLoading(!0)
|
|
1836
|
+
if (setLoading(!0), previewMode) {
|
|
1837
|
+
setDocuments(generateDummyData());
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1649
1840
|
let groqQuery, params = {};
|
|
1650
1841
|
if (customQuery)
|
|
1651
1842
|
groqQuery = customQuery;
|
|
@@ -1679,7 +1870,16 @@ const DashboardContainer = dt.div`
|
|
|
1679
1870
|
setLoading(!1);
|
|
1680
1871
|
}
|
|
1681
1872
|
})();
|
|
1682
|
-
}, [
|
|
1873
|
+
}, [
|
|
1874
|
+
client,
|
|
1875
|
+
customQuery,
|
|
1876
|
+
queryRequireSeo,
|
|
1877
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1878
|
+
JSON.stringify(queryTypes),
|
|
1879
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1880
|
+
JSON.stringify(titleField),
|
|
1881
|
+
previewMode
|
|
1882
|
+
]);
|
|
1683
1883
|
const uniqueDocumentTypes = useMemo(() => {
|
|
1684
1884
|
const types2 = new Set(documents.map((doc) => doc._type));
|
|
1685
1885
|
return Array.from(types2).sort();
|
|
@@ -1748,9 +1948,12 @@ export default defineConfig({
|
|
|
1748
1948
|
licenseStatus === "valid" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1749
1949
|
/* @__PURE__ */ jsxs(PageHeader, { children: [
|
|
1750
1950
|
/* @__PURE__ */ jsxs(PageTitle, { children: [
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1951
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1952
|
+
icon,
|
|
1953
|
+
" ",
|
|
1954
|
+
title
|
|
1955
|
+
] }),
|
|
1956
|
+
previewMode && /* @__PURE__ */ jsx(PreviewBadge, { children: "Preview Mode" })
|
|
1754
1957
|
] }),
|
|
1755
1958
|
/* @__PURE__ */ jsx(PageSubtitle, { children: description })
|
|
1756
1959
|
] }),
|
|
@@ -1866,7 +2069,10 @@ export default defineConfig({
|
|
|
1866
2069
|
}
|
|
1867
2070
|
)
|
|
1868
2071
|
] }) }),
|
|
1869
|
-
showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) :
|
|
2072
|
+
showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: typeColumnMode === "text" ? /* @__PURE__ */ jsx(TypeText, { children: resolveTypeLabel(doc._type, typeLabels) }) : (() => {
|
|
2073
|
+
const typeColor = getTypeColor(doc._type);
|
|
2074
|
+
return /* @__PURE__ */ jsx(TypeBadge, { $bgColor: typeColor.bg, $textColor: typeColor.text, children: resolveTypeLabel(doc._type, typeLabels) });
|
|
2075
|
+
})() }),
|
|
1870
2076
|
/* @__PURE__ */ jsx(ColScore, { children: /* @__PURE__ */ jsxs(ScoreBadge, { $score: doc.health.score, children: [
|
|
1871
2077
|
doc.health.score,
|
|
1872
2078
|
"%"
|
|
@@ -2873,7 +3079,8 @@ const resolveDashboardConfig = (healthDashboard) => {
|
|
|
2873
3079
|
docBadge: cfg?.docBadge,
|
|
2874
3080
|
loadingLicense: cfg?.content?.loadingLicense,
|
|
2875
3081
|
loadingDocuments: cfg?.content?.loadingDocuments,
|
|
2876
|
-
noDocuments: cfg?.content?.noDocuments
|
|
3082
|
+
noDocuments: cfg?.content?.noDocuments,
|
|
3083
|
+
previewMode: cfg?.previewMode
|
|
2877
3084
|
};
|
|
2878
3085
|
}, seofields = definePlugin((config = {}) => {
|
|
2879
3086
|
const { healthDashboard = !0 } = config, dash = resolveDashboardConfig(healthDashboard), BoundSeoHealthTool = () => o.createElement(SeoHealthTool, {
|
|
@@ -2893,7 +3100,8 @@ const resolveDashboardConfig = (healthDashboard) => {
|
|
|
2893
3100
|
docBadge: dash.docBadge,
|
|
2894
3101
|
loadingLicense: dash.loadingLicense,
|
|
2895
3102
|
loadingDocuments: dash.loadingDocuments,
|
|
2896
|
-
noDocuments: dash.noDocuments
|
|
3103
|
+
noDocuments: dash.noDocuments,
|
|
3104
|
+
previewMode: dash.previewMode
|
|
2897
3105
|
});
|
|
2898
3106
|
return {
|
|
2899
3107
|
name: "sanity-plugin-seofields",
|