@ai-react-markdown/core 1.4.2 → 1.4.3
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 +110 -0
- package/dist/index.cjs +438 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +201 -2
- package/dist/index.d.ts +201 -2
- package/dist/index.js +426 -93
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { JSX, ComponentType, PropsWithChildren, CSSProperties, FC } from 'react';
|
|
3
3
|
import { Element, ElementContent } from 'hast';
|
|
4
|
+
import { defaultSchema } from 'rehype-sanitize';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Public types for the local Markdown wrapper. Ported 1:1 from react-markdown
|
|
@@ -17,6 +18,24 @@ interface ExtraProps {
|
|
|
17
18
|
type Components = {
|
|
18
19
|
[Key in keyof JSX.IntrinsicElements]?: ComponentType<JSX.IntrinsicElements[Key] & ExtraProps> | keyof JSX.IntrinsicElements;
|
|
19
20
|
};
|
|
21
|
+
/** Transform every URL on every element attribute. Return null/empty to strip. */
|
|
22
|
+
type UrlTransform = (url: string, key: string, node: Readonly<Element>) => string | null | undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default URL transform — same allowlist as react-markdown / GitHub.
|
|
26
|
+
*
|
|
27
|
+
* @module components/markdown/urlTransform
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Make a URL safe.
|
|
32
|
+
*
|
|
33
|
+
* Allows `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp` protocols, plus
|
|
34
|
+
* URLs relative to the current protocol (e.g. `/foo`). Other protocols are
|
|
35
|
+
* stripped to the empty string. Mirrors GitHub's behaviour and matches
|
|
36
|
+
* `micromark-util-sanitize-uri` minus the URL-encoding pass.
|
|
37
|
+
*/
|
|
38
|
+
declare const defaultUrlTransform: UrlTransform;
|
|
20
39
|
|
|
21
40
|
/**
|
|
22
41
|
* Core type definitions, enums, and default configuration for ai-react-markdown.
|
|
@@ -722,6 +741,85 @@ interface AIMarkdownMetadataProviderProps<TMetadata extends AIMarkdownMetadata =
|
|
|
722
741
|
*/
|
|
723
742
|
type AIMDContentPreprocessor = (content: string) => string;
|
|
724
743
|
|
|
744
|
+
/**
|
|
745
|
+
* Builds a `rehype-sanitize` schema by handing the caller a deep clone of the
|
|
746
|
+
* library default ({@link sanitizeSchema}) to mutate or replace.
|
|
747
|
+
*
|
|
748
|
+
* The mutate-and-return pattern matches the ergonomics of Next.js's
|
|
749
|
+
* `webpack(config)` and Express middleware: a callback receives a draft,
|
|
750
|
+
* either modifies it in place (returning nothing) or returns a fresh object
|
|
751
|
+
* to replace it. The library guarantees the draft is a deep clone, so direct
|
|
752
|
+
* mutation never leaks into the shared default singleton.
|
|
753
|
+
*
|
|
754
|
+
* @module components/extendSanitizeSchema
|
|
755
|
+
*/
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* The full `rehype-sanitize` schema type. Re-exported as the canonical
|
|
759
|
+
* library-internal alias so other modules don't each redeclare
|
|
760
|
+
* `typeof defaultSchema`.
|
|
761
|
+
*
|
|
762
|
+
* The shape is owned by `rehype-sanitize`; consumers should treat it as
|
|
763
|
+
* tracking that upstream type — it may evolve across rehype-sanitize major
|
|
764
|
+
* versions.
|
|
765
|
+
*/
|
|
766
|
+
type SanitizeSchema = typeof defaultSchema;
|
|
767
|
+
/**
|
|
768
|
+
* Build a sanitize schema by mutating (or replacing) a deep clone of the
|
|
769
|
+
* library default.
|
|
770
|
+
*
|
|
771
|
+
* Designed to be called ONCE at module scope so the returned object has a
|
|
772
|
+
* stable identity across renders — passing it directly into
|
|
773
|
+
* `<AIMarkdown sanitizeSchema={…}>` keeps the block-memo cache warm.
|
|
774
|
+
*
|
|
775
|
+
* @example Append a custom URL protocol via mutation:
|
|
776
|
+
* ```ts
|
|
777
|
+
* const SCHEMA = extendSanitizeSchema((s) => {
|
|
778
|
+
* s.protocols!.href!.push('myapp');
|
|
779
|
+
* s.protocols!.src!.push('myapp');
|
|
780
|
+
* });
|
|
781
|
+
*
|
|
782
|
+
* function App() {
|
|
783
|
+
* return <AIMarkdown content={…} sanitizeSchema={SCHEMA} />;
|
|
784
|
+
* }
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* @example Return-style replacement for wider edits:
|
|
788
|
+
* ```ts
|
|
789
|
+
* const SCHEMA = extendSanitizeSchema((s) => ({
|
|
790
|
+
* ...s,
|
|
791
|
+
* tagNames: [...(s.tagNames ?? []), 'my-widget'],
|
|
792
|
+
* }));
|
|
793
|
+
* ```
|
|
794
|
+
*
|
|
795
|
+
* @remarks Allowing a protocol on `protocols.href` lets the URL through the
|
|
796
|
+
* SECOND sanitize gate. The FIRST gate (`urlTransform`) must permit the
|
|
797
|
+
* same protocol independently — see the `urlTransform` prop on
|
|
798
|
+
* `<AIMarkdown>` and the exported {@link defaultUrlTransform} for
|
|
799
|
+
* composition. Keep the two protocol lists in sync.
|
|
800
|
+
*
|
|
801
|
+
* ### Footguns
|
|
802
|
+
*
|
|
803
|
+
* - **Reassigning the local parameter** (`(s) => { s = { …new schema… }; }`)
|
|
804
|
+
* does NOT replace the draft — JS only rebinds the local variable. Either
|
|
805
|
+
* mutate the original draft or `return` the new object explicitly.
|
|
806
|
+
* - **Returning `null`** is treated the same as returning nothing (the
|
|
807
|
+
* modified draft is used). The TypeScript signature does not permit
|
|
808
|
+
* `null`, but JS callers or `as`-casted code paths could silently hit
|
|
809
|
+
* this. Prefer `return` with no value, or an explicit `return draft;`.
|
|
810
|
+
* - **Throwing inside the modifier** propagates to the call site
|
|
811
|
+
* uncaught — there is no try/catch. Callers usually invoke this once at
|
|
812
|
+
* module load, where a thrown error surfaces as a startup-time crash and
|
|
813
|
+
* is the correct failure mode.
|
|
814
|
+
*
|
|
815
|
+
* @param modifier - Receives a deep clone of the library default. Mutate it
|
|
816
|
+
* freely; either return the (possibly different) result, or return
|
|
817
|
+
* nothing to use the mutated draft. Returning `undefined` (and, by
|
|
818
|
+
* convention, `null`) is treated the same as a mutate-only call.
|
|
819
|
+
* @returns A new `Schema` object — never the library default singleton.
|
|
820
|
+
*/
|
|
821
|
+
declare function extendSanitizeSchema(modifier: (draft: SanitizeSchema) => SanitizeSchema | void): SanitizeSchema;
|
|
822
|
+
|
|
725
823
|
/**
|
|
726
824
|
* Hook for referential stability of deep-equal values.
|
|
727
825
|
*
|
|
@@ -755,6 +853,23 @@ type AIMDContentPreprocessor = (content: string) => string;
|
|
|
755
853
|
*/
|
|
756
854
|
declare function useStableValue<T>(value: T): T;
|
|
757
855
|
|
|
856
|
+
/**
|
|
857
|
+
* Builds the `rehype-sanitize` schema used by {@link MarkdownContent}.
|
|
858
|
+
*
|
|
859
|
+
* Extracted into its own module so the merge logic can be unit-tested in
|
|
860
|
+
* isolation without pulling in React or the full markdown pipeline.
|
|
861
|
+
*
|
|
862
|
+
* @module components/sanitizeSchema
|
|
863
|
+
*/
|
|
864
|
+
|
|
865
|
+
type Schema = typeof defaultSchema;
|
|
866
|
+
/**
|
|
867
|
+
* The full sanitize schema used by the markdown renderer: extends
|
|
868
|
+
* `defaultSchema` to allow `<mark>`, the KaTeX math class names, and the
|
|
869
|
+
* three custom hast tags emitted by cross-chunk coordination handlers.
|
|
870
|
+
*/
|
|
871
|
+
declare const sanitizeSchema: Schema;
|
|
872
|
+
|
|
758
873
|
/**
|
|
759
874
|
* Cross-chunk shared state. Holds per-chunk contributions (refs, defs,
|
|
760
875
|
* linkDefs) keyed by Symbol identity allocated via useId reactId, with
|
|
@@ -957,12 +1072,96 @@ interface AIMarkdownProps<TConfig extends AIMarkdownRenderConfig = AIMarkdownRen
|
|
|
957
1072
|
* characters like `:`, `/`, or spaces.
|
|
958
1073
|
*/
|
|
959
1074
|
documentId?: string;
|
|
1075
|
+
/**
|
|
1076
|
+
* Override the function that decides which URL protocols are allowed
|
|
1077
|
+
* through the FIRST sanitization gate. The default allowlist mirrors
|
|
1078
|
+
* `react-markdown` / GitHub: `http`, `https`, `irc`, `ircs`, `mailto`,
|
|
1079
|
+
* `xmpp`. Anything else is rewritten to `''`.
|
|
1080
|
+
*
|
|
1081
|
+
* **Recommended pattern**: compose with the exported
|
|
1082
|
+
* {@link defaultUrlTransform} so the built-in XSS protections survive,
|
|
1083
|
+
* and define the result at module scope so its identity is stable across
|
|
1084
|
+
* renders:
|
|
1085
|
+
*
|
|
1086
|
+
* ```ts
|
|
1087
|
+
* import AIMarkdown, { defaultUrlTransform } from '@ai-react-markdown/core';
|
|
1088
|
+
*
|
|
1089
|
+
* const ALLOWED = /^(myapp|tel):/i;
|
|
1090
|
+
* const URL_TRANSFORM = (url, key, node) =>
|
|
1091
|
+
* ALLOWED.test(url) ? url : defaultUrlTransform(url, key, node);
|
|
1092
|
+
*
|
|
1093
|
+
* function App() {
|
|
1094
|
+
* return <AIMarkdown urlTransform={URL_TRANSFORM} ... />;
|
|
1095
|
+
* }
|
|
1096
|
+
* ```
|
|
1097
|
+
*
|
|
1098
|
+
* **Regex-escaping**: scheme names per RFC 3986 may contain `+`, `-`, and
|
|
1099
|
+
* `.` (e.g. `web+app`, `coap+tcp`). All three are regex metacharacters,
|
|
1100
|
+
* so write `/^web\+app:/i` rather than `/^web+app:/i`. The latter would
|
|
1101
|
+
* match URLs starting with `we`, `wee`, `weee`, … and silently broaden
|
|
1102
|
+
* the allowlist.
|
|
1103
|
+
*
|
|
1104
|
+
* **Reference stability matters.** The block-memo cache treats this prop
|
|
1105
|
+
* as a dependency. Defining the function inline (`urlTransform={(url) =>
|
|
1106
|
+
* …}`) creates a new closure on every parent render, discards the cache
|
|
1107
|
+
* for the entire markdown document on each render, and effectively
|
|
1108
|
+
* disables Phase 5 memoization. In development the library will
|
|
1109
|
+
* `console.warn` if it detects this pattern.
|
|
1110
|
+
*
|
|
1111
|
+
* Allowing a protocol here is necessary but **not sufficient** to render
|
|
1112
|
+
* a link — the second gate (`rehype-sanitize`) also enforces its own
|
|
1113
|
+
* protocol allowlist. See the {@link sanitizeSchema} prop and the
|
|
1114
|
+
* matching {@link extendSanitizeSchema} helper for the second gate.
|
|
1115
|
+
*
|
|
1116
|
+
* **API stability**: the `UrlTransform` type tracks the upstream
|
|
1117
|
+
* `react-markdown` shape and may change with its major versions.
|
|
1118
|
+
*/
|
|
1119
|
+
urlTransform?: UrlTransform | null;
|
|
1120
|
+
/**
|
|
1121
|
+
* Override the `rehype-sanitize` schema applied to the rendered output.
|
|
1122
|
+
* The library default ({@link sanitizeSchema}) extends `rehype-sanitize`'s
|
|
1123
|
+
* own `defaultSchema` with the `<mark>` tag, KaTeX class names, and the
|
|
1124
|
+
* cross-chunk coordination tags (`cross-chunk-link`, `cross-chunk-image`,
|
|
1125
|
+
* `footnote-sup`).
|
|
1126
|
+
*
|
|
1127
|
+
* **Recommended pattern**: build the schema with {@link extendSanitizeSchema}
|
|
1128
|
+
* (mutate-and-return form) so those library additions stay intact, and
|
|
1129
|
+
* define the result at module scope:
|
|
1130
|
+
*
|
|
1131
|
+
* ```ts
|
|
1132
|
+
* import AIMarkdown, { extendSanitizeSchema } from '@ai-react-markdown/core';
|
|
1133
|
+
*
|
|
1134
|
+
* const SCHEMA = extendSanitizeSchema((s) => {
|
|
1135
|
+
* s.protocols.href.push('myapp');
|
|
1136
|
+
* s.protocols.src.push('myapp');
|
|
1137
|
+
* });
|
|
1138
|
+
*
|
|
1139
|
+
* function App() {
|
|
1140
|
+
* return <AIMarkdown sanitizeSchema={SCHEMA} ... />;
|
|
1141
|
+
* }
|
|
1142
|
+
* ```
|
|
1143
|
+
*
|
|
1144
|
+
* **Footgun**: hand-rolling a schema (e.g. spreading from
|
|
1145
|
+
* `rehype-sanitize`'s `defaultSchema` directly) silently drops the
|
|
1146
|
+
* cross-chunk tag allowlist — coordinated multi-chunk rendering will then
|
|
1147
|
+
* lose its placeholders. Prefer the helper unless you have a specific
|
|
1148
|
+
* reason to opt out.
|
|
1149
|
+
*
|
|
1150
|
+
* **Reference stability matters.** Inline `<AIMarkdown sanitizeSchema={{
|
|
1151
|
+
* ...sanitizeSchema, protocols: {...} }}>` is mitigated by an internal
|
|
1152
|
+
* `useStableValue` deep-equal pass, but the safer pattern is still
|
|
1153
|
+
* module-scope. Development builds will `console.warn` on identity flips.
|
|
1154
|
+
*
|
|
1155
|
+
* **API stability**: the `SanitizeSchema` type tracks the upstream
|
|
1156
|
+
* `rehype-sanitize` shape and may change with its major versions.
|
|
1157
|
+
*/
|
|
1158
|
+
sanitizeSchema?: SanitizeSchema;
|
|
960
1159
|
}
|
|
961
1160
|
/**
|
|
962
1161
|
* Root component that preprocesses markdown content and renders it through
|
|
963
1162
|
* a configurable remark/rehype pipeline wrapped in typography and style layers.
|
|
964
1163
|
*/
|
|
965
|
-
declare const AIMarkdownComponent: <TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig, TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata>({ streaming, content, fontSize, contentPreprocessors, customComponents, defaultConfig, config, metadata, Typography, ExtraStyles, variant, colorScheme, documentId, }: AIMarkdownProps<TConfig, TRenderData>) => react_jsx_runtime.JSX.Element;
|
|
1164
|
+
declare const AIMarkdownComponent: <TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig, TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata>({ streaming, content, fontSize, contentPreprocessors, customComponents, defaultConfig, config, metadata, Typography, ExtraStyles, variant, colorScheme, documentId, urlTransform, sanitizeSchema, }: AIMarkdownProps<TConfig, TRenderData>) => react_jsx_runtime.JSX.Element;
|
|
966
1165
|
declare const _default: typeof AIMarkdownComponent;
|
|
967
1166
|
|
|
968
|
-
export { type AIMDContentPreprocessor, type AIMarkdownColorScheme, type AIMarkdownCustomComponents, AIMarkdownDocuments, type AIMarkdownDocumentsProps, type AIMarkdownExtraStylesComponent, type AIMarkdownExtraStylesProps, type AIMarkdownMetadata, type AIMarkdownProps, type AIMarkdownRenderConfig, AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax, type AIMarkdownRenderState, type AIMarkdownTypographyComponent, type AIMarkdownTypographyProps, type AIMarkdownVariant, type ChunkData, type FootnoteDef, type LinkDef, type PartialDeep, type RefKind, type RefRecord, type Registry, _default as default, defaultAIMarkdownRenderConfig, useAIMarkdownMetadata, useAIMarkdownRenderState, useDocumentRegistry, useStableValue };
|
|
1167
|
+
export { type AIMDContentPreprocessor, type AIMarkdownColorScheme, type AIMarkdownCustomComponents, AIMarkdownDocuments, type AIMarkdownDocumentsProps, type AIMarkdownExtraStylesComponent, type AIMarkdownExtraStylesProps, type AIMarkdownMetadata, type AIMarkdownProps, type AIMarkdownRenderConfig, AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax, type AIMarkdownRenderState, type AIMarkdownTypographyComponent, type AIMarkdownTypographyProps, type AIMarkdownVariant, type ChunkData, type FootnoteDef, type LinkDef, type PartialDeep, type RefKind, type RefRecord, type Registry, type SanitizeSchema, type UrlTransform, _default as default, defaultAIMarkdownRenderConfig, defaultUrlTransform, extendSanitizeSchema, sanitizeSchema, useAIMarkdownMetadata, useAIMarkdownRenderState, useDocumentRegistry, useStableValue };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { JSX, ComponentType, PropsWithChildren, CSSProperties, FC } from 'react';
|
|
3
3
|
import { Element, ElementContent } from 'hast';
|
|
4
|
+
import { defaultSchema } from 'rehype-sanitize';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Public types for the local Markdown wrapper. Ported 1:1 from react-markdown
|
|
@@ -17,6 +18,24 @@ interface ExtraProps {
|
|
|
17
18
|
type Components = {
|
|
18
19
|
[Key in keyof JSX.IntrinsicElements]?: ComponentType<JSX.IntrinsicElements[Key] & ExtraProps> | keyof JSX.IntrinsicElements;
|
|
19
20
|
};
|
|
21
|
+
/** Transform every URL on every element attribute. Return null/empty to strip. */
|
|
22
|
+
type UrlTransform = (url: string, key: string, node: Readonly<Element>) => string | null | undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default URL transform — same allowlist as react-markdown / GitHub.
|
|
26
|
+
*
|
|
27
|
+
* @module components/markdown/urlTransform
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Make a URL safe.
|
|
32
|
+
*
|
|
33
|
+
* Allows `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp` protocols, plus
|
|
34
|
+
* URLs relative to the current protocol (e.g. `/foo`). Other protocols are
|
|
35
|
+
* stripped to the empty string. Mirrors GitHub's behaviour and matches
|
|
36
|
+
* `micromark-util-sanitize-uri` minus the URL-encoding pass.
|
|
37
|
+
*/
|
|
38
|
+
declare const defaultUrlTransform: UrlTransform;
|
|
20
39
|
|
|
21
40
|
/**
|
|
22
41
|
* Core type definitions, enums, and default configuration for ai-react-markdown.
|
|
@@ -722,6 +741,85 @@ interface AIMarkdownMetadataProviderProps<TMetadata extends AIMarkdownMetadata =
|
|
|
722
741
|
*/
|
|
723
742
|
type AIMDContentPreprocessor = (content: string) => string;
|
|
724
743
|
|
|
744
|
+
/**
|
|
745
|
+
* Builds a `rehype-sanitize` schema by handing the caller a deep clone of the
|
|
746
|
+
* library default ({@link sanitizeSchema}) to mutate or replace.
|
|
747
|
+
*
|
|
748
|
+
* The mutate-and-return pattern matches the ergonomics of Next.js's
|
|
749
|
+
* `webpack(config)` and Express middleware: a callback receives a draft,
|
|
750
|
+
* either modifies it in place (returning nothing) or returns a fresh object
|
|
751
|
+
* to replace it. The library guarantees the draft is a deep clone, so direct
|
|
752
|
+
* mutation never leaks into the shared default singleton.
|
|
753
|
+
*
|
|
754
|
+
* @module components/extendSanitizeSchema
|
|
755
|
+
*/
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* The full `rehype-sanitize` schema type. Re-exported as the canonical
|
|
759
|
+
* library-internal alias so other modules don't each redeclare
|
|
760
|
+
* `typeof defaultSchema`.
|
|
761
|
+
*
|
|
762
|
+
* The shape is owned by `rehype-sanitize`; consumers should treat it as
|
|
763
|
+
* tracking that upstream type — it may evolve across rehype-sanitize major
|
|
764
|
+
* versions.
|
|
765
|
+
*/
|
|
766
|
+
type SanitizeSchema = typeof defaultSchema;
|
|
767
|
+
/**
|
|
768
|
+
* Build a sanitize schema by mutating (or replacing) a deep clone of the
|
|
769
|
+
* library default.
|
|
770
|
+
*
|
|
771
|
+
* Designed to be called ONCE at module scope so the returned object has a
|
|
772
|
+
* stable identity across renders — passing it directly into
|
|
773
|
+
* `<AIMarkdown sanitizeSchema={…}>` keeps the block-memo cache warm.
|
|
774
|
+
*
|
|
775
|
+
* @example Append a custom URL protocol via mutation:
|
|
776
|
+
* ```ts
|
|
777
|
+
* const SCHEMA = extendSanitizeSchema((s) => {
|
|
778
|
+
* s.protocols!.href!.push('myapp');
|
|
779
|
+
* s.protocols!.src!.push('myapp');
|
|
780
|
+
* });
|
|
781
|
+
*
|
|
782
|
+
* function App() {
|
|
783
|
+
* return <AIMarkdown content={…} sanitizeSchema={SCHEMA} />;
|
|
784
|
+
* }
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* @example Return-style replacement for wider edits:
|
|
788
|
+
* ```ts
|
|
789
|
+
* const SCHEMA = extendSanitizeSchema((s) => ({
|
|
790
|
+
* ...s,
|
|
791
|
+
* tagNames: [...(s.tagNames ?? []), 'my-widget'],
|
|
792
|
+
* }));
|
|
793
|
+
* ```
|
|
794
|
+
*
|
|
795
|
+
* @remarks Allowing a protocol on `protocols.href` lets the URL through the
|
|
796
|
+
* SECOND sanitize gate. The FIRST gate (`urlTransform`) must permit the
|
|
797
|
+
* same protocol independently — see the `urlTransform` prop on
|
|
798
|
+
* `<AIMarkdown>` and the exported {@link defaultUrlTransform} for
|
|
799
|
+
* composition. Keep the two protocol lists in sync.
|
|
800
|
+
*
|
|
801
|
+
* ### Footguns
|
|
802
|
+
*
|
|
803
|
+
* - **Reassigning the local parameter** (`(s) => { s = { …new schema… }; }`)
|
|
804
|
+
* does NOT replace the draft — JS only rebinds the local variable. Either
|
|
805
|
+
* mutate the original draft or `return` the new object explicitly.
|
|
806
|
+
* - **Returning `null`** is treated the same as returning nothing (the
|
|
807
|
+
* modified draft is used). The TypeScript signature does not permit
|
|
808
|
+
* `null`, but JS callers or `as`-casted code paths could silently hit
|
|
809
|
+
* this. Prefer `return` with no value, or an explicit `return draft;`.
|
|
810
|
+
* - **Throwing inside the modifier** propagates to the call site
|
|
811
|
+
* uncaught — there is no try/catch. Callers usually invoke this once at
|
|
812
|
+
* module load, where a thrown error surfaces as a startup-time crash and
|
|
813
|
+
* is the correct failure mode.
|
|
814
|
+
*
|
|
815
|
+
* @param modifier - Receives a deep clone of the library default. Mutate it
|
|
816
|
+
* freely; either return the (possibly different) result, or return
|
|
817
|
+
* nothing to use the mutated draft. Returning `undefined` (and, by
|
|
818
|
+
* convention, `null`) is treated the same as a mutate-only call.
|
|
819
|
+
* @returns A new `Schema` object — never the library default singleton.
|
|
820
|
+
*/
|
|
821
|
+
declare function extendSanitizeSchema(modifier: (draft: SanitizeSchema) => SanitizeSchema | void): SanitizeSchema;
|
|
822
|
+
|
|
725
823
|
/**
|
|
726
824
|
* Hook for referential stability of deep-equal values.
|
|
727
825
|
*
|
|
@@ -755,6 +853,23 @@ type AIMDContentPreprocessor = (content: string) => string;
|
|
|
755
853
|
*/
|
|
756
854
|
declare function useStableValue<T>(value: T): T;
|
|
757
855
|
|
|
856
|
+
/**
|
|
857
|
+
* Builds the `rehype-sanitize` schema used by {@link MarkdownContent}.
|
|
858
|
+
*
|
|
859
|
+
* Extracted into its own module so the merge logic can be unit-tested in
|
|
860
|
+
* isolation without pulling in React or the full markdown pipeline.
|
|
861
|
+
*
|
|
862
|
+
* @module components/sanitizeSchema
|
|
863
|
+
*/
|
|
864
|
+
|
|
865
|
+
type Schema = typeof defaultSchema;
|
|
866
|
+
/**
|
|
867
|
+
* The full sanitize schema used by the markdown renderer: extends
|
|
868
|
+
* `defaultSchema` to allow `<mark>`, the KaTeX math class names, and the
|
|
869
|
+
* three custom hast tags emitted by cross-chunk coordination handlers.
|
|
870
|
+
*/
|
|
871
|
+
declare const sanitizeSchema: Schema;
|
|
872
|
+
|
|
758
873
|
/**
|
|
759
874
|
* Cross-chunk shared state. Holds per-chunk contributions (refs, defs,
|
|
760
875
|
* linkDefs) keyed by Symbol identity allocated via useId reactId, with
|
|
@@ -957,12 +1072,96 @@ interface AIMarkdownProps<TConfig extends AIMarkdownRenderConfig = AIMarkdownRen
|
|
|
957
1072
|
* characters like `:`, `/`, or spaces.
|
|
958
1073
|
*/
|
|
959
1074
|
documentId?: string;
|
|
1075
|
+
/**
|
|
1076
|
+
* Override the function that decides which URL protocols are allowed
|
|
1077
|
+
* through the FIRST sanitization gate. The default allowlist mirrors
|
|
1078
|
+
* `react-markdown` / GitHub: `http`, `https`, `irc`, `ircs`, `mailto`,
|
|
1079
|
+
* `xmpp`. Anything else is rewritten to `''`.
|
|
1080
|
+
*
|
|
1081
|
+
* **Recommended pattern**: compose with the exported
|
|
1082
|
+
* {@link defaultUrlTransform} so the built-in XSS protections survive,
|
|
1083
|
+
* and define the result at module scope so its identity is stable across
|
|
1084
|
+
* renders:
|
|
1085
|
+
*
|
|
1086
|
+
* ```ts
|
|
1087
|
+
* import AIMarkdown, { defaultUrlTransform } from '@ai-react-markdown/core';
|
|
1088
|
+
*
|
|
1089
|
+
* const ALLOWED = /^(myapp|tel):/i;
|
|
1090
|
+
* const URL_TRANSFORM = (url, key, node) =>
|
|
1091
|
+
* ALLOWED.test(url) ? url : defaultUrlTransform(url, key, node);
|
|
1092
|
+
*
|
|
1093
|
+
* function App() {
|
|
1094
|
+
* return <AIMarkdown urlTransform={URL_TRANSFORM} ... />;
|
|
1095
|
+
* }
|
|
1096
|
+
* ```
|
|
1097
|
+
*
|
|
1098
|
+
* **Regex-escaping**: scheme names per RFC 3986 may contain `+`, `-`, and
|
|
1099
|
+
* `.` (e.g. `web+app`, `coap+tcp`). All three are regex metacharacters,
|
|
1100
|
+
* so write `/^web\+app:/i` rather than `/^web+app:/i`. The latter would
|
|
1101
|
+
* match URLs starting with `we`, `wee`, `weee`, … and silently broaden
|
|
1102
|
+
* the allowlist.
|
|
1103
|
+
*
|
|
1104
|
+
* **Reference stability matters.** The block-memo cache treats this prop
|
|
1105
|
+
* as a dependency. Defining the function inline (`urlTransform={(url) =>
|
|
1106
|
+
* …}`) creates a new closure on every parent render, discards the cache
|
|
1107
|
+
* for the entire markdown document on each render, and effectively
|
|
1108
|
+
* disables Phase 5 memoization. In development the library will
|
|
1109
|
+
* `console.warn` if it detects this pattern.
|
|
1110
|
+
*
|
|
1111
|
+
* Allowing a protocol here is necessary but **not sufficient** to render
|
|
1112
|
+
* a link — the second gate (`rehype-sanitize`) also enforces its own
|
|
1113
|
+
* protocol allowlist. See the {@link sanitizeSchema} prop and the
|
|
1114
|
+
* matching {@link extendSanitizeSchema} helper for the second gate.
|
|
1115
|
+
*
|
|
1116
|
+
* **API stability**: the `UrlTransform` type tracks the upstream
|
|
1117
|
+
* `react-markdown` shape and may change with its major versions.
|
|
1118
|
+
*/
|
|
1119
|
+
urlTransform?: UrlTransform | null;
|
|
1120
|
+
/**
|
|
1121
|
+
* Override the `rehype-sanitize` schema applied to the rendered output.
|
|
1122
|
+
* The library default ({@link sanitizeSchema}) extends `rehype-sanitize`'s
|
|
1123
|
+
* own `defaultSchema` with the `<mark>` tag, KaTeX class names, and the
|
|
1124
|
+
* cross-chunk coordination tags (`cross-chunk-link`, `cross-chunk-image`,
|
|
1125
|
+
* `footnote-sup`).
|
|
1126
|
+
*
|
|
1127
|
+
* **Recommended pattern**: build the schema with {@link extendSanitizeSchema}
|
|
1128
|
+
* (mutate-and-return form) so those library additions stay intact, and
|
|
1129
|
+
* define the result at module scope:
|
|
1130
|
+
*
|
|
1131
|
+
* ```ts
|
|
1132
|
+
* import AIMarkdown, { extendSanitizeSchema } from '@ai-react-markdown/core';
|
|
1133
|
+
*
|
|
1134
|
+
* const SCHEMA = extendSanitizeSchema((s) => {
|
|
1135
|
+
* s.protocols.href.push('myapp');
|
|
1136
|
+
* s.protocols.src.push('myapp');
|
|
1137
|
+
* });
|
|
1138
|
+
*
|
|
1139
|
+
* function App() {
|
|
1140
|
+
* return <AIMarkdown sanitizeSchema={SCHEMA} ... />;
|
|
1141
|
+
* }
|
|
1142
|
+
* ```
|
|
1143
|
+
*
|
|
1144
|
+
* **Footgun**: hand-rolling a schema (e.g. spreading from
|
|
1145
|
+
* `rehype-sanitize`'s `defaultSchema` directly) silently drops the
|
|
1146
|
+
* cross-chunk tag allowlist — coordinated multi-chunk rendering will then
|
|
1147
|
+
* lose its placeholders. Prefer the helper unless you have a specific
|
|
1148
|
+
* reason to opt out.
|
|
1149
|
+
*
|
|
1150
|
+
* **Reference stability matters.** Inline `<AIMarkdown sanitizeSchema={{
|
|
1151
|
+
* ...sanitizeSchema, protocols: {...} }}>` is mitigated by an internal
|
|
1152
|
+
* `useStableValue` deep-equal pass, but the safer pattern is still
|
|
1153
|
+
* module-scope. Development builds will `console.warn` on identity flips.
|
|
1154
|
+
*
|
|
1155
|
+
* **API stability**: the `SanitizeSchema` type tracks the upstream
|
|
1156
|
+
* `rehype-sanitize` shape and may change with its major versions.
|
|
1157
|
+
*/
|
|
1158
|
+
sanitizeSchema?: SanitizeSchema;
|
|
960
1159
|
}
|
|
961
1160
|
/**
|
|
962
1161
|
* Root component that preprocesses markdown content and renders it through
|
|
963
1162
|
* a configurable remark/rehype pipeline wrapped in typography and style layers.
|
|
964
1163
|
*/
|
|
965
|
-
declare const AIMarkdownComponent: <TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig, TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata>({ streaming, content, fontSize, contentPreprocessors, customComponents, defaultConfig, config, metadata, Typography, ExtraStyles, variant, colorScheme, documentId, }: AIMarkdownProps<TConfig, TRenderData>) => react_jsx_runtime.JSX.Element;
|
|
1164
|
+
declare const AIMarkdownComponent: <TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig, TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata>({ streaming, content, fontSize, contentPreprocessors, customComponents, defaultConfig, config, metadata, Typography, ExtraStyles, variant, colorScheme, documentId, urlTransform, sanitizeSchema, }: AIMarkdownProps<TConfig, TRenderData>) => react_jsx_runtime.JSX.Element;
|
|
966
1165
|
declare const _default: typeof AIMarkdownComponent;
|
|
967
1166
|
|
|
968
|
-
export { type AIMDContentPreprocessor, type AIMarkdownColorScheme, type AIMarkdownCustomComponents, AIMarkdownDocuments, type AIMarkdownDocumentsProps, type AIMarkdownExtraStylesComponent, type AIMarkdownExtraStylesProps, type AIMarkdownMetadata, type AIMarkdownProps, type AIMarkdownRenderConfig, AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax, type AIMarkdownRenderState, type AIMarkdownTypographyComponent, type AIMarkdownTypographyProps, type AIMarkdownVariant, type ChunkData, type FootnoteDef, type LinkDef, type PartialDeep, type RefKind, type RefRecord, type Registry, _default as default, defaultAIMarkdownRenderConfig, useAIMarkdownMetadata, useAIMarkdownRenderState, useDocumentRegistry, useStableValue };
|
|
1167
|
+
export { type AIMDContentPreprocessor, type AIMarkdownColorScheme, type AIMarkdownCustomComponents, AIMarkdownDocuments, type AIMarkdownDocumentsProps, type AIMarkdownExtraStylesComponent, type AIMarkdownExtraStylesProps, type AIMarkdownMetadata, type AIMarkdownProps, type AIMarkdownRenderConfig, AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax, type AIMarkdownRenderState, type AIMarkdownTypographyComponent, type AIMarkdownTypographyProps, type AIMarkdownVariant, type ChunkData, type FootnoteDef, type LinkDef, type PartialDeep, type RefKind, type RefRecord, type Registry, type SanitizeSchema, type UrlTransform, _default as default, defaultAIMarkdownRenderConfig, defaultUrlTransform, extendSanitizeSchema, sanitizeSchema, useAIMarkdownMetadata, useAIMarkdownRenderState, useDocumentRegistry, useStableValue };
|