@stream-mdx/core 0.0.3 → 0.1.1
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/CHANGELOG.md +12 -0
- package/README.md +6 -0
- package/dist/index.cjs +305 -46
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +304 -46
- package/dist/inline-parser.cjs +76 -3
- package/dist/inline-parser.d.cts +5 -0
- package/dist/inline-parser.d.ts +5 -0
- package/dist/inline-parser.mjs +76 -3
- package/dist/mixed-content.cjs +130 -9
- package/dist/mixed-content.d.cts +16 -2
- package/dist/mixed-content.d.ts +16 -2
- package/dist/mixed-content.mjs +130 -9
- package/dist/streaming/inline-streaming.cjs +99 -34
- package/dist/streaming/inline-streaming.d.cts +13 -2
- package/dist/streaming/inline-streaming.d.ts +13 -2
- package/dist/streaming/inline-streaming.mjs +98 -34
- package/dist/types.d.cts +41 -2
- package/dist/types.d.ts +41 -2
- package/dist/worker-html-sanitizer.cjs +16 -2
- package/dist/worker-html-sanitizer.mjs +16 -2
- package/package.json +2 -2
package/dist/mixed-content.cjs
CHANGED
|
@@ -41,9 +41,23 @@ var rehypeParse = __toESM(require("rehype-parse"), 1);
|
|
|
41
41
|
var rehypeSanitize = __toESM(require("rehype-sanitize"), 1);
|
|
42
42
|
var rehypeStringify = __toESM(require("rehype-stringify"), 1);
|
|
43
43
|
var import_unified = require("unified");
|
|
44
|
-
var
|
|
44
|
+
var rehypeSanitizeModule = rehypeSanitize;
|
|
45
|
+
var defaultSchema = rehypeSanitizeModule.defaultSchema;
|
|
46
|
+
var resolvePlugin = (mod) => {
|
|
47
|
+
if (typeof mod === "function") return mod;
|
|
48
|
+
if (mod && typeof mod.default === "function") {
|
|
49
|
+
return mod.default;
|
|
50
|
+
}
|
|
51
|
+
if (mod && typeof mod.default?.default === "function") {
|
|
52
|
+
return mod.default?.default;
|
|
53
|
+
}
|
|
54
|
+
return mod;
|
|
55
|
+
};
|
|
56
|
+
var rehypeParsePlugin = resolvePlugin(rehypeParse);
|
|
57
|
+
var rehypeSanitizePlugin = resolvePlugin(rehypeSanitizeModule);
|
|
58
|
+
var rehypeStringifyPlugin = resolvePlugin(rehypeStringify);
|
|
45
59
|
var SANITIZED_SCHEMA = createSchema();
|
|
46
|
-
var sanitizeProcessor = (0, import_unified.unified)().use(
|
|
60
|
+
var sanitizeProcessor = (0, import_unified.unified)().use(rehypeParsePlugin, { fragment: true }).use(rehypeSanitizePlugin, SANITIZED_SCHEMA).use(rehypeStringifyPlugin).freeze();
|
|
47
61
|
function sanitizeHtmlInWorker(html) {
|
|
48
62
|
if (!html) return "";
|
|
49
63
|
try {
|
|
@@ -159,9 +173,27 @@ function mergeAttributes(existing, additions) {
|
|
|
159
173
|
}
|
|
160
174
|
|
|
161
175
|
// src/mixed-content.ts
|
|
162
|
-
|
|
176
|
+
var DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS = /* @__PURE__ */ new Set([
|
|
177
|
+
"span",
|
|
178
|
+
"em",
|
|
179
|
+
"strong",
|
|
180
|
+
"code",
|
|
181
|
+
"kbd",
|
|
182
|
+
"del",
|
|
183
|
+
"s",
|
|
184
|
+
"mark",
|
|
185
|
+
"sub",
|
|
186
|
+
"sup",
|
|
187
|
+
"i",
|
|
188
|
+
"b",
|
|
189
|
+
"u",
|
|
190
|
+
"small",
|
|
191
|
+
"abbr",
|
|
192
|
+
"a"
|
|
193
|
+
]);
|
|
194
|
+
function extractMixedContentSegments(raw, baseOffset, parseInline, options) {
|
|
163
195
|
if (!raw) return [];
|
|
164
|
-
const initial = splitByTagSegments(raw, baseOffset, parseInline);
|
|
196
|
+
const initial = splitByTagSegments(raw, baseOffset, parseInline, options);
|
|
165
197
|
const expanded = [];
|
|
166
198
|
for (const segment of initial) {
|
|
167
199
|
if (segment.kind === "text") {
|
|
@@ -172,22 +204,58 @@ function extractMixedContentSegments(raw, baseOffset, parseInline) {
|
|
|
172
204
|
}
|
|
173
205
|
return mergeAdjacentTextSegments(expanded, parseInline);
|
|
174
206
|
}
|
|
175
|
-
function splitByTagSegments(source, baseOffset, parseInline) {
|
|
207
|
+
function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
176
208
|
const segments = [];
|
|
177
209
|
const lowerSource = source.toLowerCase();
|
|
178
210
|
const tagPattern = /<([A-Za-z][\w:-]*)([^<>]*?)\/?>/g;
|
|
179
211
|
let cursor = 0;
|
|
180
212
|
let match = tagPattern.exec(source);
|
|
181
213
|
const baseIsFinite = typeof baseOffset === "number" && Number.isFinite(baseOffset);
|
|
214
|
+
const htmlAllowTags = normalizeHtmlAllowlist(options?.html?.allowTags);
|
|
215
|
+
const htmlAutoClose = options?.html?.autoClose === true;
|
|
216
|
+
const htmlMaxNewlines = normalizeNewlineLimit(options?.html?.maxNewlines);
|
|
217
|
+
const mdxAutoClose = options?.mdx?.autoClose === true;
|
|
218
|
+
const mdxMaxNewlines = normalizeNewlineLimit(options?.mdx?.maxNewlines);
|
|
219
|
+
const mdxAllowlist = normalizeComponentAllowlist(options?.mdx?.componentAllowlist);
|
|
182
220
|
while (match !== null) {
|
|
183
221
|
const start = match.index;
|
|
184
222
|
const tagName = match[1];
|
|
185
223
|
const matchText = match[0];
|
|
186
|
-
const
|
|
224
|
+
const tagNameLower = tagName.toLowerCase();
|
|
225
|
+
const isSelfClosing = matchText.endsWith("/>") || isVoidHtmlTag(tagNameLower);
|
|
226
|
+
const mdxCandidate = isLikelyMdxComponent(tagName);
|
|
227
|
+
const mdxAllowed = mdxCandidate && (!mdxAllowlist || mdxAllowlist.has(tagName));
|
|
228
|
+
if (mdxCandidate && mdxAllowlist && !mdxAllowed) {
|
|
229
|
+
tagPattern.lastIndex = start + 1;
|
|
230
|
+
match = tagPattern.exec(source);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
187
233
|
let end = tagPattern.lastIndex;
|
|
188
|
-
if (!isSelfClosing && !
|
|
234
|
+
if (!isSelfClosing && !mdxAllowed) {
|
|
189
235
|
const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
|
|
190
236
|
if (closingIndex === -1) {
|
|
237
|
+
if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
|
|
238
|
+
const tail = source.slice(end);
|
|
239
|
+
const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
|
|
240
|
+
if (newlineCount <= htmlMaxNewlines) {
|
|
241
|
+
if (start > cursor) {
|
|
242
|
+
const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
|
|
243
|
+
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
244
|
+
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
245
|
+
}
|
|
246
|
+
const rawSegment2 = source.slice(start);
|
|
247
|
+
const closedValue = `${rawSegment2}</${tagName}>`;
|
|
248
|
+
const segment2 = {
|
|
249
|
+
kind: "html",
|
|
250
|
+
value: closedValue,
|
|
251
|
+
range: createSegmentRange(baseOffset, start, source.length),
|
|
252
|
+
sanitized: sanitizeHtmlInWorker(closedValue)
|
|
253
|
+
};
|
|
254
|
+
segments.push(segment2);
|
|
255
|
+
cursor = source.length;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
191
259
|
tagPattern.lastIndex = start + 1;
|
|
192
260
|
match = tagPattern.exec(source);
|
|
193
261
|
continue;
|
|
@@ -199,8 +267,8 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
199
267
|
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
200
268
|
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
201
269
|
}
|
|
202
|
-
|
|
203
|
-
const kind =
|
|
270
|
+
let rawSegment = source.slice(start, end);
|
|
271
|
+
const kind = mdxAllowed ? "mdx" : "html";
|
|
204
272
|
const segment = {
|
|
205
273
|
kind,
|
|
206
274
|
value: rawSegment,
|
|
@@ -209,6 +277,17 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
209
277
|
if (kind === "html") {
|
|
210
278
|
segment.sanitized = sanitizeHtmlInWorker(rawSegment);
|
|
211
279
|
} else {
|
|
280
|
+
const tail = source.slice(end);
|
|
281
|
+
const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
|
|
282
|
+
if (mdxAutoClose && newlineCount > mdxMaxNewlines) {
|
|
283
|
+
tagPattern.lastIndex = start + 1;
|
|
284
|
+
match = tagPattern.exec(source);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (mdxAutoClose && !rawSegment.endsWith("/>")) {
|
|
288
|
+
rawSegment = selfCloseTag(rawSegment);
|
|
289
|
+
segment.value = rawSegment;
|
|
290
|
+
}
|
|
212
291
|
segment.status = "pending";
|
|
213
292
|
}
|
|
214
293
|
segments.push(segment);
|
|
@@ -333,6 +412,48 @@ var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "meta", "input"
|
|
|
333
412
|
function isVoidHtmlTag(tagName) {
|
|
334
413
|
return VOID_HTML_TAGS.has(tagName.toLowerCase());
|
|
335
414
|
}
|
|
415
|
+
function normalizeNewlineLimit(value) {
|
|
416
|
+
if (!Number.isFinite(value ?? Number.NaN)) {
|
|
417
|
+
return 2;
|
|
418
|
+
}
|
|
419
|
+
return Math.max(0, value ?? 0);
|
|
420
|
+
}
|
|
421
|
+
function normalizeHtmlAllowlist(value) {
|
|
422
|
+
if (!value) return DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
423
|
+
const tags = /* @__PURE__ */ new Set();
|
|
424
|
+
for (const tag of value) {
|
|
425
|
+
if (tag) {
|
|
426
|
+
tags.add(tag.toLowerCase());
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return tags.size > 0 ? tags : DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
430
|
+
}
|
|
431
|
+
function normalizeComponentAllowlist(value) {
|
|
432
|
+
if (!value) return null;
|
|
433
|
+
const tags = /* @__PURE__ */ new Set();
|
|
434
|
+
for (const tag of value) {
|
|
435
|
+
if (tag) tags.add(tag);
|
|
436
|
+
}
|
|
437
|
+
return tags.size > 0 ? tags : null;
|
|
438
|
+
}
|
|
439
|
+
function countNewlines(value, limit) {
|
|
440
|
+
let count = 0;
|
|
441
|
+
for (let i = 0; i < value.length; i++) {
|
|
442
|
+
if (value.charCodeAt(i) === 10) {
|
|
443
|
+
count += 1;
|
|
444
|
+
if (limit !== void 0 && count >= limit) {
|
|
445
|
+
return count;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return count;
|
|
450
|
+
}
|
|
451
|
+
function selfCloseTag(rawTag) {
|
|
452
|
+
if (rawTag.endsWith("/>")) return rawTag;
|
|
453
|
+
const closeIndex = rawTag.lastIndexOf(">");
|
|
454
|
+
if (closeIndex === -1) return rawTag;
|
|
455
|
+
return `${rawTag.slice(0, closeIndex)}/>`;
|
|
456
|
+
}
|
|
336
457
|
function isLikelyMdxComponent(tagName) {
|
|
337
458
|
const first = tagName.charAt(0);
|
|
338
459
|
return first.toUpperCase() === first && first.toLowerCase() !== first;
|
package/dist/mixed-content.d.cts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { InlineNode, MixedContentSegment } from './types.cjs';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface MixedContentAutoCloseHtmlOptions {
|
|
4
|
+
autoClose?: boolean;
|
|
5
|
+
maxNewlines?: number;
|
|
6
|
+
allowTags?: Iterable<string>;
|
|
7
|
+
}
|
|
8
|
+
interface MixedContentAutoCloseMdxOptions {
|
|
9
|
+
autoClose?: boolean;
|
|
10
|
+
maxNewlines?: number;
|
|
11
|
+
componentAllowlist?: Iterable<string>;
|
|
12
|
+
}
|
|
13
|
+
interface MixedContentOptions {
|
|
14
|
+
html?: MixedContentAutoCloseHtmlOptions;
|
|
15
|
+
mdx?: MixedContentAutoCloseMdxOptions;
|
|
16
|
+
}
|
|
17
|
+
declare function extractMixedContentSegments(raw: string, baseOffset: number | undefined, parseInline: (content: string) => InlineNode[], options?: MixedContentOptions): MixedContentSegment[];
|
|
4
18
|
declare function isLikelyMdxComponent(tagName: string): boolean;
|
|
5
19
|
declare function findClosingHtmlTag(lowerSource: string, lowerTagName: string, startIndex: number): number;
|
|
6
20
|
|
|
7
|
-
export { extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent };
|
|
21
|
+
export { type MixedContentAutoCloseHtmlOptions, type MixedContentAutoCloseMdxOptions, type MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent };
|
package/dist/mixed-content.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { InlineNode, MixedContentSegment } from './types.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface MixedContentAutoCloseHtmlOptions {
|
|
4
|
+
autoClose?: boolean;
|
|
5
|
+
maxNewlines?: number;
|
|
6
|
+
allowTags?: Iterable<string>;
|
|
7
|
+
}
|
|
8
|
+
interface MixedContentAutoCloseMdxOptions {
|
|
9
|
+
autoClose?: boolean;
|
|
10
|
+
maxNewlines?: number;
|
|
11
|
+
componentAllowlist?: Iterable<string>;
|
|
12
|
+
}
|
|
13
|
+
interface MixedContentOptions {
|
|
14
|
+
html?: MixedContentAutoCloseHtmlOptions;
|
|
15
|
+
mdx?: MixedContentAutoCloseMdxOptions;
|
|
16
|
+
}
|
|
17
|
+
declare function extractMixedContentSegments(raw: string, baseOffset: number | undefined, parseInline: (content: string) => InlineNode[], options?: MixedContentOptions): MixedContentSegment[];
|
|
4
18
|
declare function isLikelyMdxComponent(tagName: string): boolean;
|
|
5
19
|
declare function findClosingHtmlTag(lowerSource: string, lowerTagName: string, startIndex: number): number;
|
|
6
20
|
|
|
7
|
-
export { extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent };
|
|
21
|
+
export { type MixedContentAutoCloseHtmlOptions, type MixedContentAutoCloseMdxOptions, type MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent };
|
package/dist/mixed-content.mjs
CHANGED
|
@@ -3,9 +3,23 @@ import * as rehypeParse from "rehype-parse";
|
|
|
3
3
|
import * as rehypeSanitize from "rehype-sanitize";
|
|
4
4
|
import * as rehypeStringify from "rehype-stringify";
|
|
5
5
|
import { unified } from "unified";
|
|
6
|
-
var
|
|
6
|
+
var rehypeSanitizeModule = rehypeSanitize;
|
|
7
|
+
var defaultSchema = rehypeSanitizeModule.defaultSchema;
|
|
8
|
+
var resolvePlugin = (mod) => {
|
|
9
|
+
if (typeof mod === "function") return mod;
|
|
10
|
+
if (mod && typeof mod.default === "function") {
|
|
11
|
+
return mod.default;
|
|
12
|
+
}
|
|
13
|
+
if (mod && typeof mod.default?.default === "function") {
|
|
14
|
+
return mod.default?.default;
|
|
15
|
+
}
|
|
16
|
+
return mod;
|
|
17
|
+
};
|
|
18
|
+
var rehypeParsePlugin = resolvePlugin(rehypeParse);
|
|
19
|
+
var rehypeSanitizePlugin = resolvePlugin(rehypeSanitizeModule);
|
|
20
|
+
var rehypeStringifyPlugin = resolvePlugin(rehypeStringify);
|
|
7
21
|
var SANITIZED_SCHEMA = createSchema();
|
|
8
|
-
var sanitizeProcessor = unified().use(
|
|
22
|
+
var sanitizeProcessor = unified().use(rehypeParsePlugin, { fragment: true }).use(rehypeSanitizePlugin, SANITIZED_SCHEMA).use(rehypeStringifyPlugin).freeze();
|
|
9
23
|
function sanitizeHtmlInWorker(html) {
|
|
10
24
|
if (!html) return "";
|
|
11
25
|
try {
|
|
@@ -121,9 +135,27 @@ function mergeAttributes(existing, additions) {
|
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
// src/mixed-content.ts
|
|
124
|
-
|
|
138
|
+
var DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS = /* @__PURE__ */ new Set([
|
|
139
|
+
"span",
|
|
140
|
+
"em",
|
|
141
|
+
"strong",
|
|
142
|
+
"code",
|
|
143
|
+
"kbd",
|
|
144
|
+
"del",
|
|
145
|
+
"s",
|
|
146
|
+
"mark",
|
|
147
|
+
"sub",
|
|
148
|
+
"sup",
|
|
149
|
+
"i",
|
|
150
|
+
"b",
|
|
151
|
+
"u",
|
|
152
|
+
"small",
|
|
153
|
+
"abbr",
|
|
154
|
+
"a"
|
|
155
|
+
]);
|
|
156
|
+
function extractMixedContentSegments(raw, baseOffset, parseInline, options) {
|
|
125
157
|
if (!raw) return [];
|
|
126
|
-
const initial = splitByTagSegments(raw, baseOffset, parseInline);
|
|
158
|
+
const initial = splitByTagSegments(raw, baseOffset, parseInline, options);
|
|
127
159
|
const expanded = [];
|
|
128
160
|
for (const segment of initial) {
|
|
129
161
|
if (segment.kind === "text") {
|
|
@@ -134,22 +166,58 @@ function extractMixedContentSegments(raw, baseOffset, parseInline) {
|
|
|
134
166
|
}
|
|
135
167
|
return mergeAdjacentTextSegments(expanded, parseInline);
|
|
136
168
|
}
|
|
137
|
-
function splitByTagSegments(source, baseOffset, parseInline) {
|
|
169
|
+
function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
138
170
|
const segments = [];
|
|
139
171
|
const lowerSource = source.toLowerCase();
|
|
140
172
|
const tagPattern = /<([A-Za-z][\w:-]*)([^<>]*?)\/?>/g;
|
|
141
173
|
let cursor = 0;
|
|
142
174
|
let match = tagPattern.exec(source);
|
|
143
175
|
const baseIsFinite = typeof baseOffset === "number" && Number.isFinite(baseOffset);
|
|
176
|
+
const htmlAllowTags = normalizeHtmlAllowlist(options?.html?.allowTags);
|
|
177
|
+
const htmlAutoClose = options?.html?.autoClose === true;
|
|
178
|
+
const htmlMaxNewlines = normalizeNewlineLimit(options?.html?.maxNewlines);
|
|
179
|
+
const mdxAutoClose = options?.mdx?.autoClose === true;
|
|
180
|
+
const mdxMaxNewlines = normalizeNewlineLimit(options?.mdx?.maxNewlines);
|
|
181
|
+
const mdxAllowlist = normalizeComponentAllowlist(options?.mdx?.componentAllowlist);
|
|
144
182
|
while (match !== null) {
|
|
145
183
|
const start = match.index;
|
|
146
184
|
const tagName = match[1];
|
|
147
185
|
const matchText = match[0];
|
|
148
|
-
const
|
|
186
|
+
const tagNameLower = tagName.toLowerCase();
|
|
187
|
+
const isSelfClosing = matchText.endsWith("/>") || isVoidHtmlTag(tagNameLower);
|
|
188
|
+
const mdxCandidate = isLikelyMdxComponent(tagName);
|
|
189
|
+
const mdxAllowed = mdxCandidate && (!mdxAllowlist || mdxAllowlist.has(tagName));
|
|
190
|
+
if (mdxCandidate && mdxAllowlist && !mdxAllowed) {
|
|
191
|
+
tagPattern.lastIndex = start + 1;
|
|
192
|
+
match = tagPattern.exec(source);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
149
195
|
let end = tagPattern.lastIndex;
|
|
150
|
-
if (!isSelfClosing && !
|
|
196
|
+
if (!isSelfClosing && !mdxAllowed) {
|
|
151
197
|
const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
|
|
152
198
|
if (closingIndex === -1) {
|
|
199
|
+
if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
|
|
200
|
+
const tail = source.slice(end);
|
|
201
|
+
const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
|
|
202
|
+
if (newlineCount <= htmlMaxNewlines) {
|
|
203
|
+
if (start > cursor) {
|
|
204
|
+
const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
|
|
205
|
+
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
206
|
+
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
207
|
+
}
|
|
208
|
+
const rawSegment2 = source.slice(start);
|
|
209
|
+
const closedValue = `${rawSegment2}</${tagName}>`;
|
|
210
|
+
const segment2 = {
|
|
211
|
+
kind: "html",
|
|
212
|
+
value: closedValue,
|
|
213
|
+
range: createSegmentRange(baseOffset, start, source.length),
|
|
214
|
+
sanitized: sanitizeHtmlInWorker(closedValue)
|
|
215
|
+
};
|
|
216
|
+
segments.push(segment2);
|
|
217
|
+
cursor = source.length;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
153
221
|
tagPattern.lastIndex = start + 1;
|
|
154
222
|
match = tagPattern.exec(source);
|
|
155
223
|
continue;
|
|
@@ -161,8 +229,8 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
161
229
|
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
162
230
|
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
163
231
|
}
|
|
164
|
-
|
|
165
|
-
const kind =
|
|
232
|
+
let rawSegment = source.slice(start, end);
|
|
233
|
+
const kind = mdxAllowed ? "mdx" : "html";
|
|
166
234
|
const segment = {
|
|
167
235
|
kind,
|
|
168
236
|
value: rawSegment,
|
|
@@ -171,6 +239,17 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
171
239
|
if (kind === "html") {
|
|
172
240
|
segment.sanitized = sanitizeHtmlInWorker(rawSegment);
|
|
173
241
|
} else {
|
|
242
|
+
const tail = source.slice(end);
|
|
243
|
+
const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
|
|
244
|
+
if (mdxAutoClose && newlineCount > mdxMaxNewlines) {
|
|
245
|
+
tagPattern.lastIndex = start + 1;
|
|
246
|
+
match = tagPattern.exec(source);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (mdxAutoClose && !rawSegment.endsWith("/>")) {
|
|
250
|
+
rawSegment = selfCloseTag(rawSegment);
|
|
251
|
+
segment.value = rawSegment;
|
|
252
|
+
}
|
|
174
253
|
segment.status = "pending";
|
|
175
254
|
}
|
|
176
255
|
segments.push(segment);
|
|
@@ -295,6 +374,48 @@ var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "meta", "input"
|
|
|
295
374
|
function isVoidHtmlTag(tagName) {
|
|
296
375
|
return VOID_HTML_TAGS.has(tagName.toLowerCase());
|
|
297
376
|
}
|
|
377
|
+
function normalizeNewlineLimit(value) {
|
|
378
|
+
if (!Number.isFinite(value ?? Number.NaN)) {
|
|
379
|
+
return 2;
|
|
380
|
+
}
|
|
381
|
+
return Math.max(0, value ?? 0);
|
|
382
|
+
}
|
|
383
|
+
function normalizeHtmlAllowlist(value) {
|
|
384
|
+
if (!value) return DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
385
|
+
const tags = /* @__PURE__ */ new Set();
|
|
386
|
+
for (const tag of value) {
|
|
387
|
+
if (tag) {
|
|
388
|
+
tags.add(tag.toLowerCase());
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return tags.size > 0 ? tags : DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
392
|
+
}
|
|
393
|
+
function normalizeComponentAllowlist(value) {
|
|
394
|
+
if (!value) return null;
|
|
395
|
+
const tags = /* @__PURE__ */ new Set();
|
|
396
|
+
for (const tag of value) {
|
|
397
|
+
if (tag) tags.add(tag);
|
|
398
|
+
}
|
|
399
|
+
return tags.size > 0 ? tags : null;
|
|
400
|
+
}
|
|
401
|
+
function countNewlines(value, limit) {
|
|
402
|
+
let count = 0;
|
|
403
|
+
for (let i = 0; i < value.length; i++) {
|
|
404
|
+
if (value.charCodeAt(i) === 10) {
|
|
405
|
+
count += 1;
|
|
406
|
+
if (limit !== void 0 && count >= limit) {
|
|
407
|
+
return count;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return count;
|
|
412
|
+
}
|
|
413
|
+
function selfCloseTag(rawTag) {
|
|
414
|
+
if (rawTag.endsWith("/>")) return rawTag;
|
|
415
|
+
const closeIndex = rawTag.lastIndexOf(">");
|
|
416
|
+
if (closeIndex === -1) return rawTag;
|
|
417
|
+
return `${rawTag.slice(0, closeIndex)}/>`;
|
|
418
|
+
}
|
|
298
419
|
function isLikelyMdxComponent(tagName) {
|
|
299
420
|
const first = tagName.charAt(0);
|
|
300
421
|
return first.toUpperCase() === first && first.toLowerCase() !== first;
|
|
@@ -20,69 +20,134 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/streaming/inline-streaming.ts
|
|
21
21
|
var inline_streaming_exports = {};
|
|
22
22
|
__export(inline_streaming_exports, {
|
|
23
|
+
normalizeFormatAnticipation: () => normalizeFormatAnticipation,
|
|
23
24
|
prepareInlineStreamingContent: () => prepareInlineStreamingContent
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(inline_streaming_exports);
|
|
27
|
+
var DEFAULT_FORMAT_ANTICIPATION = {
|
|
28
|
+
inline: false,
|
|
29
|
+
mathInline: false,
|
|
30
|
+
mathBlock: false,
|
|
31
|
+
html: false,
|
|
32
|
+
mdx: false,
|
|
33
|
+
regex: false
|
|
34
|
+
};
|
|
35
|
+
function normalizeFormatAnticipation(input) {
|
|
36
|
+
if (input === true) {
|
|
37
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION, inline: true };
|
|
38
|
+
}
|
|
39
|
+
if (!input) {
|
|
40
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION };
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
inline: input.inline ?? false,
|
|
44
|
+
mathInline: input.mathInline ?? false,
|
|
45
|
+
mathBlock: input.mathBlock ?? false,
|
|
46
|
+
html: input.html ?? false,
|
|
47
|
+
mdx: input.mdx ?? false,
|
|
48
|
+
regex: input.regex ?? false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
26
51
|
function prepareInlineStreamingContent(content, options) {
|
|
27
|
-
const enableAnticipation = Boolean(options?.formatAnticipation);
|
|
28
52
|
const enableMath = options?.math !== false;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
const anticipation = normalizeFormatAnticipation(options?.formatAnticipation);
|
|
54
|
+
const enableInlineAnticipation = anticipation.inline;
|
|
55
|
+
const enableMathInlineAnticipation = anticipation.mathInline;
|
|
56
|
+
const enableMathBlockAnticipation = anticipation.mathBlock;
|
|
57
|
+
const stack = [];
|
|
58
|
+
const toggleToken = (token) => {
|
|
59
|
+
const last = stack[stack.length - 1];
|
|
60
|
+
if (last === token) {
|
|
61
|
+
stack.pop();
|
|
62
|
+
} else {
|
|
63
|
+
stack.push(token);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
let mathDisplayOpen = false;
|
|
67
|
+
let mathDisplayCrossedNewline = false;
|
|
34
68
|
for (let i = 0; i < content.length; i++) {
|
|
35
69
|
const code = content.charCodeAt(i);
|
|
36
|
-
if (code ===
|
|
37
|
-
|
|
70
|
+
if (code === 10 || code === 13) {
|
|
71
|
+
if (mathDisplayOpen) {
|
|
72
|
+
mathDisplayCrossedNewline = true;
|
|
73
|
+
}
|
|
38
74
|
continue;
|
|
39
75
|
}
|
|
40
76
|
if (code === 96) {
|
|
41
|
-
|
|
77
|
+
toggleToken("code");
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (code === 126 && i + 1 < content.length && content.charCodeAt(i + 1) === 126) {
|
|
81
|
+
toggleToken("strike");
|
|
82
|
+
i += 1;
|
|
42
83
|
continue;
|
|
43
84
|
}
|
|
44
85
|
if (code === 42) {
|
|
45
86
|
if (i + 1 < content.length && content.charCodeAt(i + 1) === 42) {
|
|
46
|
-
|
|
47
|
-
starCount += 2;
|
|
87
|
+
toggleToken("strong");
|
|
48
88
|
i += 1;
|
|
49
89
|
} else {
|
|
50
|
-
|
|
90
|
+
toggleToken("em");
|
|
51
91
|
}
|
|
52
92
|
continue;
|
|
53
93
|
}
|
|
54
|
-
if (code ===
|
|
55
|
-
if (i + 1 < content.length && content.charCodeAt(i + 1) ===
|
|
56
|
-
|
|
94
|
+
if (enableMath && code === 36) {
|
|
95
|
+
if (i + 1 < content.length && content.charCodeAt(i + 1) === 36) {
|
|
96
|
+
toggleToken("math-display");
|
|
97
|
+
if (mathDisplayOpen) {
|
|
98
|
+
mathDisplayOpen = false;
|
|
99
|
+
mathDisplayCrossedNewline = false;
|
|
100
|
+
} else {
|
|
101
|
+
mathDisplayOpen = true;
|
|
102
|
+
mathDisplayCrossedNewline = false;
|
|
103
|
+
}
|
|
57
104
|
i += 1;
|
|
105
|
+
} else {
|
|
106
|
+
toggleToken("math-inline");
|
|
58
107
|
}
|
|
59
108
|
}
|
|
60
109
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return { kind: "parse", status: "complete", content, appended: "" };
|
|
110
|
+
const hasIncompleteFormatting = stack.some((token) => token === "code" || token === "strike" || token === "strong" || token === "em");
|
|
111
|
+
const hasIncompleteMathInline = stack.includes("math-inline");
|
|
112
|
+
const hasIncompleteMathDisplay = stack.includes("math-display");
|
|
113
|
+
const hasIncompleteMath = hasIncompleteMathInline || hasIncompleteMathDisplay;
|
|
114
|
+
if (enableMath && hasIncompleteMath) {
|
|
115
|
+
if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
|
|
116
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
117
|
+
}
|
|
118
|
+
if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
|
|
119
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
120
|
+
}
|
|
73
121
|
}
|
|
74
|
-
if (!
|
|
122
|
+
if (hasIncompleteFormatting && !enableInlineAnticipation) {
|
|
75
123
|
return { kind: "raw", status: "raw", reason: "incomplete-formatting" };
|
|
76
124
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
if (!hasIncompleteFormatting && !hasIncompleteMath) {
|
|
126
|
+
return { kind: "parse", status: "complete", content, appended: "" };
|
|
127
|
+
}
|
|
128
|
+
const appendForToken = (token) => {
|
|
129
|
+
switch (token) {
|
|
130
|
+
case "code":
|
|
131
|
+
return "`";
|
|
132
|
+
case "strike":
|
|
133
|
+
return "~~";
|
|
134
|
+
case "strong":
|
|
135
|
+
return "**";
|
|
136
|
+
case "em":
|
|
137
|
+
return "*";
|
|
138
|
+
case "math-inline":
|
|
139
|
+
return "$";
|
|
140
|
+
case "math-display":
|
|
141
|
+
return "$$";
|
|
142
|
+
default:
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const appended = stack.slice().reverse().map((token) => appendForToken(token)).join("");
|
|
83
147
|
return { kind: "parse", status: "anticipated", content: content + appended, appended };
|
|
84
148
|
}
|
|
85
149
|
// Annotate the CommonJS export names for ESM import in node:
|
|
86
150
|
0 && (module.exports = {
|
|
151
|
+
normalizeFormatAnticipation,
|
|
87
152
|
prepareInlineStreamingContent
|
|
88
153
|
});
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { FormatAnticipationConfig } from '../types.cjs';
|
|
2
|
+
|
|
1
3
|
type InlineStreamingInlineStatus = "complete" | "anticipated" | "raw";
|
|
4
|
+
type NormalizedFormatAnticipation = {
|
|
5
|
+
inline: boolean;
|
|
6
|
+
mathInline: boolean;
|
|
7
|
+
mathBlock: boolean;
|
|
8
|
+
html: boolean;
|
|
9
|
+
mdx: boolean;
|
|
10
|
+
regex: boolean;
|
|
11
|
+
};
|
|
12
|
+
declare function normalizeFormatAnticipation(input?: FormatAnticipationConfig): NormalizedFormatAnticipation;
|
|
2
13
|
type InlineStreamingPrepareResult = {
|
|
3
14
|
kind: "raw";
|
|
4
15
|
status: "raw";
|
|
@@ -10,8 +21,8 @@ type InlineStreamingPrepareResult = {
|
|
|
10
21
|
appended: string;
|
|
11
22
|
};
|
|
12
23
|
declare function prepareInlineStreamingContent(content: string, options?: {
|
|
13
|
-
formatAnticipation?:
|
|
24
|
+
formatAnticipation?: FormatAnticipationConfig;
|
|
14
25
|
math?: boolean;
|
|
15
26
|
}): InlineStreamingPrepareResult;
|
|
16
27
|
|
|
17
|
-
export { type InlineStreamingInlineStatus, type InlineStreamingPrepareResult, prepareInlineStreamingContent };
|
|
28
|
+
export { type InlineStreamingInlineStatus, type InlineStreamingPrepareResult, type NormalizedFormatAnticipation, normalizeFormatAnticipation, prepareInlineStreamingContent };
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { FormatAnticipationConfig } from '../types.js';
|
|
2
|
+
|
|
1
3
|
type InlineStreamingInlineStatus = "complete" | "anticipated" | "raw";
|
|
4
|
+
type NormalizedFormatAnticipation = {
|
|
5
|
+
inline: boolean;
|
|
6
|
+
mathInline: boolean;
|
|
7
|
+
mathBlock: boolean;
|
|
8
|
+
html: boolean;
|
|
9
|
+
mdx: boolean;
|
|
10
|
+
regex: boolean;
|
|
11
|
+
};
|
|
12
|
+
declare function normalizeFormatAnticipation(input?: FormatAnticipationConfig): NormalizedFormatAnticipation;
|
|
2
13
|
type InlineStreamingPrepareResult = {
|
|
3
14
|
kind: "raw";
|
|
4
15
|
status: "raw";
|
|
@@ -10,8 +21,8 @@ type InlineStreamingPrepareResult = {
|
|
|
10
21
|
appended: string;
|
|
11
22
|
};
|
|
12
23
|
declare function prepareInlineStreamingContent(content: string, options?: {
|
|
13
|
-
formatAnticipation?:
|
|
24
|
+
formatAnticipation?: FormatAnticipationConfig;
|
|
14
25
|
math?: boolean;
|
|
15
26
|
}): InlineStreamingPrepareResult;
|
|
16
27
|
|
|
17
|
-
export { type InlineStreamingInlineStatus, type InlineStreamingPrepareResult, prepareInlineStreamingContent };
|
|
28
|
+
export { type InlineStreamingInlineStatus, type InlineStreamingPrepareResult, type NormalizedFormatAnticipation, normalizeFormatAnticipation, prepareInlineStreamingContent };
|