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.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
- customQuery ? groqQuery = customQuery : queryTypes && queryTypes.length > 0 ? (groqQuery = `*[_type in $types${queryRequireSeo ? " && seo != null" : ""} && !(_id in path("drafts.**"))]{
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
- title,
1657
+ ${titleProj},
1625
1658
  slug,
1626
1659
  seo,
1627
1660
  _updatedAt
1628
- }`, params = { types: queryTypes }) : groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
1661
+ }`, params = { types: queryTypes };
1662
+ } else
1663
+ groqQuery = `*[seo != null && !(_id in path("drafts.**"))]{
1629
1664
  _id,
1630
1665
  _type,
1631
- title,
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__ */ jsxs(ColTitle, { children: [
1822
- /* @__PURE__ */ jsx(DocTitleAnchor, { id: doc._id, type: doc._type, children: doc.title || "Untitled" }),
1823
- showDocumentId && /* @__PURE__ */ jsx(DocId, { children: doc._id })
1824
- ] }),
1825
- showTypeColumn && /* @__PURE__ */ jsx(ColType, { children: /* @__PURE__ */ jsx(TypeBadge, { children: doc._type }) }),
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 < 150 ? feedback.push({
1921
- text: `Description is ${charCount} chars \u2014 below recommended 150.`,
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 seofields = definePlugin((config = {}) => {
2811
- const { healthDashboard = !0 } = config, dashboardEnabled = healthDashboard !== !1, dashboardToolTitle = typeof healthDashboard == "object" ? healthDashboard.tool?.title ?? "SEO Health" : "SEO Health", dashboardName = typeof healthDashboard == "object" ? healthDashboard.tool?.name ?? "seo-health-dashboard" : "seo-health-dashboard", dashboardPageIcon = typeof healthDashboard == "object" ? healthDashboard.content?.icon ?? void 0 : void 0, dashboardPageTitle = typeof healthDashboard == "object" ? healthDashboard.content?.title ?? void 0 : void 0, dashboardDescription = typeof healthDashboard == "object" ? healthDashboard.content?.description ?? void 0 : void 0, dashboardShowTypeColumn = typeof healthDashboard == "object" ? healthDashboard.display?.typeColumn ?? void 0 : void 0, dashboardShowDocumentId = typeof healthDashboard == "object" ? healthDashboard.display?.documentId ?? void 0 : void 0, dashboardQueryTypes = typeof healthDashboard == "object" ? healthDashboard.query?.types ?? void 0 : void 0, dashboardQueryRequireSeo = typeof healthDashboard == "object" ? healthDashboard.query?.requireSeo ?? void 0 : void 0, dashboardQueryGroq = typeof healthDashboard == "object" ? healthDashboard.query?.groq ?? void 0 : void 0, dashboardApiVersion = typeof healthDashboard == "object" ? healthDashboard.apiVersion ?? void 0 : void 0, dashboardLicenseKey = typeof healthDashboard == "object" ? healthDashboard.licenseKey ?? void 0 : void 0, BoundSeoHealthTool = () => o.createElement(SeoHealthTool, {
2812
- icon: dashboardPageIcon,
2813
- title: dashboardPageTitle,
2814
- description: dashboardDescription,
2815
- showTypeColumn: dashboardShowTypeColumn,
2816
- showDocumentId: dashboardShowDocumentId,
2817
- queryTypes: dashboardQueryTypes,
2818
- queryRequireSeo: dashboardQueryRequireSeo,
2819
- customQuery: dashboardQueryGroq,
2820
- apiVersion: dashboardApiVersion,
2821
- licenseKey: dashboardLicenseKey
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
- ...dashboardEnabled && {
2903
+ ...dash.enabled && {
2830
2904
  tools: [
2831
2905
  {
2832
- name: dashboardName,
2833
- title: dashboardToolTitle,
2906
+ name: dash.toolName,
2907
+ title: dash.toolTitle,
2834
2908
  component: BoundSeoHealthTool,
2835
2909
  icon: () => "\u{1F4CA}"
2836
2910
  }