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.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
- `, getStatusCategory = (score) => score >= 80 ? "excellent" : score >= 60 ? "good" : score >= 40 ? "fair" : score > 0 ? "poor" : "missing", scoreMetaTitle = (title) => {
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)`, SeoHealthDashboard = ({
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
- }, [client, customQuery, queryRequireSeo, JSON.stringify(queryTypes), JSON.stringify(titleField)]);
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
- icon,
1752
- " ",
1753
- title
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) }) : /* @__PURE__ */ jsx(TypeBadge, { 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",