@ai-react-markdown/core 1.4.0 → 1.4.2

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 CHANGED
@@ -95,7 +95,7 @@ function StreamingChat({ content, isStreaming }: { content: string; isStreaming:
95
95
  | `customComponents` | `AIMarkdownCustomComponents` | `undefined` | `react-markdown` component overrides for specific HTML elements. |
96
96
  | `Typography` | `AIMarkdownTypographyComponent` | `DefaultTypography` | Typography wrapper component. |
97
97
  | `ExtraStyles` | `AIMarkdownExtraStylesComponent` | `undefined` | Optional extra style wrapper rendered between typography and content. |
98
- | `documentId` | `string` | auto via `useId()` | Stable id for the _logical markdown document_ this `<AIMarkdown>` is rendering. Used as the id namespace for clobberable attributes (`id`, hash hrefs) so two documents on the same page do not cross-link (footnote `[^1]` in message A won't scroll to `[^1]` in message B). When one document is split into chunks rendered by multiple `<AIMarkdown>` instances, pass the SAME `documentId` to every chunk so prefixes align. The value is passed through `encodeURIComponent` before being injected into HTML attributes, so any string is safe (React's `useId()` output, your own opaque ids, user-supplied UUIDs). |
98
+ | `documentId` | `string` | auto via `useId()` | Stable id for the _logical markdown document_ this `<AIMarkdown>` is rendering. Used as the id namespace for clobberable attributes (`id`, hash hrefs) so two documents on the same page do not cross-link (footnote `[^1]` in message A won't scroll to `[^1]` in message B). When one document is split into chunks rendered by multiple `<AIMarkdown>` instances, pass the SAME `documentId` to every chunk so prefixes align. The value is passed through `encodeURIComponent` before being injected into HTML attributes, so any string is safe (React's `useId()` output, your own opaque ids, user-supplied UUIDs). Long ids (>16 chars, e.g. UUIDs) are hashed via MurmurHash3 to a short Base62 form **inside the rendered `id="…"`/`href="#…"` prefix only** to keep HTML compact; `state.documentId` itself and registry keying via `useDocumentRegistry` stay raw, so deep linking and any consumer code reading `documentId` are unaffected. |
99
99
 
100
100
  ## Configuration
101
101
 
@@ -220,7 +220,7 @@ function CustomCodeBlock({ children }: PropsWithChildren) {
220
220
  | `variant` | `AIMarkdownVariant` | Active typography variant. |
221
221
  | `colorScheme` | `AIMarkdownColorScheme` | Active color scheme. |
222
222
  | `documentId` | `string` | Stable id for the logical markdown document — caller-supplied or auto-generated via `useId()`. |
223
- | `clobberPrefix` | `string` | URI-safe id prefix derived from `documentId`, used by every clobberable HTML attribute (`id=…` / `href="#…"`). Read this from the render state rather than recomputing locally when writing components that emit anchors. |
223
+ | `clobberPrefix` | `string` | URI-safe id prefix derived from `documentId` (with MurmurHash3 → Base62 shortening applied for >16-char ids), used by every clobberable HTML attribute (`id=…` / `href="#…"`). Read this from the render state rather than recomputing locally when writing components that emit anchors — the prefix's exact byte form is not part of the stability contract and may shift across versions. |
224
224
  | `config` | `TConfig` | Active render configuration (merged with defaults). |
225
225
 
226
226
  ### `useAIMarkdownMetadata<TMetadata>()`
package/dist/index.cjs CHANGED
@@ -1112,6 +1112,64 @@ var defaultAIMarkdownRenderConfig = Object.freeze({
1112
1112
  preserveOrphanReferences: true
1113
1113
  });
1114
1114
 
1115
+ // src/components/shortenDocumentId.ts
1116
+ var BASE62_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1117
+ function toBase62(n) {
1118
+ if (n === 0) return BASE62_ALPHABET[0];
1119
+ let v = n >>> 0;
1120
+ let out = "";
1121
+ while (v > 0) {
1122
+ out = BASE62_ALPHABET[v % 62] + out;
1123
+ v = Math.floor(v / 62);
1124
+ }
1125
+ return out;
1126
+ }
1127
+ function rotl32(x, r) {
1128
+ return (x << r | x >>> 32 - r) >>> 0;
1129
+ }
1130
+ var C1 = 3432918353;
1131
+ var C2 = 461845907;
1132
+ var UTF8_ENCODER = /* @__PURE__ */ new TextEncoder();
1133
+ function murmur3_32(s, seed = 0) {
1134
+ const bytes = UTF8_ENCODER.encode(s);
1135
+ const len = bytes.length;
1136
+ let h1 = seed >>> 0;
1137
+ const nblocks = len >>> 2;
1138
+ for (let i = 0; i < nblocks; i++) {
1139
+ const b = i * 4;
1140
+ let k1 = bytes[b] | bytes[b + 1] << 8 | bytes[b + 2] << 16 | bytes[b + 3] << 24;
1141
+ k1 = Math.imul(k1, C1);
1142
+ k1 = rotl32(k1, 15);
1143
+ k1 = Math.imul(k1, C2);
1144
+ h1 ^= k1;
1145
+ h1 = rotl32(h1, 13);
1146
+ h1 = Math.imul(h1, 5) + 3864292196 >>> 0;
1147
+ }
1148
+ const tail = nblocks * 4;
1149
+ const rem = len & 3;
1150
+ if (rem > 0) {
1151
+ let k1 = 0;
1152
+ if (rem === 3) k1 ^= bytes[tail + 2] << 16;
1153
+ if (rem >= 2) k1 ^= bytes[tail + 1] << 8;
1154
+ k1 ^= bytes[tail];
1155
+ k1 = Math.imul(k1, C1);
1156
+ k1 = rotl32(k1, 15);
1157
+ k1 = Math.imul(k1, C2);
1158
+ h1 ^= k1;
1159
+ }
1160
+ h1 ^= len;
1161
+ h1 ^= h1 >>> 16;
1162
+ h1 = Math.imul(h1, 2246822507);
1163
+ h1 ^= h1 >>> 13;
1164
+ h1 = Math.imul(h1, 3266489909);
1165
+ h1 ^= h1 >>> 16;
1166
+ return h1 >>> 0;
1167
+ }
1168
+ function shortenDocumentId(id, threshold = 16) {
1169
+ if (id.length <= threshold) return id;
1170
+ return toBase62(murmur3_32(id));
1171
+ }
1172
+
1115
1173
  // src/context.tsx
1116
1174
  var import_jsx_runtime = require("react/jsx-runtime");
1117
1175
  var AIMarkdownRenderStateContext = (0, import_react.createContext)(null);
@@ -1167,7 +1225,15 @@ var AIMarkdownRenderStateProvider = ({
1167
1225
  // construction site, not at the documentId storage site, so consumers
1168
1226
  // accessing `documentId` directly still see the raw React-native value
1169
1227
  // (e.g. `useId()`'s `_r_0_`) while id="..."/href="#..." bytes are safe.
1170
- clobberPrefix: `${encodeURIComponent(resolvedDocumentId)}-user-content-`,
1228
+ //
1229
+ // `shortenDocumentId` is applied here (NOT at the documentId storage
1230
+ // site) for the same reason: consumer-supplied UUIDs and nanoids
1231
+ // shouldn't bloat every rendered `id="…"`. Registry keying — which
1232
+ // reads `state.documentId` directly — stays on the raw value, so the
1233
+ // shortening is a pure HTML-output concern and the `useDocumentRegistry`
1234
+ // API surface is unaffected. Pure function ⇒ all chunks sharing one
1235
+ // logical documentId still produce identical prefixes.
1236
+ clobberPrefix: `${encodeURIComponent(shortenDocumentId(resolvedDocumentId))}-user-content-`,
1171
1237
  config: mergedConfig
1172
1238
  }),
1173
1239
  [streaming, fontSize, variant, colorScheme, resolvedDocumentId, mergedConfig]