@asteroidcms/core-utils 0.1.5 → 0.1.7
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 +78 -70
- package/dist/client.cjs +53 -137
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -43
- package/dist/client.d.ts +6 -43
- package/dist/client.js +56 -139
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +90 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -6
- package/dist/index.d.ts +40 -6
- package/dist/index.js +89 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- **Provider-driven** - configure `cmsUrl`, `apiKey`, and Apollo behavior in one place
|
|
12
12
|
- **API-key auth only** - sends `x-api-key` on every request, nothing else
|
|
13
13
|
- **Typed hooks** - `useCmsContent` / `useCmsMutate` build GraphQL on the fly from a declarative selection
|
|
14
|
+
- **Server helpers** - `fetchCmsContent` / `cmsMutate` for Next.js Server Components, Route Handlers, and scripts
|
|
14
15
|
- **Tree-shakeable** - ESM + CJS + types, `@apollo/client`/`react` as peer deps
|
|
15
16
|
|
|
16
17
|
---
|
|
@@ -34,7 +35,7 @@ npm install @apollo/client-integration-nextjs # for nextjs (optional)
|
|
|
34
35
|
Wrap your app once:
|
|
35
36
|
|
|
36
37
|
```tsx
|
|
37
|
-
import { AsteroidCMSProvider } from "@asteroidcms/core-utils";
|
|
38
|
+
import { AsteroidCMSProvider } from "@asteroidcms/core-utils/client";
|
|
38
39
|
|
|
39
40
|
export function Root() {
|
|
40
41
|
return (
|
|
@@ -51,7 +52,7 @@ export function Root() {
|
|
|
51
52
|
Then use the hooks anywhere:
|
|
52
53
|
|
|
53
54
|
```tsx
|
|
54
|
-
import { useCmsContent, useCmsImage } from "@asteroidcms/core-utils";
|
|
55
|
+
import { useCmsContent, useCmsImage } from "@asteroidcms/core-utils/client";
|
|
55
56
|
|
|
56
57
|
function NewsList() {
|
|
57
58
|
const cmsImage = useCmsImage();
|
|
@@ -197,9 +198,7 @@ const { data: topStories } = useCmsContent({
|
|
|
197
198
|
|
|
198
199
|
## `fetchCmsContent` (Next.js / RSC)
|
|
199
200
|
|
|
200
|
-
Server-side counterpart to `useCmsContent`. Use it in Next.js Server Components,
|
|
201
|
-
|
|
202
|
-
Pass a `getClient` function that returns a server-side Apollo client. The shape matches what `registerApolloClient` from `@apollo/client-integration-nextjs` already returns, so you can hand it through directly.
|
|
201
|
+
Server-side counterpart to `useCmsContent`. Use it in Next.js Server Components, Route Handlers, or any other server context. Accepts a `getClient` function plus the same options object as `useCmsContent`, and returns the resolved data directly.
|
|
203
202
|
|
|
204
203
|
```ts
|
|
205
204
|
// app/lib/cms-server.ts
|
|
@@ -242,28 +241,6 @@ const articles = await fetchCmsContent<Article[]>(getClient, {
|
|
|
242
241
|
|
|
243
242
|
Outside Next.js you can pass any `() => ApolloClient` - e.g. `() => createApolloClient({ cmsUrl, apiKey })`.
|
|
244
243
|
|
|
245
|
-
Add `import "server-only"` in the file that calls it if you want Next.js to fail the build when it leaks into a client component.
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## `buildCmsQuery`
|
|
250
|
-
|
|
251
|
-
Lower-level helper that turns a declarative selection into a GraphQL `DocumentNode` plus variables. Used internally by `useCmsContent` and `fetchCmsContent`; exported so you can drive your own Apollo calls (cache reads, prefetching, batching, etc.).
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
import { buildCmsQuery } from "@asteroidcms/core-utils";
|
|
255
|
-
|
|
256
|
-
const { query, variables, isSingle } = buildCmsQuery({
|
|
257
|
-
schema_slug: "news",
|
|
258
|
-
limit: 10,
|
|
259
|
-
status: "PUBLISHED",
|
|
260
|
-
select: ["title", "slug"],
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const { data } = await apolloClient.query({ query, variables });
|
|
264
|
-
const entries = isSingle ? data.entry : data.entries;
|
|
265
|
-
```
|
|
266
|
-
|
|
267
244
|
---
|
|
268
245
|
|
|
269
246
|
## `useCmsMutate`
|
|
@@ -310,6 +287,69 @@ removeComment();
|
|
|
310
287
|
|
|
311
288
|
---
|
|
312
289
|
|
|
290
|
+
## `cmsMutate` (Next.js / RSC)
|
|
291
|
+
|
|
292
|
+
Server-side counterpart to `useCmsMutate`. Use it in Route Handlers, webhooks, cron jobs, or build scripts.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { cmsMutate } from "@asteroidcms/core-utils";
|
|
296
|
+
import { getClient } from "@/app/lib/cms-server";
|
|
297
|
+
|
|
298
|
+
// Create
|
|
299
|
+
const entry = await cmsMutate<{ id: string }>(getClient, {
|
|
300
|
+
schema_slug: "newsletter_subscribers",
|
|
301
|
+
mutationType: "create",
|
|
302
|
+
variables: { data: { email: "user@example.com", name: "Alice" } },
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Update
|
|
306
|
+
await cmsMutate(getClient, {
|
|
307
|
+
schema_slug: "news",
|
|
308
|
+
mutationType: "update",
|
|
309
|
+
entryId: "abc123",
|
|
310
|
+
variables: { data: { title: "Updated title" } },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Delete
|
|
314
|
+
await cmsMutate(getClient, {
|
|
315
|
+
schema_slug: "comments",
|
|
316
|
+
mutationType: "delete",
|
|
317
|
+
entryId: "xyz789",
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## `buildCmsQuery` / `buildCmsMutation`
|
|
324
|
+
|
|
325
|
+
Lower-level helpers that turn a declarative selection into GraphQL `DocumentNode` plus variables. Used internally by the hooks and server helpers; exported so you can drive your own Apollo calls.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { buildCmsQuery, buildCmsMutation } from "@asteroidcms/core-utils";
|
|
329
|
+
|
|
330
|
+
// Query
|
|
331
|
+
const { query, variables, isSingle } = buildCmsQuery({
|
|
332
|
+
schema_slug: "news",
|
|
333
|
+
limit: 10,
|
|
334
|
+
status: "PUBLISHED",
|
|
335
|
+
select: ["title", "slug"],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const { data } = await apolloClient.query({ query, variables });
|
|
339
|
+
const entries = isSingle ? data.entry : data.entries;
|
|
340
|
+
|
|
341
|
+
// Mutation
|
|
342
|
+
const { mutation, variables: mutVars } = buildCmsMutation({
|
|
343
|
+
schema_slug: "news",
|
|
344
|
+
mutationType: "create",
|
|
345
|
+
variables: { data: { title: "Hello" } },
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const { data: mutData } = await apolloClient.mutate({ mutation, variables: mutVars });
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
313
353
|
## `cmsImage` / `useCmsImage`
|
|
314
354
|
|
|
315
355
|
Build a canonical media URL for an asset id.
|
|
@@ -372,7 +412,7 @@ import { Info } from "lucide-react";
|
|
|
372
412
|
/>;
|
|
373
413
|
```
|
|
374
414
|
|
|
375
|
-
Additional props: `contentRef` (forwards the wrapper element, useful for
|
|
415
|
+
Additional props: `contentRef` (forwards the wrapper element, useful for scroll observers or ToC hooks), `onReady` (fires once enhancements have run), and `calloutIcons` (per-variant icon override for `<aside data-callout data-icon>` blocks).
|
|
376
416
|
|
|
377
417
|
Or use the parser directly (server-safe, no `highlight.js`):
|
|
378
418
|
|
|
@@ -388,56 +428,24 @@ const html = parseRichText(article.body, {
|
|
|
388
428
|
|
|
389
429
|
### Table of contents
|
|
390
430
|
|
|
391
|
-
Build a
|
|
431
|
+
Build a static ToC from HTML with `extractHeadingsFromHtml`, or extract headings from live DOM with `extractHeadingsFromElement`:
|
|
392
432
|
|
|
393
|
-
```
|
|
394
|
-
import {
|
|
395
|
-
import {
|
|
396
|
-
RichTextContent,
|
|
397
|
-
useTableOfContents,
|
|
398
|
-
} from "@asteroidcms/core-utils/client";
|
|
399
|
-
|
|
400
|
-
function Article({ slug, html }: { slug: string; html: string }) {
|
|
401
|
-
const contentRef = useRef<HTMLElement | null>(null);
|
|
402
|
-
const { items, activeId } = useTableOfContents(contentRef, {
|
|
403
|
-
levels: [2, 3],
|
|
404
|
-
contentKey: slug, // re-collect when content swaps
|
|
405
|
-
activationOffset: 96, // px from viewport top
|
|
406
|
-
});
|
|
433
|
+
```ts
|
|
434
|
+
import { extractHeadingsFromHtml } from "@asteroidcms/core-utils";
|
|
407
435
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
<RichTextContent
|
|
411
|
-
html={html}
|
|
412
|
-
as="article"
|
|
413
|
-
className="prose"
|
|
414
|
-
contentRef={contentRef}
|
|
415
|
-
/>
|
|
416
|
-
<nav>
|
|
417
|
-
{items.map((it) => (
|
|
418
|
-
<a
|
|
419
|
-
key={it.id}
|
|
420
|
-
href={`#${it.id}`}
|
|
421
|
-
className={it.id === activeId ? "font-semibold" : ""}
|
|
422
|
-
>
|
|
423
|
-
{it.text}
|
|
424
|
-
</a>
|
|
425
|
-
))}
|
|
426
|
-
</nav>
|
|
427
|
-
</div>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
436
|
+
const toc = extractHeadingsFromHtml(article.body, { levels: [2, 3] });
|
|
437
|
+
// → [{ id: "intro", text: "Intro", level: 2 }, ...]
|
|
430
438
|
```
|
|
431
439
|
|
|
432
|
-
For a static, server-side outline (RSC layouts, sitemaps, RSS), use `extractHeadingsFromHtml`:
|
|
433
|
-
|
|
434
440
|
```ts
|
|
435
|
-
import {
|
|
441
|
+
import { extractHeadingsFromElement } from "@asteroidcms/core-utils/client";
|
|
436
442
|
|
|
437
|
-
const toc =
|
|
443
|
+
const toc = extractHeadingsFromElement(contentRef.current, { levels: [2, 3] });
|
|
438
444
|
```
|
|
439
445
|
|
|
440
|
-
|
|
446
|
+
Pair with `RichTextContent`'s `contentRef` prop and a scroll listener to build live active-heading tracking.
|
|
447
|
+
|
|
448
|
+
See the [full rich-text docs](./docs/web-sdk-react/10-rich-text.md) for `classMap` variants, parser options, and code block features.
|
|
441
449
|
|
|
442
450
|
---
|
|
443
451
|
|
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
|
|
@@ -1727,97 +1735,6 @@ function extractHeadingsFromElement(root, options = {}) {
|
|
|
1727
1735
|
return out;
|
|
1728
1736
|
}
|
|
1729
1737
|
|
|
1730
|
-
// src/hooks/useTableOfContents.tsx
|
|
1731
|
-
function useTableOfContents(ref, options = {}) {
|
|
1732
|
-
const {
|
|
1733
|
-
levels,
|
|
1734
|
-
contentKey = null,
|
|
1735
|
-
scrollMarginTop = 24,
|
|
1736
|
-
activationOffset = 96
|
|
1737
|
-
} = options;
|
|
1738
|
-
const [items, setItems] = react.useState([]);
|
|
1739
|
-
const [activeId, setActiveId] = react.useState("");
|
|
1740
|
-
react.useEffect(() => {
|
|
1741
|
-
const root = ref.current;
|
|
1742
|
-
if (!root) {
|
|
1743
|
-
setItems([]);
|
|
1744
|
-
setActiveId("");
|
|
1745
|
-
return;
|
|
1746
|
-
}
|
|
1747
|
-
let raf = 0;
|
|
1748
|
-
const collect = () => {
|
|
1749
|
-
const next = extractHeadingsFromElement(root, {
|
|
1750
|
-
levels,
|
|
1751
|
-
scrollMarginTop
|
|
1752
|
-
});
|
|
1753
|
-
setItems(next);
|
|
1754
|
-
setActiveId(
|
|
1755
|
-
(prev) => next.some((h) => h.id === prev) ? prev : next[0]?.id ?? ""
|
|
1756
|
-
);
|
|
1757
|
-
};
|
|
1758
|
-
raf = requestAnimationFrame(collect);
|
|
1759
|
-
const mo = new MutationObserver(() => {
|
|
1760
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1761
|
-
raf = requestAnimationFrame(collect);
|
|
1762
|
-
});
|
|
1763
|
-
mo.observe(root, { childList: true, subtree: true, characterData: true });
|
|
1764
|
-
return () => {
|
|
1765
|
-
mo.disconnect();
|
|
1766
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1767
|
-
};
|
|
1768
|
-
}, [ref, contentKey, levels, scrollMarginTop]);
|
|
1769
|
-
react.useEffect(() => {
|
|
1770
|
-
if (items.length === 0) return;
|
|
1771
|
-
const targets = items.map((it) => document.getElementById(it.id)).filter((el) => el !== null);
|
|
1772
|
-
if (targets.length === 0) return;
|
|
1773
|
-
let raf = 0;
|
|
1774
|
-
const compute = () => {
|
|
1775
|
-
raf = 0;
|
|
1776
|
-
let activeIdx = 0;
|
|
1777
|
-
for (let i = 0; i < items.length; i++) {
|
|
1778
|
-
const el = document.getElementById(items[i].id);
|
|
1779
|
-
if (!el) continue;
|
|
1780
|
-
if (el.getBoundingClientRect().top - activationOffset <= 0) {
|
|
1781
|
-
activeIdx = i;
|
|
1782
|
-
} else {
|
|
1783
|
-
break;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
const scroller = document.scrollingElement || document.documentElement;
|
|
1787
|
-
const scrollY = window.scrollY;
|
|
1788
|
-
const viewportH = window.innerHeight;
|
|
1789
|
-
const atBottom = scrollY + viewportH >= scroller.scrollHeight - 2;
|
|
1790
|
-
if (atBottom) {
|
|
1791
|
-
for (let i = items.length - 1; i > activeIdx; i--) {
|
|
1792
|
-
const el = document.getElementById(items[i].id);
|
|
1793
|
-
if (el && el.getBoundingClientRect().top < viewportH) {
|
|
1794
|
-
activeIdx = i;
|
|
1795
|
-
break;
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
setActiveId(items[activeIdx].id);
|
|
1800
|
-
};
|
|
1801
|
-
const schedule = () => {
|
|
1802
|
-
if (raf) return;
|
|
1803
|
-
raf = requestAnimationFrame(compute);
|
|
1804
|
-
};
|
|
1805
|
-
const io = new IntersectionObserver(schedule, {
|
|
1806
|
-
rootMargin: `-${activationOffset}px 0px -${Math.max(0, window.innerHeight - activationOffset - 1)}px 0px`,
|
|
1807
|
-
threshold: 0
|
|
1808
|
-
});
|
|
1809
|
-
targets.forEach((t) => io.observe(t));
|
|
1810
|
-
window.addEventListener("resize", schedule, { passive: true });
|
|
1811
|
-
compute();
|
|
1812
|
-
return () => {
|
|
1813
|
-
io.disconnect();
|
|
1814
|
-
window.removeEventListener("resize", schedule);
|
|
1815
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1816
|
-
};
|
|
1817
|
-
}, [items, activationOffset]);
|
|
1818
|
-
return { items, activeId };
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1738
|
exports.AsteroidCMSProvider = AsteroidCMSProvider;
|
|
1822
1739
|
exports.RichTextContent = RichTextContent;
|
|
1823
1740
|
exports.extractHeadingsFromElement = extractHeadingsFromElement;
|
|
@@ -1827,6 +1744,5 @@ exports.useAsteroidCMSConfig = useAsteroidCMSConfig;
|
|
|
1827
1744
|
exports.useCmsContent = useCmsContent;
|
|
1828
1745
|
exports.useCmsImage = useCmsImage;
|
|
1829
1746
|
exports.useCmsMutate = useCmsMutate;
|
|
1830
|
-
exports.useTableOfContents = useTableOfContents;
|
|
1831
1747
|
//# sourceMappingURL=client.cjs.map
|
|
1832
1748
|
//# sourceMappingURL=client.cjs.map
|