@asteroidcms/core-utils 0.1.4 → 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 +83 -70
- package/dist/client.cjs +161 -105
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +31 -51
- package/dist/client.d.ts +31 -51
- package/dist/client.js +164 -107
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +99 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -7
- package/dist/index.d.ts +41 -7
- package/dist/index.js +98 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/client.d.cts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import { PropsWithChildren, RefObject } from 'react';
|
|
2
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
4
3
|
import * as _apollo_client from '@apollo/client';
|
|
5
4
|
import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient } from '@apollo/client';
|
|
6
5
|
import { useMutation } from '@apollo/client/react';
|
|
@@ -205,7 +204,7 @@ declare function useCmsImage(): (id?: string) => string;
|
|
|
205
204
|
*
|
|
206
205
|
* Idempotent: parseRichText(parseRichText(x, opts), opts) === parseRichText(x, opts).
|
|
207
206
|
*/
|
|
208
|
-
type RichTextClassKey = "p" | "br" | "hr" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol" | "li" | "blockquote" | "pre" | "code" | "inlineCode" | "a" | "strong" | "em" | "u" | "s" | "kbd" | "table" | "tableWrapper" | "thead" | "tbody" | "tr" | "th" | "td" | "figure" | "figcaption" | "img" | "span" | "callout" | "calloutTitle";
|
|
207
|
+
type RichTextClassKey = "p" | "br" | "hr" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol" | "li" | "blockquote" | "pre" | "code" | "inlineCode" | "a" | "strong" | "em" | "u" | "s" | "kbd" | "table" | "tableWrapper" | "thead" | "tbody" | "tr" | "th" | "td" | "figure" | "figcaption" | "img" | "span" | "callout" | "calloutTitle" | "collapsible" | "collapsibleTitle";
|
|
209
208
|
type RichTextClassMap = Partial<Record<RichTextClassKey, string>> & {
|
|
210
209
|
/** Variant overrides keyed as `${matchKey}:${variant}`, e.g. "callout:warning". */
|
|
211
210
|
variants?: Record<string, string>;
|
|
@@ -225,18 +224,37 @@ interface RichTextContentProps {
|
|
|
225
224
|
*/
|
|
226
225
|
onReady?: (root: HTMLElement) => void;
|
|
227
226
|
/**
|
|
228
|
-
* Optional ref to the wrapper element. Useful for
|
|
229
|
-
*
|
|
227
|
+
* Optional ref to the wrapper element. Useful for consumers that need a
|
|
228
|
+
* stable reference to the rendered tree (e.g. scroll observers, ToC hooks).
|
|
230
229
|
*/
|
|
231
230
|
contentRef?: React.MutableRefObject<HTMLElement | null>;
|
|
231
|
+
/**
|
|
232
|
+
* Per-variant icon override for callouts that opt into icons via
|
|
233
|
+
* `data-icon`. Provide a React element for any variant key — these
|
|
234
|
+
* elements are rendered into the chip via a portal, so they live in
|
|
235
|
+
* React land (refs, event handlers, theme context all work). Variants
|
|
236
|
+
* you don't provide fall back to the built-in SVG glyph.
|
|
237
|
+
*
|
|
238
|
+
* Common variants: `"info" | "warning" | "success" | "danger" | "default"`.
|
|
239
|
+
* Custom variant names work too — anything you set via
|
|
240
|
+
* `data-variant` on an `<aside data-callout>`.
|
|
241
|
+
*
|
|
242
|
+
* Example:
|
|
243
|
+
* ```tsx
|
|
244
|
+
* import { Info, AlertTriangle } from "lucide-react";
|
|
245
|
+
*
|
|
246
|
+
* <RichTextContent
|
|
247
|
+
* html={article.doc}
|
|
248
|
+
* calloutIcons={{
|
|
249
|
+
* info: <Info size={14} strokeWidth={2.4} />,
|
|
250
|
+
* warning: <AlertTriangle size={14} strokeWidth={2.4} />,
|
|
251
|
+
* }}
|
|
252
|
+
* />
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
calloutIcons?: Partial<Record<string, ReactNode>>;
|
|
232
256
|
}
|
|
233
|
-
declare function RichTextContent({ html, classMap, as, className, onReady, contentRef, }: RichTextContentProps):
|
|
234
|
-
ref: react.MutableRefObject<HTMLElement | null>;
|
|
235
|
-
className: string | undefined;
|
|
236
|
-
dangerouslySetInnerHTML: {
|
|
237
|
-
__html: string;
|
|
238
|
-
};
|
|
239
|
-
}, HTMLElement>;
|
|
257
|
+
declare function RichTextContent({ html, classMap, as, className, onReady, contentRef, calloutIcons, }: RichTextContentProps): react_jsx_runtime.JSX.Element;
|
|
240
258
|
|
|
241
259
|
/**
|
|
242
260
|
* Heading extraction helpers used to build tables of contents (ToC) from
|
|
@@ -275,42 +293,4 @@ declare function extractHeadingsFromElement(root: HTMLElement, options?: Extract
|
|
|
275
293
|
scrollMarginTop?: number;
|
|
276
294
|
}): ExtractedHeading[];
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
/** Heading levels to include. Defaults to `[2, 3]`. */
|
|
280
|
-
levels?: ReadonlyArray<HeadingLevel>;
|
|
281
|
-
/**
|
|
282
|
-
* Re-collect headings whenever this value changes. Pass the article slug
|
|
283
|
-
* (or any stable identifier) so swapping content rebuilds the ToC.
|
|
284
|
-
*/
|
|
285
|
-
contentKey?: string | number | null;
|
|
286
|
-
/** Pixels to subtract from heading scroll-into-view target. Default 24. */
|
|
287
|
-
scrollMarginTop?: number;
|
|
288
|
-
/**
|
|
289
|
-
* Distance from the top of the viewport (in px) at which a heading becomes
|
|
290
|
-
* "active". A heading is considered active once its top edge has scrolled
|
|
291
|
-
* past this line. Default `96` — works well with a sticky header that
|
|
292
|
-
* stands ~60–80px tall.
|
|
293
|
-
*/
|
|
294
|
-
activationOffset?: number;
|
|
295
|
-
}
|
|
296
|
-
interface UseTableOfContentsResult {
|
|
297
|
-
items: ExtractedHeading[];
|
|
298
|
-
activeId: string;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Build a table of contents from a rendered element and track which
|
|
302
|
-
* heading is currently in view via IntersectionObserver. Resilient to
|
|
303
|
-
* content swaps when `contentKey` is provided.
|
|
304
|
-
*
|
|
305
|
-
* Usage:
|
|
306
|
-
* ```tsx
|
|
307
|
-
* const ref = useRef<HTMLDivElement>(null);
|
|
308
|
-
* const { items, activeId } = useTableOfContents(ref, {
|
|
309
|
-
* contentKey: article.slug,
|
|
310
|
-
* levels: [2, 3],
|
|
311
|
-
* });
|
|
312
|
-
* ```
|
|
313
|
-
*/
|
|
314
|
-
declare function useTableOfContents(ref: RefObject<HTMLElement | null>, options?: UseTableOfContentsOptions): UseTableOfContentsResult;
|
|
315
|
-
|
|
316
|
-
export { AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, type UseTableOfContentsOptions, type UseTableOfContentsResult, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate, useTableOfContents };
|
|
296
|
+
export { AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import { PropsWithChildren, RefObject } from 'react';
|
|
2
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
4
3
|
import * as _apollo_client from '@apollo/client';
|
|
5
4
|
import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient } from '@apollo/client';
|
|
6
5
|
import { useMutation } from '@apollo/client/react';
|
|
@@ -205,7 +204,7 @@ declare function useCmsImage(): (id?: string) => string;
|
|
|
205
204
|
*
|
|
206
205
|
* Idempotent: parseRichText(parseRichText(x, opts), opts) === parseRichText(x, opts).
|
|
207
206
|
*/
|
|
208
|
-
type RichTextClassKey = "p" | "br" | "hr" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol" | "li" | "blockquote" | "pre" | "code" | "inlineCode" | "a" | "strong" | "em" | "u" | "s" | "kbd" | "table" | "tableWrapper" | "thead" | "tbody" | "tr" | "th" | "td" | "figure" | "figcaption" | "img" | "span" | "callout" | "calloutTitle";
|
|
207
|
+
type RichTextClassKey = "p" | "br" | "hr" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol" | "li" | "blockquote" | "pre" | "code" | "inlineCode" | "a" | "strong" | "em" | "u" | "s" | "kbd" | "table" | "tableWrapper" | "thead" | "tbody" | "tr" | "th" | "td" | "figure" | "figcaption" | "img" | "span" | "callout" | "calloutTitle" | "collapsible" | "collapsibleTitle";
|
|
209
208
|
type RichTextClassMap = Partial<Record<RichTextClassKey, string>> & {
|
|
210
209
|
/** Variant overrides keyed as `${matchKey}:${variant}`, e.g. "callout:warning". */
|
|
211
210
|
variants?: Record<string, string>;
|
|
@@ -225,18 +224,37 @@ interface RichTextContentProps {
|
|
|
225
224
|
*/
|
|
226
225
|
onReady?: (root: HTMLElement) => void;
|
|
227
226
|
/**
|
|
228
|
-
* Optional ref to the wrapper element. Useful for
|
|
229
|
-
*
|
|
227
|
+
* Optional ref to the wrapper element. Useful for consumers that need a
|
|
228
|
+
* stable reference to the rendered tree (e.g. scroll observers, ToC hooks).
|
|
230
229
|
*/
|
|
231
230
|
contentRef?: React.MutableRefObject<HTMLElement | null>;
|
|
231
|
+
/**
|
|
232
|
+
* Per-variant icon override for callouts that opt into icons via
|
|
233
|
+
* `data-icon`. Provide a React element for any variant key — these
|
|
234
|
+
* elements are rendered into the chip via a portal, so they live in
|
|
235
|
+
* React land (refs, event handlers, theme context all work). Variants
|
|
236
|
+
* you don't provide fall back to the built-in SVG glyph.
|
|
237
|
+
*
|
|
238
|
+
* Common variants: `"info" | "warning" | "success" | "danger" | "default"`.
|
|
239
|
+
* Custom variant names work too — anything you set via
|
|
240
|
+
* `data-variant` on an `<aside data-callout>`.
|
|
241
|
+
*
|
|
242
|
+
* Example:
|
|
243
|
+
* ```tsx
|
|
244
|
+
* import { Info, AlertTriangle } from "lucide-react";
|
|
245
|
+
*
|
|
246
|
+
* <RichTextContent
|
|
247
|
+
* html={article.doc}
|
|
248
|
+
* calloutIcons={{
|
|
249
|
+
* info: <Info size={14} strokeWidth={2.4} />,
|
|
250
|
+
* warning: <AlertTriangle size={14} strokeWidth={2.4} />,
|
|
251
|
+
* }}
|
|
252
|
+
* />
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
calloutIcons?: Partial<Record<string, ReactNode>>;
|
|
232
256
|
}
|
|
233
|
-
declare function RichTextContent({ html, classMap, as, className, onReady, contentRef, }: RichTextContentProps):
|
|
234
|
-
ref: react.MutableRefObject<HTMLElement | null>;
|
|
235
|
-
className: string | undefined;
|
|
236
|
-
dangerouslySetInnerHTML: {
|
|
237
|
-
__html: string;
|
|
238
|
-
};
|
|
239
|
-
}, HTMLElement>;
|
|
257
|
+
declare function RichTextContent({ html, classMap, as, className, onReady, contentRef, calloutIcons, }: RichTextContentProps): react_jsx_runtime.JSX.Element;
|
|
240
258
|
|
|
241
259
|
/**
|
|
242
260
|
* Heading extraction helpers used to build tables of contents (ToC) from
|
|
@@ -275,42 +293,4 @@ declare function extractHeadingsFromElement(root: HTMLElement, options?: Extract
|
|
|
275
293
|
scrollMarginTop?: number;
|
|
276
294
|
}): ExtractedHeading[];
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
/** Heading levels to include. Defaults to `[2, 3]`. */
|
|
280
|
-
levels?: ReadonlyArray<HeadingLevel>;
|
|
281
|
-
/**
|
|
282
|
-
* Re-collect headings whenever this value changes. Pass the article slug
|
|
283
|
-
* (or any stable identifier) so swapping content rebuilds the ToC.
|
|
284
|
-
*/
|
|
285
|
-
contentKey?: string | number | null;
|
|
286
|
-
/** Pixels to subtract from heading scroll-into-view target. Default 24. */
|
|
287
|
-
scrollMarginTop?: number;
|
|
288
|
-
/**
|
|
289
|
-
* Distance from the top of the viewport (in px) at which a heading becomes
|
|
290
|
-
* "active". A heading is considered active once its top edge has scrolled
|
|
291
|
-
* past this line. Default `96` — works well with a sticky header that
|
|
292
|
-
* stands ~60–80px tall.
|
|
293
|
-
*/
|
|
294
|
-
activationOffset?: number;
|
|
295
|
-
}
|
|
296
|
-
interface UseTableOfContentsResult {
|
|
297
|
-
items: ExtractedHeading[];
|
|
298
|
-
activeId: string;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Build a table of contents from a rendered element and track which
|
|
302
|
-
* heading is currently in view via IntersectionObserver. Resilient to
|
|
303
|
-
* content swaps when `contentKey` is provided.
|
|
304
|
-
*
|
|
305
|
-
* Usage:
|
|
306
|
-
* ```tsx
|
|
307
|
-
* const ref = useRef<HTMLDivElement>(null);
|
|
308
|
-
* const { items, activeId } = useTableOfContents(ref, {
|
|
309
|
-
* contentKey: article.slug,
|
|
310
|
-
* levels: [2, 3],
|
|
311
|
-
* });
|
|
312
|
-
* ```
|
|
313
|
-
*/
|
|
314
|
-
declare function useTableOfContents(ref: RefObject<HTMLElement | null>, options?: UseTableOfContentsOptions): UseTableOfContentsResult;
|
|
315
|
-
|
|
316
|
-
export { AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, type UseTableOfContentsOptions, type UseTableOfContentsResult, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate, useTableOfContents };
|
|
296
|
+
export { AsteroidCMSProvider, type AsteroidCMSProviderProps, type ExtractHeadingsOptions, type ExtractedHeading, type HeadingLevel, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
|
package/dist/client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, useContext, useMemo, useRef, useEffect,
|
|
2
|
+
import { createContext, useContext, useMemo, useRef, useState, useEffect, Fragment, createElement } from 'react';
|
|
3
3
|
import { ApolloProvider, useQuery, useMutation } from '@apollo/client/react';
|
|
4
4
|
import { gql, HttpLink, ApolloClient, InMemoryCache, ApolloLink, CombinedGraphQLErrors, CombinedProtocolErrors } from '@apollo/client';
|
|
5
5
|
import { SetContextLink } from '@apollo/client/link/context';
|
|
6
6
|
import { ErrorLink } from '@apollo/client/link/error';
|
|
7
|
-
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
|
+
import { createPortal } from 'react-dom';
|
|
8
9
|
import hljs from 'highlight.js/lib/common';
|
|
9
|
-
import 'highlight.js/styles/tokyo-night-dark.css';
|
|
10
10
|
|
|
11
11
|
var AsteroidCMSContext = createContext(null);
|
|
12
12
|
function useAsteroidCMSConfig() {
|
|
@@ -365,7 +365,9 @@ var DEFAULT_ALLOWLIST = [
|
|
|
365
365
|
"section",
|
|
366
366
|
"article",
|
|
367
367
|
"div",
|
|
368
|
-
"span"
|
|
368
|
+
"span",
|
|
369
|
+
"details",
|
|
370
|
+
"summary"
|
|
369
371
|
];
|
|
370
372
|
var ALLOWED_ATTRS = {
|
|
371
373
|
a: ["href", "title", "target", "rel"],
|
|
@@ -375,7 +377,8 @@ var ALLOWED_ATTRS = {
|
|
|
375
377
|
col: ["span", "width"],
|
|
376
378
|
colgroup: ["span"],
|
|
377
379
|
table: ["border", "cellpadding", "cellspacing"],
|
|
378
|
-
span: ["style"]
|
|
380
|
+
span: ["style"],
|
|
381
|
+
details: ["open"]
|
|
379
382
|
};
|
|
380
383
|
var ALLOWED_STYLE_PROPS = {
|
|
381
384
|
span: ["font-size"]
|
|
@@ -409,7 +412,8 @@ var GLOBAL_ALLOWED_ATTRS = [
|
|
|
409
412
|
"data-title",
|
|
410
413
|
"data-callout-title",
|
|
411
414
|
"data-language",
|
|
412
|
-
"data-filename"
|
|
415
|
+
"data-filename",
|
|
416
|
+
"data-icon"
|
|
413
417
|
];
|
|
414
418
|
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src"]);
|
|
415
419
|
function parseRichText(html, options = {}) {
|
|
@@ -823,6 +827,8 @@ function classKeyForTag(tag, attrs, openStack) {
|
|
|
823
827
|
if (tag === "p" && "data-callout-title" in attrs) return "calloutTitle";
|
|
824
828
|
if (tag === "code" && openStack[openStack.length - 1] !== "pre")
|
|
825
829
|
return "inlineCode";
|
|
830
|
+
if (tag === "details") return "collapsible";
|
|
831
|
+
if (tag === "summary") return "collapsibleTitle";
|
|
826
832
|
const known = [
|
|
827
833
|
"p",
|
|
828
834
|
"br",
|
|
@@ -989,8 +995,6 @@ function sanitizeAndStyle(html, options) {
|
|
|
989
995
|
}
|
|
990
996
|
return out.join("");
|
|
991
997
|
}
|
|
992
|
-
|
|
993
|
-
// src/components/RichTextContent.tsx
|
|
994
998
|
var DEFAULT_CLASS_MAP = {
|
|
995
999
|
variants: {
|
|
996
1000
|
"figure:pullquote": "relative my-8",
|
|
@@ -1025,6 +1029,46 @@ function findQuoteBody(bq) {
|
|
|
1025
1029
|
if (lastIdx < 0) return { first: null, last: null };
|
|
1026
1030
|
return { first: children[0], last: children[lastIdx] };
|
|
1027
1031
|
}
|
|
1032
|
+
var CALLOUT_ICON_SVG = {
|
|
1033
|
+
info: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 16v-5"/><path d="M12 8h.01"/></svg>`,
|
|
1034
|
+
warning: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
|
|
1035
|
+
success: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>`,
|
|
1036
|
+
danger: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
|
|
1037
|
+
default: `<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M12 2.5l1.6 5.4a4 4 0 0 0 2.5 2.5L21.5 12l-5.4 1.6a4 4 0 0 0-2.5 2.5L12 21.5l-1.6-5.4a4 4 0 0 0-2.5-2.5L2.5 12l5.4-1.6a4 4 0 0 0 2.5-2.5z"/></svg>`
|
|
1038
|
+
};
|
|
1039
|
+
function calloutVariantOf(el) {
|
|
1040
|
+
return el.getAttribute("data-variant") ?? "default";
|
|
1041
|
+
}
|
|
1042
|
+
function isIconEnabled(el) {
|
|
1043
|
+
const v = el.getAttribute("data-icon");
|
|
1044
|
+
if (v === null) return false;
|
|
1045
|
+
return v !== "false" && v !== "0";
|
|
1046
|
+
}
|
|
1047
|
+
function enhanceCallouts(root) {
|
|
1048
|
+
const callouts = root.querySelectorAll("aside[data-callout]");
|
|
1049
|
+
callouts.forEach((el) => {
|
|
1050
|
+
if (el.dataset.rtCalloutEnhanced === "1") return;
|
|
1051
|
+
el.dataset.rtCalloutEnhanced = "1";
|
|
1052
|
+
if (!isIconEnabled(el)) return;
|
|
1053
|
+
if (el.querySelector(":scope > .rt-callout-icon")) return;
|
|
1054
|
+
const variant = calloutVariantOf(el);
|
|
1055
|
+
const icon = document.createElement("span");
|
|
1056
|
+
icon.className = "rt-callout-icon";
|
|
1057
|
+
icon.dataset.variant = variant;
|
|
1058
|
+
icon.setAttribute("aria-hidden", "true");
|
|
1059
|
+
el.prepend(icon);
|
|
1060
|
+
});
|
|
1061
|
+
const chips = [];
|
|
1062
|
+
root.querySelectorAll(
|
|
1063
|
+
"aside[data-callout] > .rt-callout-icon"
|
|
1064
|
+
).forEach((chip) => {
|
|
1065
|
+
chips.push({
|
|
1066
|
+
el: chip,
|
|
1067
|
+
variant: chip.dataset.variant || (chip.parentElement?.getAttribute("data-variant") ?? "default")
|
|
1068
|
+
});
|
|
1069
|
+
});
|
|
1070
|
+
return chips;
|
|
1071
|
+
}
|
|
1028
1072
|
function enhanceBlockquotes(root) {
|
|
1029
1073
|
const quotes = root.querySelectorAll("blockquote");
|
|
1030
1074
|
quotes.forEach((bq) => {
|
|
@@ -1254,6 +1298,28 @@ function enhanceCodeBlocks(root) {
|
|
|
1254
1298
|
highlightCodeBlock(pre);
|
|
1255
1299
|
});
|
|
1256
1300
|
}
|
|
1301
|
+
var HLJS_THEME = `
|
|
1302
|
+
pre code.hljs { display: block; overflow-x: auto; padding: 1em; }
|
|
1303
|
+
code.hljs { padding: 3px 5px; }
|
|
1304
|
+
.hljs-meta, .hljs-comment { color: #565f89; }
|
|
1305
|
+
.hljs-tag, .hljs-doctag, .hljs-selector-id, .hljs-selector-class,
|
|
1306
|
+
.hljs-regexp, .hljs-template-tag, .hljs-selector-pseudo,
|
|
1307
|
+
.hljs-selector-attr, .hljs-variable.language_, .hljs-deletion { color: #f7768e; }
|
|
1308
|
+
.hljs-variable, .hljs-template-variable, .hljs-number, .hljs-literal,
|
|
1309
|
+
.hljs-type, .hljs-params, .hljs-link { color: #ff9e64; }
|
|
1310
|
+
.hljs-built_in, .hljs-attribute { color: #e0af68; }
|
|
1311
|
+
.hljs-selector-tag { color: #73daca; }
|
|
1312
|
+
.hljs-keyword, .hljs-title.function_, .hljs-title, .hljs-title.class_,
|
|
1313
|
+
.hljs-title.class_.inherited__, .hljs-subst, .hljs-property { color: #7dcfff; }
|
|
1314
|
+
.hljs-quote, .hljs-string, .hljs-symbol, .hljs-bullet,
|
|
1315
|
+
.hljs-addition { color: #9ece6a; }
|
|
1316
|
+
.hljs-code, .hljs-formula, .hljs-section { color: #7aa2f7; }
|
|
1317
|
+
.hljs-name, .hljs-operator, .hljs-char.escape_, .hljs-attr { color: #bb9af7; }
|
|
1318
|
+
.hljs-punctuation { color: #c0caf5; }
|
|
1319
|
+
.hljs { background: #1a1b26; color: #9aa5ce; }
|
|
1320
|
+
.hljs-emphasis { font-style: italic; }
|
|
1321
|
+
.hljs-strong { font-weight: bold; }
|
|
1322
|
+
`;
|
|
1257
1323
|
var CODEBLOCK_STYLE = `
|
|
1258
1324
|
.rt-codeblock { position: relative; }
|
|
1259
1325
|
.rt-codeblock[data-filename]:not([data-filename=""]) { padding-top: 2rem; }
|
|
@@ -1305,6 +1371,64 @@ var CODEBLOCK_STYLE = `
|
|
|
1305
1371
|
}
|
|
1306
1372
|
.rt-quote-open { margin-right: 0.15em; }
|
|
1307
1373
|
.rt-quote-close { margin-left: 0.15em; }
|
|
1374
|
+
|
|
1375
|
+
/* Callout chip ----------------------------------------------------------- */
|
|
1376
|
+
/* Layout: when a callout opts into an icon, use a 2-column grid so the chip
|
|
1377
|
+
sits left of the title + body. Consumers can override via classMap. */
|
|
1378
|
+
aside[data-callout][data-icon]:not([data-icon="false"]):not([data-icon="0"]) {
|
|
1379
|
+
display: grid;
|
|
1380
|
+
grid-template-columns: auto 1fr;
|
|
1381
|
+
column-gap: 0.85rem;
|
|
1382
|
+
align-items: start;
|
|
1383
|
+
}
|
|
1384
|
+
aside[data-callout][data-icon]:not([data-icon="false"]):not([data-icon="0"]) > .rt-callout-icon {
|
|
1385
|
+
grid-row: 1 / span 99;
|
|
1386
|
+
margin-top: 0.05rem;
|
|
1387
|
+
}
|
|
1388
|
+
.rt-callout-icon {
|
|
1389
|
+
display: inline-flex;
|
|
1390
|
+
align-items: center;
|
|
1391
|
+
justify-content: center;
|
|
1392
|
+
width: 1.6rem;
|
|
1393
|
+
height: 1.6rem;
|
|
1394
|
+
border-radius: 0.4rem;
|
|
1395
|
+
color: #fff;
|
|
1396
|
+
background: #475569;
|
|
1397
|
+
flex-shrink: 0;
|
|
1398
|
+
}
|
|
1399
|
+
.rt-callout-icon[data-variant="info"] { background: #2563eb; }
|
|
1400
|
+
.rt-callout-icon[data-variant="warning"] { background: #d97706; }
|
|
1401
|
+
.rt-callout-icon[data-variant="success"] { background: #16a34a; }
|
|
1402
|
+
.rt-callout-icon[data-variant="danger"] { background: #dc2626; }
|
|
1403
|
+
.rt-callout-icon[data-variant="default"] { background: #475569; }
|
|
1404
|
+
|
|
1405
|
+
/* Collapsible (FAQ accordion) ------------------------------------------ */
|
|
1406
|
+
/* Native <details>/<summary> with a rotating chevron. Only structural
|
|
1407
|
+
styling lives here \u2014 colors, padding, and typography belong to
|
|
1408
|
+
consumers via the \`collapsible\` and \`collapsibleTitle\` classMap keys. */
|
|
1409
|
+
details[data-collapsible] > summary {
|
|
1410
|
+
cursor: pointer;
|
|
1411
|
+
list-style: none;
|
|
1412
|
+
display: flex;
|
|
1413
|
+
align-items: center;
|
|
1414
|
+
justify-content: space-between;
|
|
1415
|
+
gap: 0.75rem;
|
|
1416
|
+
}
|
|
1417
|
+
details[data-collapsible] > summary::-webkit-details-marker { display: none; }
|
|
1418
|
+
details[data-collapsible] > summary::after {
|
|
1419
|
+
content: "";
|
|
1420
|
+
width: 0.55rem;
|
|
1421
|
+
height: 0.55rem;
|
|
1422
|
+
border-right: 1.5px solid currentColor;
|
|
1423
|
+
border-bottom: 1.5px solid currentColor;
|
|
1424
|
+
transform: rotate(-45deg) translate(-0.1rem, 0.05rem);
|
|
1425
|
+
transition: transform 0.18s ease;
|
|
1426
|
+
opacity: 0.55;
|
|
1427
|
+
flex-shrink: 0;
|
|
1428
|
+
}
|
|
1429
|
+
details[data-collapsible][open] > summary::after {
|
|
1430
|
+
transform: rotate(45deg) translate(-0.05rem, -0.05rem);
|
|
1431
|
+
}
|
|
1308
1432
|
/* highlight.js theme handles .hljs-* color classes; we only override the
|
|
1309
1433
|
default .hljs background so the per-block chrome (dark bg, terminal,
|
|
1310
1434
|
diff red/green rows) wins. */
|
|
@@ -1446,17 +1570,29 @@ function ensureCodeBlockStyles() {
|
|
|
1446
1570
|
}
|
|
1447
1571
|
const tag = document.createElement("style");
|
|
1448
1572
|
tag.id = "rt-codeblock-style";
|
|
1449
|
-
tag.textContent = CODEBLOCK_STYLE;
|
|
1573
|
+
tag.textContent = HLJS_THEME + "\n" + CODEBLOCK_STYLE;
|
|
1450
1574
|
document.head.appendChild(tag);
|
|
1451
1575
|
styleInjected = true;
|
|
1452
1576
|
}
|
|
1577
|
+
function calloutChipsEqual(a, b) {
|
|
1578
|
+
if (a.length !== b.length) return false;
|
|
1579
|
+
for (let i = 0; i < a.length; i++) {
|
|
1580
|
+
if (a[i].el !== b[i].el || a[i].variant !== b[i].variant) return false;
|
|
1581
|
+
}
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
function BuiltinCalloutIcon({ variant }) {
|
|
1585
|
+
const html = CALLOUT_ICON_SVG[variant] ?? CALLOUT_ICON_SVG.default;
|
|
1586
|
+
return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
1587
|
+
}
|
|
1453
1588
|
function RichTextContent({
|
|
1454
1589
|
html,
|
|
1455
1590
|
classMap,
|
|
1456
1591
|
as = "div",
|
|
1457
1592
|
className,
|
|
1458
1593
|
onReady,
|
|
1459
|
-
contentRef
|
|
1594
|
+
contentRef,
|
|
1595
|
+
calloutIcons
|
|
1460
1596
|
}) {
|
|
1461
1597
|
const merged = useMemo(
|
|
1462
1598
|
() => mergeClassMap(DEFAULT_CLASS_MAP, classMap),
|
|
@@ -1467,6 +1603,7 @@ function RichTextContent({
|
|
|
1467
1603
|
[html, merged]
|
|
1468
1604
|
);
|
|
1469
1605
|
const ref = useRef(null);
|
|
1606
|
+
const [chips, setChips] = useState([]);
|
|
1470
1607
|
useEffect(() => {
|
|
1471
1608
|
ensureCodeBlockStyles();
|
|
1472
1609
|
const root = ref.current;
|
|
@@ -1476,6 +1613,8 @@ function RichTextContent({
|
|
|
1476
1613
|
mo.disconnect();
|
|
1477
1614
|
enhanceCodeBlocks(root);
|
|
1478
1615
|
enhanceBlockquotes(root);
|
|
1616
|
+
const nextChips = enhanceCallouts(root);
|
|
1617
|
+
setChips((prev) => calloutChipsEqual(prev, nextChips) ? prev : nextChips);
|
|
1479
1618
|
onReady?.(root);
|
|
1480
1619
|
mo.observe(root, { childList: true, subtree: true });
|
|
1481
1620
|
};
|
|
@@ -1493,11 +1632,20 @@ function RichTextContent({
|
|
|
1493
1632
|
if (raf) cancelAnimationFrame(raf);
|
|
1494
1633
|
};
|
|
1495
1634
|
}, [safe, onReady, contentRef]);
|
|
1496
|
-
return
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1635
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1636
|
+
createElement(as, {
|
|
1637
|
+
ref,
|
|
1638
|
+
className,
|
|
1639
|
+
dangerouslySetInnerHTML: { __html: safe }
|
|
1640
|
+
}),
|
|
1641
|
+
chips.map(
|
|
1642
|
+
(chip, i) => createPortal(
|
|
1643
|
+
calloutIcons && chip.variant in calloutIcons ? calloutIcons[chip.variant] : /* @__PURE__ */ jsx(BuiltinCalloutIcon, { variant: chip.variant }),
|
|
1644
|
+
chip.el,
|
|
1645
|
+
`${chip.variant}:${i}`
|
|
1646
|
+
)
|
|
1647
|
+
)
|
|
1648
|
+
] });
|
|
1501
1649
|
}
|
|
1502
1650
|
|
|
1503
1651
|
// src/utils/extractHeadings.ts
|
|
@@ -1573,97 +1721,6 @@ function extractHeadingsFromElement(root, options = {}) {
|
|
|
1573
1721
|
return out;
|
|
1574
1722
|
}
|
|
1575
1723
|
|
|
1576
|
-
|
|
1577
|
-
function useTableOfContents(ref, options = {}) {
|
|
1578
|
-
const {
|
|
1579
|
-
levels,
|
|
1580
|
-
contentKey = null,
|
|
1581
|
-
scrollMarginTop = 24,
|
|
1582
|
-
activationOffset = 96
|
|
1583
|
-
} = options;
|
|
1584
|
-
const [items, setItems] = useState([]);
|
|
1585
|
-
const [activeId, setActiveId] = useState("");
|
|
1586
|
-
useEffect(() => {
|
|
1587
|
-
const root = ref.current;
|
|
1588
|
-
if (!root) {
|
|
1589
|
-
setItems([]);
|
|
1590
|
-
setActiveId("");
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
let raf = 0;
|
|
1594
|
-
const collect = () => {
|
|
1595
|
-
const next = extractHeadingsFromElement(root, {
|
|
1596
|
-
levels,
|
|
1597
|
-
scrollMarginTop
|
|
1598
|
-
});
|
|
1599
|
-
setItems(next);
|
|
1600
|
-
setActiveId(
|
|
1601
|
-
(prev) => next.some((h) => h.id === prev) ? prev : next[0]?.id ?? ""
|
|
1602
|
-
);
|
|
1603
|
-
};
|
|
1604
|
-
raf = requestAnimationFrame(collect);
|
|
1605
|
-
const mo = new MutationObserver(() => {
|
|
1606
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1607
|
-
raf = requestAnimationFrame(collect);
|
|
1608
|
-
});
|
|
1609
|
-
mo.observe(root, { childList: true, subtree: true, characterData: true });
|
|
1610
|
-
return () => {
|
|
1611
|
-
mo.disconnect();
|
|
1612
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1613
|
-
};
|
|
1614
|
-
}, [ref, contentKey, levels, scrollMarginTop]);
|
|
1615
|
-
useEffect(() => {
|
|
1616
|
-
if (items.length === 0) return;
|
|
1617
|
-
const targets = items.map((it) => document.getElementById(it.id)).filter((el) => el !== null);
|
|
1618
|
-
if (targets.length === 0) return;
|
|
1619
|
-
let raf = 0;
|
|
1620
|
-
const compute = () => {
|
|
1621
|
-
raf = 0;
|
|
1622
|
-
let activeIdx = 0;
|
|
1623
|
-
for (let i = 0; i < items.length; i++) {
|
|
1624
|
-
const el = document.getElementById(items[i].id);
|
|
1625
|
-
if (!el) continue;
|
|
1626
|
-
if (el.getBoundingClientRect().top - activationOffset <= 0) {
|
|
1627
|
-
activeIdx = i;
|
|
1628
|
-
} else {
|
|
1629
|
-
break;
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
const scroller = document.scrollingElement || document.documentElement;
|
|
1633
|
-
const scrollY = window.scrollY;
|
|
1634
|
-
const viewportH = window.innerHeight;
|
|
1635
|
-
const atBottom = scrollY + viewportH >= scroller.scrollHeight - 2;
|
|
1636
|
-
if (atBottom) {
|
|
1637
|
-
for (let i = items.length - 1; i > activeIdx; i--) {
|
|
1638
|
-
const el = document.getElementById(items[i].id);
|
|
1639
|
-
if (el && el.getBoundingClientRect().top < viewportH) {
|
|
1640
|
-
activeIdx = i;
|
|
1641
|
-
break;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
setActiveId(items[activeIdx].id);
|
|
1646
|
-
};
|
|
1647
|
-
const schedule = () => {
|
|
1648
|
-
if (raf) return;
|
|
1649
|
-
raf = requestAnimationFrame(compute);
|
|
1650
|
-
};
|
|
1651
|
-
const io = new IntersectionObserver(schedule, {
|
|
1652
|
-
rootMargin: `-${activationOffset}px 0px -${Math.max(0, window.innerHeight - activationOffset - 1)}px 0px`,
|
|
1653
|
-
threshold: 0
|
|
1654
|
-
});
|
|
1655
|
-
targets.forEach((t) => io.observe(t));
|
|
1656
|
-
window.addEventListener("resize", schedule, { passive: true });
|
|
1657
|
-
compute();
|
|
1658
|
-
return () => {
|
|
1659
|
-
io.disconnect();
|
|
1660
|
-
window.removeEventListener("resize", schedule);
|
|
1661
|
-
if (raf) cancelAnimationFrame(raf);
|
|
1662
|
-
};
|
|
1663
|
-
}, [items, activationOffset]);
|
|
1664
|
-
return { items, activeId };
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
export { AsteroidCMSProvider, RichTextContent, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate, useTableOfContents };
|
|
1724
|
+
export { AsteroidCMSProvider, RichTextContent, extractHeadingsFromElement, extractHeadingsFromHtml, slugify, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
|
|
1668
1725
|
//# sourceMappingURL=client.js.map
|
|
1669
1726
|
//# sourceMappingURL=client.js.map
|