@asteroidcms/core-utils 0.1.6 → 0.1.8
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 +1 -1
- package/dist/client.cjs +522 -45
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +347 -2
- package/dist/client.d.ts +347 -2
- package/dist/client.js +518 -48
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +379 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +186 -1
- package/dist/index.d.ts +186 -1
- package/dist/index.js +368 -1
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +211 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +114 -0
- package/dist/next.d.ts +114 -0
- package/dist/next.js +201 -0
- package/dist/next.js.map +1 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>
|
|
3
3
|
<div style="display: inline-flex; align-items: center; gap: 4px;">
|
|
4
|
-
<img src="https://cms.theasteroid.tech/logo/
|
|
4
|
+
<img src="https://cms.theasteroid.tech/logo/logo.svg" alt="@asteroidcms" height="25px" />
|
|
5
5
|
<span>/core-utils</span>
|
|
6
6
|
</div>
|
|
7
7
|
</h1>
|
package/dist/client.cjs
CHANGED
|
@@ -1050,37 +1050,38 @@ function isIconEnabled(el) {
|
|
|
1050
1050
|
if (v === null) return false;
|
|
1051
1051
|
return v !== "false" && v !== "0";
|
|
1052
1052
|
}
|
|
1053
|
-
function enhanceCallouts(root) {
|
|
1053
|
+
function enhanceCallouts(root, calloutIcons) {
|
|
1054
1054
|
const callouts = root.querySelectorAll("aside[data-callout]");
|
|
1055
1055
|
callouts.forEach((el) => {
|
|
1056
|
-
if (el.dataset.rtCalloutEnhanced === "1") return;
|
|
1057
|
-
el.dataset.rtCalloutEnhanced = "1";
|
|
1058
|
-
if (!isIconEnabled(el)) return;
|
|
1059
1056
|
if (el.querySelector(":scope > .rt-callout-icon")) return;
|
|
1057
|
+
if (!isIconEnabled(el)) return;
|
|
1060
1058
|
const variant = calloutVariantOf(el);
|
|
1061
1059
|
const icon = document.createElement("span");
|
|
1062
1060
|
icon.className = "rt-callout-icon";
|
|
1063
1061
|
icon.dataset.variant = variant;
|
|
1064
1062
|
icon.setAttribute("aria-hidden", "true");
|
|
1063
|
+
if (!calloutIcons || !(variant in calloutIcons)) {
|
|
1064
|
+
icon.innerHTML = CALLOUT_ICON_SVG[variant] ?? CALLOUT_ICON_SVG.default;
|
|
1065
|
+
}
|
|
1065
1066
|
el.prepend(icon);
|
|
1066
1067
|
});
|
|
1068
|
+
if (!calloutIcons) return [];
|
|
1067
1069
|
const chips = [];
|
|
1068
1070
|
root.querySelectorAll(
|
|
1069
1071
|
"aside[data-callout] > .rt-callout-icon"
|
|
1070
1072
|
).forEach((chip) => {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1073
|
+
const variant = chip.dataset.variant || (chip.parentElement?.getAttribute("data-variant") ?? "default");
|
|
1074
|
+
if (variant in calloutIcons) {
|
|
1075
|
+
chips.push({ el: chip, variant });
|
|
1076
|
+
}
|
|
1075
1077
|
});
|
|
1076
1078
|
return chips;
|
|
1077
1079
|
}
|
|
1078
1080
|
function enhanceBlockquotes(root) {
|
|
1079
1081
|
const quotes = root.querySelectorAll("blockquote");
|
|
1080
1082
|
quotes.forEach((bq) => {
|
|
1081
|
-
if (bq.
|
|
1083
|
+
if (bq.querySelector(":scope > .rt-quote-open")) return;
|
|
1082
1084
|
if (bq.closest('figure[data-variant="pullquote"]')) return;
|
|
1083
|
-
bq.dataset.rtQuoted = "1";
|
|
1084
1085
|
const { first, last } = findQuoteBody(bq);
|
|
1085
1086
|
if (!first || !last) return;
|
|
1086
1087
|
const open = document.createElement("span");
|
|
@@ -1119,11 +1120,10 @@ function highlightCodeBlock(pre) {
|
|
|
1119
1120
|
if (!lang) return;
|
|
1120
1121
|
const code = pre.querySelector("code");
|
|
1121
1122
|
if (!code) return;
|
|
1122
|
-
if (code.
|
|
1123
|
+
if (code.classList.contains("hljs")) return;
|
|
1123
1124
|
const source = code.textContent ?? "";
|
|
1124
1125
|
code.innerHTML = highlightSource(source, lang);
|
|
1125
1126
|
code.classList.add("hljs");
|
|
1126
|
-
code.dataset.rtHighlighted = "1";
|
|
1127
1127
|
}
|
|
1128
1128
|
var DIFF_SEPARATOR_RE = /\n?@@---@@\n?/;
|
|
1129
1129
|
function diffLines(a, b) {
|
|
@@ -1246,8 +1246,7 @@ function buildCodeBlockLabel(pre) {
|
|
|
1246
1246
|
function enhanceCodeBlocks(root) {
|
|
1247
1247
|
const blocks = root.querySelectorAll("pre");
|
|
1248
1248
|
blocks.forEach((pre) => {
|
|
1249
|
-
if (pre.
|
|
1250
|
-
pre.dataset.rtEnhanced = "1";
|
|
1249
|
+
if (pre.classList.contains("rt-codeblock")) return;
|
|
1251
1250
|
pre.classList.add("rt-codeblock");
|
|
1252
1251
|
const variant = pre.dataset.variant;
|
|
1253
1252
|
if (variant === "diff") {
|
|
@@ -1591,7 +1590,7 @@ function BuiltinCalloutIcon({ variant }) {
|
|
|
1591
1590
|
const html = CALLOUT_ICON_SVG[variant] ?? CALLOUT_ICON_SVG.default;
|
|
1592
1591
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
1593
1592
|
}
|
|
1594
|
-
|
|
1593
|
+
var RichTextContent = react.memo(function RichTextContent2({
|
|
1595
1594
|
html,
|
|
1596
1595
|
classMap,
|
|
1597
1596
|
as = "div",
|
|
@@ -1609,49 +1608,58 @@ function RichTextContent({
|
|
|
1609
1608
|
[html, merged]
|
|
1610
1609
|
);
|
|
1611
1610
|
const ref = react.useRef(null);
|
|
1612
|
-
const
|
|
1613
|
-
react.
|
|
1611
|
+
const prevSafe = react.useRef("");
|
|
1612
|
+
const chipsRef = react.useRef([]);
|
|
1613
|
+
const onReadyRef = react.useRef(onReady);
|
|
1614
|
+
onReadyRef.current = onReady;
|
|
1615
|
+
const contentRefStable = react.useRef(contentRef);
|
|
1616
|
+
contentRefStable.current = contentRef;
|
|
1617
|
+
const calloutIconsRef = react.useRef(calloutIcons);
|
|
1618
|
+
calloutIconsRef.current = calloutIcons;
|
|
1619
|
+
const [renderKey, setRenderKey] = react.useState(0);
|
|
1620
|
+
react.useLayoutEffect(() => {
|
|
1614
1621
|
ensureCodeBlockStyles();
|
|
1615
1622
|
const root = ref.current;
|
|
1616
1623
|
if (!root) return;
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
raf = requestAnimationFrame(() => {
|
|
1631
|
-
raf = 0;
|
|
1632
|
-
apply();
|
|
1633
|
-
});
|
|
1634
|
-
});
|
|
1635
|
-
apply();
|
|
1636
|
-
return () => {
|
|
1637
|
-
mo.disconnect();
|
|
1638
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1639
|
-
};
|
|
1640
|
-
}, [safe, onReady, contentRef]);
|
|
1624
|
+
if (contentRefStable.current) contentRefStable.current.current = root;
|
|
1625
|
+
if (prevSafe.current === safe) return;
|
|
1626
|
+
prevSafe.current = safe;
|
|
1627
|
+
root.innerHTML = safe;
|
|
1628
|
+
enhanceCodeBlocks(root);
|
|
1629
|
+
enhanceBlockquotes(root);
|
|
1630
|
+
const nextChips = enhanceCallouts(root, calloutIconsRef.current);
|
|
1631
|
+
if (!calloutChipsEqual(chipsRef.current, nextChips)) {
|
|
1632
|
+
chipsRef.current = nextChips;
|
|
1633
|
+
if (nextChips.length > 0) setRenderKey((n) => n + 1);
|
|
1634
|
+
}
|
|
1635
|
+
onReadyRef.current?.(root);
|
|
1636
|
+
}, [safe]);
|
|
1641
1637
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
1642
1638
|
react.createElement(as, {
|
|
1643
1639
|
ref,
|
|
1644
|
-
className
|
|
1645
|
-
dangerouslySetInnerHTML: { __html: safe }
|
|
1640
|
+
className
|
|
1646
1641
|
}),
|
|
1647
|
-
|
|
1642
|
+
chipsRef.current.map(
|
|
1648
1643
|
(chip, i) => reactDom.createPortal(
|
|
1649
|
-
|
|
1644
|
+
calloutIconsRef.current && chip.variant in calloutIconsRef.current ? calloutIconsRef.current[chip.variant] : /* @__PURE__ */ jsxRuntime.jsx(BuiltinCalloutIcon, { variant: chip.variant }),
|
|
1650
1645
|
chip.el,
|
|
1651
1646
|
`${chip.variant}:${i}`
|
|
1652
1647
|
)
|
|
1653
1648
|
)
|
|
1654
1649
|
] });
|
|
1650
|
+
}, richTextPropsEqual);
|
|
1651
|
+
function richTextPropsEqual(prev, next) {
|
|
1652
|
+
if (prev.html !== next.html) return false;
|
|
1653
|
+
if (prev.classMap !== next.classMap) return false;
|
|
1654
|
+
if (prev.as !== next.as) return false;
|
|
1655
|
+
if (prev.className !== next.className) return false;
|
|
1656
|
+
const prevKeys = prev.calloutIcons ? Object.keys(prev.calloutIcons) : [];
|
|
1657
|
+
const nextKeys = next.calloutIcons ? Object.keys(next.calloutIcons) : [];
|
|
1658
|
+
if (prevKeys.length !== nextKeys.length) return false;
|
|
1659
|
+
for (const k of nextKeys) {
|
|
1660
|
+
if (!prevKeys.includes(k)) return false;
|
|
1661
|
+
}
|
|
1662
|
+
return true;
|
|
1655
1663
|
}
|
|
1656
1664
|
|
|
1657
1665
|
// src/utils/extractHeadings.ts
|
|
@@ -1726,12 +1734,481 @@ function extractHeadingsFromElement(root, options = {}) {
|
|
|
1726
1734
|
});
|
|
1727
1735
|
return out;
|
|
1728
1736
|
}
|
|
1737
|
+
function setMeta(attr, key, content) {
|
|
1738
|
+
let element = document.head.querySelector(
|
|
1739
|
+
`meta[${attr}="${key}"]`
|
|
1740
|
+
);
|
|
1741
|
+
if (!element) {
|
|
1742
|
+
if (!content) return;
|
|
1743
|
+
element = document.createElement("meta");
|
|
1744
|
+
element.setAttribute(attr, key);
|
|
1745
|
+
document.head.appendChild(element);
|
|
1746
|
+
} else if (!content) {
|
|
1747
|
+
element.remove();
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
element.setAttribute("content", content);
|
|
1751
|
+
}
|
|
1752
|
+
function setCanonical(url) {
|
|
1753
|
+
let canonical = document.head.querySelector(
|
|
1754
|
+
'link[rel="canonical"]'
|
|
1755
|
+
);
|
|
1756
|
+
if (!canonical) {
|
|
1757
|
+
canonical = document.createElement("link");
|
|
1758
|
+
canonical.rel = "canonical";
|
|
1759
|
+
document.head.appendChild(canonical);
|
|
1760
|
+
}
|
|
1761
|
+
canonical.href = url;
|
|
1762
|
+
}
|
|
1763
|
+
function JsonLd({ data }) {
|
|
1764
|
+
if (!data) return null;
|
|
1765
|
+
const html = JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1766
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1767
|
+
"script",
|
|
1768
|
+
{
|
|
1769
|
+
type: "application/ld+json",
|
|
1770
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1771
|
+
}
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
function Seo({
|
|
1775
|
+
title,
|
|
1776
|
+
description,
|
|
1777
|
+
url,
|
|
1778
|
+
siteName,
|
|
1779
|
+
keywords,
|
|
1780
|
+
twitter,
|
|
1781
|
+
image,
|
|
1782
|
+
noindex
|
|
1783
|
+
}) {
|
|
1784
|
+
react.useEffect(() => {
|
|
1785
|
+
document.title = title;
|
|
1786
|
+
setMeta("name", "description", description);
|
|
1787
|
+
setCanonical(url);
|
|
1788
|
+
setMeta("property", "og:title", title);
|
|
1789
|
+
setMeta("property", "og:description", description);
|
|
1790
|
+
setMeta("property", "og:url", url);
|
|
1791
|
+
setMeta("property", "og:site_name", siteName || "");
|
|
1792
|
+
setMeta("name", "keywords", keywords || "");
|
|
1793
|
+
setMeta("name", "robots", noindex ? "noindex" : "index");
|
|
1794
|
+
setMeta("name", "twitter:card", image ? "summary_large_image" : "summary");
|
|
1795
|
+
setMeta("name", "twitter:title", title);
|
|
1796
|
+
setMeta("name", "twitter:description", description);
|
|
1797
|
+
setMeta("name", "twitter:site", twitter || "");
|
|
1798
|
+
setMeta("property", "og:image", image || "");
|
|
1799
|
+
setMeta("name", "twitter:image", image || "");
|
|
1800
|
+
}, [title, description, url, siteName, keywords, twitter, image, noindex]);
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// src/seo/seo.builders.ts
|
|
1805
|
+
function applyTitleTemplate(config, title) {
|
|
1806
|
+
return config.titleTemplate ? config.titleTemplate(title) : `${title} | ${config.siteName}`;
|
|
1807
|
+
}
|
|
1808
|
+
function buildOgImageUrl(config, params) {
|
|
1809
|
+
if (config.getOgImageUrl) {
|
|
1810
|
+
return config.getOgImageUrl(params);
|
|
1811
|
+
}
|
|
1812
|
+
const palette = config.ogImage?.palette;
|
|
1813
|
+
if (!palette) return void 0;
|
|
1814
|
+
const apiPath = config.ogImage?.apiPath ?? "/api/og";
|
|
1815
|
+
const base = config.baseUrl.replace(/\/$/, "");
|
|
1816
|
+
const searchParams = new URLSearchParams({
|
|
1817
|
+
title: params.title,
|
|
1818
|
+
type: params.type ?? "article",
|
|
1819
|
+
siteName: config.siteName,
|
|
1820
|
+
bg: palette.background,
|
|
1821
|
+
fg: palette.foreground,
|
|
1822
|
+
accent: palette.accent
|
|
1823
|
+
});
|
|
1824
|
+
if (params.subtitle?.trim()) searchParams.set("subtitle", params.subtitle.trim());
|
|
1825
|
+
if (params.eyebrow?.trim()) searchParams.set("eyebrow", params.eyebrow.trim());
|
|
1826
|
+
if (palette.accentMuted) searchParams.set("accentMuted", palette.accentMuted);
|
|
1827
|
+
if (palette.mutedText) searchParams.set("muted", palette.mutedText);
|
|
1828
|
+
return `${base}${apiPath}?${searchParams.toString()}`;
|
|
1829
|
+
}
|
|
1830
|
+
function resolveArticleImage(post, config) {
|
|
1831
|
+
const featuredImage = config.cmsUrl ? cmsImage(post.featured_image, { cmsUrl: config.cmsUrl }) : "";
|
|
1832
|
+
if (featuredImage) return featuredImage;
|
|
1833
|
+
const description = post.meta_description?.trim() || post.description?.trim() || config.defaultDescription;
|
|
1834
|
+
return buildOgImageUrl(config, {
|
|
1835
|
+
title: post.title,
|
|
1836
|
+
subtitle: description,
|
|
1837
|
+
eyebrow: config.contentLabel ?? "Article",
|
|
1838
|
+
type: "article"
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
function buildArticleSeoValues(post, config, slug, options) {
|
|
1842
|
+
const articlePath = config.articlePath ?? "/blog";
|
|
1843
|
+
const url = `${config.baseUrl.replace(/\/$/, "")}${articlePath}/${slug}`;
|
|
1844
|
+
const description = post.meta_description?.trim() || post.description?.trim() || config.defaultDescription || `Read the latest from ${config.siteName}.`;
|
|
1845
|
+
return {
|
|
1846
|
+
title: applyTitleTemplate(config, post.title),
|
|
1847
|
+
siteName: config.siteName,
|
|
1848
|
+
twitter: config.twitter ?? "",
|
|
1849
|
+
description,
|
|
1850
|
+
url,
|
|
1851
|
+
keywords: config.defaultKeywords ?? post.title,
|
|
1852
|
+
image: resolveArticleImage(post, config),
|
|
1853
|
+
noindex: options?.noindex ?? config.noindex,
|
|
1854
|
+
manifestUrl: config.manifestUrl
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function buildArticleListingSeoValues(config, options) {
|
|
1858
|
+
const articlePath = config.articlePath ?? "/blog";
|
|
1859
|
+
const base = config.baseUrl.replace(/\/$/, "");
|
|
1860
|
+
const label = config.contentLabel ?? "Articles";
|
|
1861
|
+
const categoryName = options?.categoryName?.trim();
|
|
1862
|
+
const categorySlug = options?.categorySlug?.trim();
|
|
1863
|
+
const titleText = categoryName ? `${categoryName} ${label}` : label;
|
|
1864
|
+
const description = categoryName ? `Explore ${categoryName} ${label.toLowerCase()}, guides, and the latest updates from ${config.siteName}.` : config.defaultDescription || `Browse ${label.toLowerCase()}, insights, and the latest updates from ${config.siteName}.`;
|
|
1865
|
+
const url = categorySlug ? `${base}${articlePath}/category/${categorySlug}` : `${base}${articlePath}`;
|
|
1866
|
+
return {
|
|
1867
|
+
title: applyTitleTemplate(config, titleText),
|
|
1868
|
+
siteName: config.siteName,
|
|
1869
|
+
twitter: config.twitter ?? "",
|
|
1870
|
+
description,
|
|
1871
|
+
url,
|
|
1872
|
+
keywords: config.defaultKeywords ?? (categoryName ? `${categoryName}, ${config.siteName}` : `${config.siteName} ${label.toLowerCase()}`),
|
|
1873
|
+
image: buildOgImageUrl(config, {
|
|
1874
|
+
title: titleText,
|
|
1875
|
+
subtitle: description,
|
|
1876
|
+
eyebrow: categoryName ? "Category" : label,
|
|
1877
|
+
type: "listing"
|
|
1878
|
+
}),
|
|
1879
|
+
noindex: options?.noindex ?? config.noindex,
|
|
1880
|
+
manifestUrl: config.manifestUrl
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
function seoValuesToClientProps(values) {
|
|
1884
|
+
return {
|
|
1885
|
+
title: values.title,
|
|
1886
|
+
description: values.description,
|
|
1887
|
+
url: values.url,
|
|
1888
|
+
siteName: values.siteName,
|
|
1889
|
+
keywords: values.keywords,
|
|
1890
|
+
twitter: values.twitter,
|
|
1891
|
+
image: values.image,
|
|
1892
|
+
noindex: values.noindex
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// src/seo/jsonld.ts
|
|
1897
|
+
function buildArticleJsonLd(props) {
|
|
1898
|
+
return {
|
|
1899
|
+
"@context": "https://schema.org",
|
|
1900
|
+
"@type": props.articleType ?? "Article",
|
|
1901
|
+
headline: props.title,
|
|
1902
|
+
description: props.description,
|
|
1903
|
+
url: props.url,
|
|
1904
|
+
...props.image ? { image: props.image } : {},
|
|
1905
|
+
...props.publishedTime ? { datePublished: props.publishedTime } : {},
|
|
1906
|
+
...props.category ? { articleSection: props.category } : {},
|
|
1907
|
+
...props.tags && props.tags.length > 0 ? { keywords: props.tags.join(", ") } : {},
|
|
1908
|
+
author: { "@type": "Person", name: props.authorName || props.siteName },
|
|
1909
|
+
publisher: { "@id": `${props.siteUrl}/#organization` },
|
|
1910
|
+
isPartOf: { "@id": `${props.siteUrl}/#website` },
|
|
1911
|
+
inLanguage: "en-US"
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
function buildCollectionJsonLd(props) {
|
|
1915
|
+
return {
|
|
1916
|
+
"@context": "https://schema.org",
|
|
1917
|
+
"@type": "CollectionPage",
|
|
1918
|
+
name: props.name,
|
|
1919
|
+
description: props.description,
|
|
1920
|
+
url: props.url,
|
|
1921
|
+
isPartOf: { "@id": `${props.siteUrl}/#website` },
|
|
1922
|
+
publisher: { "@id": `${props.siteUrl}/#organization` },
|
|
1923
|
+
inLanguage: "en-US"
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function AsteroidArticlePage(props) {
|
|
1927
|
+
const {
|
|
1928
|
+
slug,
|
|
1929
|
+
useArticle,
|
|
1930
|
+
seo,
|
|
1931
|
+
articleType,
|
|
1932
|
+
backLink,
|
|
1933
|
+
renderRoot,
|
|
1934
|
+
renderSkeleton,
|
|
1935
|
+
renderError,
|
|
1936
|
+
renderHeader,
|
|
1937
|
+
renderMeta,
|
|
1938
|
+
renderDescription,
|
|
1939
|
+
renderFeaturedImage,
|
|
1940
|
+
renderToc,
|
|
1941
|
+
renderContent,
|
|
1942
|
+
renderPreArticle,
|
|
1943
|
+
renderMidArticle,
|
|
1944
|
+
renderPostArticle,
|
|
1945
|
+
renderTags,
|
|
1946
|
+
renderAuthorDetails,
|
|
1947
|
+
renderRelatedPosts,
|
|
1948
|
+
renderCTA,
|
|
1949
|
+
renderJsonLd,
|
|
1950
|
+
noindex,
|
|
1951
|
+
children
|
|
1952
|
+
} = props;
|
|
1953
|
+
const { data: article, loading, error } = useArticle(slug);
|
|
1954
|
+
const cmsConfig = react.useContext(AsteroidCMSContext);
|
|
1955
|
+
const seoConfig = seo && !seo.cmsUrl && cmsConfig?.cmsUrl ? { ...seo, cmsUrl: cmsConfig.cmsUrl } : seo;
|
|
1956
|
+
if (children) {
|
|
1957
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children({ data: article, loading, error }) });
|
|
1958
|
+
}
|
|
1959
|
+
if (loading) {
|
|
1960
|
+
const body2 = renderSkeleton?.() ?? null;
|
|
1961
|
+
return renderRoot ? renderRoot({ children: body2 }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: body2 });
|
|
1962
|
+
}
|
|
1963
|
+
if (!article || error) {
|
|
1964
|
+
const body2 = renderError?.({ error, reason: error ? "error" : "not-found" }) ?? null;
|
|
1965
|
+
return renderRoot ? renderRoot({ children: body2 }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: body2 });
|
|
1966
|
+
}
|
|
1967
|
+
const seoValues = seoConfig && article ? buildArticleSeoValues(article, seoConfig, slug, { noindex }) : null;
|
|
1968
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1969
|
+
seoValues ? /* @__PURE__ */ jsxRuntime.jsx(Seo, { ...seoValuesToClientProps(seoValues) }) : null,
|
|
1970
|
+
seoConfig && article ? renderJsonLd?.({ post: article }) ?? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1971
|
+
JsonLd,
|
|
1972
|
+
{
|
|
1973
|
+
data: buildArticleJsonLd({
|
|
1974
|
+
title: article.title,
|
|
1975
|
+
description: article.description || seoConfig.defaultDescription || "",
|
|
1976
|
+
url: `${(seoConfig.baseUrl || "").replace(/\/$/, "")}${seoConfig.articlePath ?? "/blog"}/${slug}`,
|
|
1977
|
+
siteName: seoConfig.siteName,
|
|
1978
|
+
siteUrl: (seoConfig.baseUrl || "").replace(/\/$/, ""),
|
|
1979
|
+
articleType,
|
|
1980
|
+
image: seoValues?.image,
|
|
1981
|
+
authorName: article.author?.name,
|
|
1982
|
+
publishedTime: article.published_date || void 0,
|
|
1983
|
+
tags: article.tags?.split(",").map((t) => t.trim()).filter(Boolean),
|
|
1984
|
+
category: article.category?.name
|
|
1985
|
+
})
|
|
1986
|
+
}
|
|
1987
|
+
) : null,
|
|
1988
|
+
backLink,
|
|
1989
|
+
renderPreArticle?.({ post: article }),
|
|
1990
|
+
renderHeader?.({ post: article }),
|
|
1991
|
+
renderMeta?.({ post: article }),
|
|
1992
|
+
renderDescription?.({ post: article }),
|
|
1993
|
+
renderFeaturedImage?.({ post: article }),
|
|
1994
|
+
renderToc?.({ post: article }),
|
|
1995
|
+
renderContent?.({ post: article }),
|
|
1996
|
+
renderMidArticle?.({ post: article }),
|
|
1997
|
+
renderTags?.({ post: article }),
|
|
1998
|
+
renderAuthorDetails?.({ post: article }),
|
|
1999
|
+
renderRelatedPosts?.({ post: article }),
|
|
2000
|
+
renderCTA?.({ post: article }),
|
|
2001
|
+
renderPostArticle?.({ post: article })
|
|
2002
|
+
] });
|
|
2003
|
+
return renderRoot ? renderRoot({ children: body }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: body });
|
|
2004
|
+
}
|
|
2005
|
+
function useDebouncedValue(value, delay) {
|
|
2006
|
+
const [debouncedValue, setDebouncedValue] = react.useState(value);
|
|
2007
|
+
react.useEffect(() => {
|
|
2008
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
2009
|
+
return () => clearTimeout(timer);
|
|
2010
|
+
}, [value, delay]);
|
|
2011
|
+
return debouncedValue;
|
|
2012
|
+
}
|
|
2013
|
+
function defaultGetCategoryName(post) {
|
|
2014
|
+
return post.category?.name?.trim() || void 0;
|
|
2015
|
+
}
|
|
2016
|
+
function defaultGroupPostsByCategory(posts) {
|
|
2017
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2018
|
+
for (const post of posts) {
|
|
2019
|
+
const categoryName = defaultGetCategoryName(post) || "Other";
|
|
2020
|
+
const categorySlug = post.category?.slug || "other";
|
|
2021
|
+
const existing = groups.get(categorySlug);
|
|
2022
|
+
if (existing) {
|
|
2023
|
+
existing.posts.push(post);
|
|
2024
|
+
} else {
|
|
2025
|
+
groups.set(categorySlug, { categoryName, categorySlug, posts: [post] });
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return Array.from(groups.values());
|
|
2029
|
+
}
|
|
2030
|
+
function applyPostFilters(posts, {
|
|
2031
|
+
categorySlug,
|
|
2032
|
+
articleSlug
|
|
2033
|
+
}) {
|
|
2034
|
+
let filtered = posts;
|
|
2035
|
+
if (categorySlug) {
|
|
2036
|
+
filtered = filtered.filter((post) => post.category?.slug === categorySlug);
|
|
2037
|
+
}
|
|
2038
|
+
if (articleSlug) {
|
|
2039
|
+
filtered = filtered.filter((post) => post.slug === articleSlug);
|
|
2040
|
+
}
|
|
2041
|
+
return filtered;
|
|
2042
|
+
}
|
|
2043
|
+
function splitFeaturedAndRest(posts) {
|
|
2044
|
+
const featured = posts.find((post) => post.featured) ?? null;
|
|
2045
|
+
const rest = featured ? posts.filter((post) => post.slug !== featured.slug) : [...posts];
|
|
2046
|
+
return { featured, rest };
|
|
2047
|
+
}
|
|
2048
|
+
function useAsteroidArticlesState({
|
|
2049
|
+
usePosts,
|
|
2050
|
+
categorySlug,
|
|
2051
|
+
articleSlug,
|
|
2052
|
+
searchDebounceMs = 800,
|
|
2053
|
+
groupPostsByCategory = defaultGroupPostsByCategory
|
|
2054
|
+
}) {
|
|
2055
|
+
const [searchQuery, setSearchQuery] = react.useState("");
|
|
2056
|
+
const debouncedSearchQuery = useDebouncedValue(searchQuery, searchDebounceMs);
|
|
2057
|
+
const { posts: rawPosts, loading, error } = usePosts(debouncedSearchQuery);
|
|
2058
|
+
const posts = react.useMemo(
|
|
2059
|
+
() => applyPostFilters(rawPosts, { categorySlug, articleSlug }),
|
|
2060
|
+
[rawPosts, categorySlug, articleSlug]
|
|
2061
|
+
);
|
|
2062
|
+
const { featured, rest } = react.useMemo(
|
|
2063
|
+
() => splitFeaturedAndRest(posts),
|
|
2064
|
+
[posts]
|
|
2065
|
+
);
|
|
2066
|
+
const isSearching = debouncedSearchQuery.trim().length > 0;
|
|
2067
|
+
const categoryGroups = react.useMemo(() => {
|
|
2068
|
+
if (isSearching) {
|
|
2069
|
+
if (posts.length === 0) return [];
|
|
2070
|
+
return [
|
|
2071
|
+
{
|
|
2072
|
+
categoryName: `Search results for "${debouncedSearchQuery.trim()}"`,
|
|
2073
|
+
categorySlug: "search-results",
|
|
2074
|
+
posts
|
|
2075
|
+
}
|
|
2076
|
+
];
|
|
2077
|
+
}
|
|
2078
|
+
return groupPostsByCategory(rest);
|
|
2079
|
+
}, [isSearching, posts, debouncedSearchQuery, rest, groupPostsByCategory]);
|
|
2080
|
+
const hasError = Boolean(error);
|
|
2081
|
+
const isEmpty = !featured && rest.length === 0;
|
|
2082
|
+
return {
|
|
2083
|
+
posts,
|
|
2084
|
+
featured,
|
|
2085
|
+
rest,
|
|
2086
|
+
categoryGroups,
|
|
2087
|
+
loading,
|
|
2088
|
+
error,
|
|
2089
|
+
hasError,
|
|
2090
|
+
isEmpty,
|
|
2091
|
+
isSearching,
|
|
2092
|
+
searchQuery,
|
|
2093
|
+
debouncedSearchQuery,
|
|
2094
|
+
setSearchQuery
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function AsteroidArticlesListing(props) {
|
|
2098
|
+
const {
|
|
2099
|
+
eyebrow,
|
|
2100
|
+
title,
|
|
2101
|
+
description,
|
|
2102
|
+
seo,
|
|
2103
|
+
categorySlug,
|
|
2104
|
+
renderRoot,
|
|
2105
|
+
renderHeader,
|
|
2106
|
+
renderSearch,
|
|
2107
|
+
renderFeaturedCard,
|
|
2108
|
+
renderPostCard,
|
|
2109
|
+
renderCategoryHeading,
|
|
2110
|
+
renderPostGrid,
|
|
2111
|
+
renderCategoryGroup,
|
|
2112
|
+
renderSkeleton,
|
|
2113
|
+
renderEmpty,
|
|
2114
|
+
renderContent,
|
|
2115
|
+
renderJsonLd,
|
|
2116
|
+
noindex,
|
|
2117
|
+
children
|
|
2118
|
+
} = props;
|
|
2119
|
+
const state = useAsteroidArticlesState(props);
|
|
2120
|
+
const categoryName = categorySlug ? state.posts[0]?.category?.name?.trim() : void 0;
|
|
2121
|
+
if (children) {
|
|
2122
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(state) });
|
|
2123
|
+
}
|
|
2124
|
+
const seoNode = seo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2125
|
+
Seo,
|
|
2126
|
+
{
|
|
2127
|
+
...seoValuesToClientProps(
|
|
2128
|
+
buildArticleListingSeoValues(seo, {
|
|
2129
|
+
categoryName,
|
|
2130
|
+
categorySlug,
|
|
2131
|
+
noindex
|
|
2132
|
+
})
|
|
2133
|
+
)
|
|
2134
|
+
}
|
|
2135
|
+
) : null;
|
|
2136
|
+
const jsonLdNode = seo ? renderJsonLd?.(state) ?? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2137
|
+
JsonLd,
|
|
2138
|
+
{
|
|
2139
|
+
data: buildCollectionJsonLd({
|
|
2140
|
+
name: categoryName || `${seo.siteName} ${seo.contentLabel ?? "Articles"}`,
|
|
2141
|
+
description: seo.defaultDescription || "",
|
|
2142
|
+
url: `${(seo.baseUrl || "").replace(/\/$/, "")}${seo.articlePath ?? "/blog"}${categorySlug ? `/category/${categorySlug}` : ""}`,
|
|
2143
|
+
siteUrl: (seo.baseUrl || "").replace(/\/$/, "")
|
|
2144
|
+
})
|
|
2145
|
+
}
|
|
2146
|
+
) : null;
|
|
2147
|
+
const handleSearchSubmit = (event) => {
|
|
2148
|
+
event.preventDefault();
|
|
2149
|
+
};
|
|
2150
|
+
const searchNode = renderSearch ? renderSearch({
|
|
2151
|
+
value: state.searchQuery,
|
|
2152
|
+
onChange: state.setSearchQuery,
|
|
2153
|
+
onSubmit: handleSearchSubmit
|
|
2154
|
+
}) : null;
|
|
2155
|
+
const headerNode = renderHeader ? renderHeader({ eyebrow, title, description, search: searchNode }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2156
|
+
eyebrow,
|
|
2157
|
+
title,
|
|
2158
|
+
description,
|
|
2159
|
+
searchNode
|
|
2160
|
+
] });
|
|
2161
|
+
const featuredNode = state.featured && !state.isSearching && renderFeaturedCard ? renderFeaturedCard({ post: state.featured }) : null;
|
|
2162
|
+
const noSearchResultsNode = state.isSearching && !state.loading && state.posts.length === 0 ? renderEmpty?.({
|
|
2163
|
+
reason: "no-results",
|
|
2164
|
+
searchQuery: state.debouncedSearchQuery.trim()
|
|
2165
|
+
}) ?? null : null;
|
|
2166
|
+
const groupsNode = noSearchResultsNode ? null : state.categoryGroups.map((group) => {
|
|
2167
|
+
const postCards = group.posts.map((post, index) => /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: renderPostCard({ post, index, group }) }, post.slug ?? index));
|
|
2168
|
+
const gridNode = renderPostGrid ? renderPostGrid({ posts: group.posts, group, children: postCards }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: postCards });
|
|
2169
|
+
const headingNode = renderCategoryHeading ? renderCategoryHeading({ group }) : group.categoryName;
|
|
2170
|
+
const defaultContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2171
|
+
headingNode,
|
|
2172
|
+
gridNode
|
|
2173
|
+
] });
|
|
2174
|
+
return renderCategoryGroup ? renderCategoryGroup({ group, defaultContent }) : defaultContent;
|
|
2175
|
+
});
|
|
2176
|
+
const contentNode = !state.loading && !state.hasError && (state.featured || state.rest.length > 0) ? renderContent ? renderContent({
|
|
2177
|
+
featured: featuredNode,
|
|
2178
|
+
groups: groupsNode,
|
|
2179
|
+
noSearchResults: noSearchResultsNode
|
|
2180
|
+
}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2181
|
+
featuredNode,
|
|
2182
|
+
noSearchResultsNode,
|
|
2183
|
+
groupsNode
|
|
2184
|
+
] }) : null;
|
|
2185
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2186
|
+
seoNode,
|
|
2187
|
+
jsonLdNode,
|
|
2188
|
+
headerNode,
|
|
2189
|
+
state.loading ? renderSkeleton?.() : null,
|
|
2190
|
+
!state.loading && (state.hasError || state.isEmpty) ? renderEmpty?.({
|
|
2191
|
+
reason: state.hasError ? "error" : "no-posts",
|
|
2192
|
+
searchQuery: state.debouncedSearchQuery.trim(),
|
|
2193
|
+
error: state.error
|
|
2194
|
+
}) : null,
|
|
2195
|
+
contentNode
|
|
2196
|
+
] });
|
|
2197
|
+
return renderRoot ? renderRoot({ children: body }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: body });
|
|
2198
|
+
}
|
|
1729
2199
|
|
|
2200
|
+
exports.AsteroidArticlePage = AsteroidArticlePage;
|
|
2201
|
+
exports.AsteroidArticlesListing = AsteroidArticlesListing;
|
|
1730
2202
|
exports.AsteroidCMSProvider = AsteroidCMSProvider;
|
|
2203
|
+
exports.JsonLd = JsonLd;
|
|
1731
2204
|
exports.RichTextContent = RichTextContent;
|
|
2205
|
+
exports.Seo = Seo;
|
|
2206
|
+
exports.defaultGetCategoryName = defaultGetCategoryName;
|
|
2207
|
+
exports.defaultGroupPostsByCategory = defaultGroupPostsByCategory;
|
|
1732
2208
|
exports.extractHeadingsFromElement = extractHeadingsFromElement;
|
|
1733
2209
|
exports.extractHeadingsFromHtml = extractHeadingsFromHtml;
|
|
1734
2210
|
exports.slugify = slugify;
|
|
2211
|
+
exports.useAsteroidArticlesState = useAsteroidArticlesState;
|
|
1735
2212
|
exports.useAsteroidCMSConfig = useAsteroidCMSConfig;
|
|
1736
2213
|
exports.useCmsContent = useCmsContent;
|
|
1737
2214
|
exports.useCmsImage = useCmsImage;
|