@asteroidcms/core-utils 0.1.5 → 0.1.6
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 +0 -92
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +4 -42
- package/dist/client.d.ts +4 -42
- package/dist/client.js +1 -92
- 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
|
@@ -1727,97 +1727,6 @@ function extractHeadingsFromElement(root, options = {}) {
|
|
|
1727
1727
|
return out;
|
|
1728
1728
|
}
|
|
1729
1729
|
|
|
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
1730
|
exports.AsteroidCMSProvider = AsteroidCMSProvider;
|
|
1822
1731
|
exports.RichTextContent = RichTextContent;
|
|
1823
1732
|
exports.extractHeadingsFromElement = extractHeadingsFromElement;
|
|
@@ -1827,6 +1736,5 @@ exports.useAsteroidCMSConfig = useAsteroidCMSConfig;
|
|
|
1827
1736
|
exports.useCmsContent = useCmsContent;
|
|
1828
1737
|
exports.useCmsImage = useCmsImage;
|
|
1829
1738
|
exports.useCmsMutate = useCmsMutate;
|
|
1830
|
-
exports.useTableOfContents = useTableOfContents;
|
|
1831
1739
|
//# sourceMappingURL=client.cjs.map
|
|
1832
1740
|
//# sourceMappingURL=client.cjs.map
|