@stream-mdx/core 0.0.2 → 0.1.0
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 +13 -0
- package/dist/index.cjs +358 -27
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +356 -27
- package/dist/inline-parser.cjs +100 -18
- package/dist/inline-parser.d.cts +10 -0
- package/dist/inline-parser.d.ts +10 -0
- package/dist/inline-parser.mjs +100 -18
- 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 +153 -0
- package/dist/streaming/inline-streaming.d.cts +28 -0
- package/dist/streaming/inline-streaming.d.ts +28 -0
- package/dist/streaming/inline-streaming.mjs +127 -0
- package/dist/types.d.cts +24 -1
- package/dist/types.d.ts +24 -1
- package/dist/worker-html-sanitizer.cjs +16 -2
- package/dist/worker-html-sanitizer.mjs +16 -2
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @stream-mdx/core
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 294e557: Release StreamMDX 0.1.0 across all published packages.
|
|
8
|
+
|
|
9
|
+
## 0.0.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 47d1374: Add opt-in streaming format anticipation and an optional Mermaid diagram addon for ` ```mermaid ` code blocks.
|
|
14
|
+
- 7c79f09: Port the docs demo page to match the ql-homepage streaming UI, and fix several streaming parser/rendering edge cases (hard line breaks and display-math handling) while keeping the hosted worker bundle self-contained for static hosting.
|
|
15
|
+
|
|
3
16
|
## 0.0.2
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -72,8 +72,10 @@ __export(index_exports, {
|
|
|
72
72
|
inlineNodesToPlainText: () => inlineNodesToPlainText,
|
|
73
73
|
isLikelyMdxComponent: () => isLikelyMdxComponent,
|
|
74
74
|
normalizeBlockquoteText: () => normalizeBlockquoteText,
|
|
75
|
+
normalizeFormatAnticipation: () => normalizeFormatAnticipation,
|
|
75
76
|
normalizeLang: () => normalizeLang,
|
|
76
77
|
parseCodeFenceInfo: () => parseCodeFenceInfo,
|
|
78
|
+
prepareInlineStreamingContent: () => prepareInlineStreamingContent,
|
|
77
79
|
removeHeadingMarkers: () => removeHeadingMarkers,
|
|
78
80
|
sanitizeCodeHTML: () => sanitizeCodeHTML,
|
|
79
81
|
sanitizeHTML: () => sanitizeHTML,
|
|
@@ -289,12 +291,42 @@ function filterAllowedAttributes(attrs) {
|
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
// src/inline-parser.ts
|
|
294
|
+
function ensureGlobal(pattern) {
|
|
295
|
+
if (pattern.global) return pattern;
|
|
296
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
297
|
+
return new RegExp(pattern.source, flags);
|
|
298
|
+
}
|
|
299
|
+
function findLastMatch(pattern, value) {
|
|
300
|
+
const re = ensureGlobal(pattern);
|
|
301
|
+
let match = null;
|
|
302
|
+
let next;
|
|
303
|
+
while ((next = re.exec(value)) !== null) {
|
|
304
|
+
match = next;
|
|
305
|
+
}
|
|
306
|
+
return match;
|
|
307
|
+
}
|
|
308
|
+
function findMatchAfter(pattern, value, startIndex) {
|
|
309
|
+
const re = ensureGlobal(pattern);
|
|
310
|
+
re.lastIndex = Math.max(0, startIndex);
|
|
311
|
+
return re.exec(value);
|
|
312
|
+
}
|
|
313
|
+
function isSamePattern(a, b) {
|
|
314
|
+
return a.source === b.source && a.flags === b.flags;
|
|
315
|
+
}
|
|
316
|
+
function countMatches(pattern, value) {
|
|
317
|
+
const re = ensureGlobal(pattern);
|
|
318
|
+
let count = 0;
|
|
319
|
+
while (re.exec(value)) {
|
|
320
|
+
count += 1;
|
|
321
|
+
}
|
|
322
|
+
return count;
|
|
323
|
+
}
|
|
292
324
|
var InlineParser = class {
|
|
293
325
|
constructor(options = {}) {
|
|
294
326
|
this.plugins = [];
|
|
295
327
|
this.cache = /* @__PURE__ */ new Map();
|
|
296
328
|
this.maxCacheEntries = Number.isFinite(options.maxCacheEntries ?? Number.NaN) ? Math.max(0, options.maxCacheEntries ?? 0) : 2e3;
|
|
297
|
-
this.registerDefaultPlugins();
|
|
329
|
+
this.registerDefaultPlugins({ enableMath: options.enableMath !== false });
|
|
298
330
|
}
|
|
299
331
|
/**
|
|
300
332
|
* Register a plugin with the parser
|
|
@@ -336,6 +368,42 @@ var InlineParser = class {
|
|
|
336
368
|
}
|
|
337
369
|
return result;
|
|
338
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Streaming regex anticipation helper. Returns an append string if a plugin
|
|
373
|
+
* declares an incomplete match at the end of the buffer.
|
|
374
|
+
*/
|
|
375
|
+
getRegexAnticipationAppend(content) {
|
|
376
|
+
if (!content || this.plugins.length === 0) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
for (const plugin of this.plugins) {
|
|
380
|
+
if (!("re" in plugin)) continue;
|
|
381
|
+
const regexPlugin = plugin;
|
|
382
|
+
const anticipation = regexPlugin.anticipation;
|
|
383
|
+
if (!anticipation) continue;
|
|
384
|
+
const maxScanChars = Number.isFinite(anticipation.maxScanChars ?? Number.NaN) ? Math.max(1, anticipation.maxScanChars ?? 0) : 240;
|
|
385
|
+
const scan = content.slice(Math.max(0, content.length - maxScanChars));
|
|
386
|
+
if (regexPlugin.fastCheck && !regexPlugin.fastCheck(scan)) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const lastStart = findLastMatch(anticipation.start, scan);
|
|
390
|
+
if (!lastStart) continue;
|
|
391
|
+
if (isSamePattern(anticipation.start, anticipation.end)) {
|
|
392
|
+
const occurrences = countMatches(anticipation.start, scan);
|
|
393
|
+
if (occurrences % 2 === 0) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
const startIndex = lastStart.index + lastStart[0].length;
|
|
398
|
+
const hasEnd = Boolean(findMatchAfter(anticipation.end, scan, startIndex));
|
|
399
|
+
if (hasEnd) continue;
|
|
400
|
+
}
|
|
401
|
+
const appendValue = typeof anticipation.append === "function" ? anticipation.append(lastStart, content) : anticipation.append;
|
|
402
|
+
if (!appendValue) continue;
|
|
403
|
+
return appendValue;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
339
407
|
/**
|
|
340
408
|
* Clear the memoization cache
|
|
341
409
|
*/
|
|
@@ -360,10 +428,27 @@ var InlineParser = class {
|
|
|
360
428
|
* Register default plugins with proper precedence ordering
|
|
361
429
|
* Lower priority numbers = higher precedence (run first)
|
|
362
430
|
*/
|
|
363
|
-
registerDefaultPlugins() {
|
|
431
|
+
registerDefaultPlugins(options) {
|
|
432
|
+
if (options.enableMath) {
|
|
433
|
+
this.registerPlugin({
|
|
434
|
+
id: "math-display",
|
|
435
|
+
priority: 0,
|
|
436
|
+
re: /\$\$([\s\S]+?)\$\$/g,
|
|
437
|
+
toNode: (match) => ({ kind: "math-display", tex: match[1].trim() }),
|
|
438
|
+
fastCheck: (text) => text.indexOf("$$") !== -1
|
|
439
|
+
});
|
|
440
|
+
this.registerPlugin({
|
|
441
|
+
id: "math-inline",
|
|
442
|
+
priority: 1,
|
|
443
|
+
re: /\$([^$\n]+?)\$/g,
|
|
444
|
+
// Non-greedy to prevent spanning multiple expressions
|
|
445
|
+
toNode: (match) => ({ kind: "math-inline", tex: match[1].trim() }),
|
|
446
|
+
fastCheck: (text) => text.indexOf("$") !== -1
|
|
447
|
+
});
|
|
448
|
+
}
|
|
364
449
|
this.registerPlugin({
|
|
365
450
|
id: "escaped-character",
|
|
366
|
-
priority:
|
|
451
|
+
priority: 2,
|
|
367
452
|
re: /\\([\\`*_{}\[\]()#+\-.!>])/g,
|
|
368
453
|
toNode: (match) => ({
|
|
369
454
|
kind: "text",
|
|
@@ -372,19 +457,11 @@ var InlineParser = class {
|
|
|
372
457
|
fastCheck: (text) => text.indexOf("\\") !== -1
|
|
373
458
|
});
|
|
374
459
|
this.registerPlugin({
|
|
375
|
-
id: "
|
|
376
|
-
priority: 1,
|
|
377
|
-
re: /\$\$([^$]+?)\$\$/g,
|
|
378
|
-
toNode: (match) => ({ kind: "math-display", tex: match[1].trim() }),
|
|
379
|
-
fastCheck: (text) => text.indexOf("$$") !== -1
|
|
380
|
-
});
|
|
381
|
-
this.registerPlugin({
|
|
382
|
-
id: "math-inline",
|
|
460
|
+
id: "hard-break",
|
|
383
461
|
priority: 2,
|
|
384
|
-
re:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
fastCheck: (text) => text.indexOf("$") !== -1
|
|
462
|
+
re: /\\\r?\n| {2,}\r?\n/g,
|
|
463
|
+
toNode: (_match) => ({ kind: "br" }),
|
|
464
|
+
fastCheck: (text) => text.indexOf("\n") !== -1 || text.indexOf("\r") !== -1
|
|
388
465
|
});
|
|
389
466
|
this.registerPlugin({
|
|
390
467
|
id: "code-spans",
|
|
@@ -455,9 +532,16 @@ var InlineParser = class {
|
|
|
455
532
|
this.registerPlugin({
|
|
456
533
|
id: "citations",
|
|
457
534
|
priority: 10,
|
|
458
|
-
re: /\[\^([^\]]+)\]|@cite\{([^}]+)\}/g,
|
|
459
|
-
toNode: (match) => ({ kind: "citation", id: match[1] || match[2] }),
|
|
460
|
-
fastCheck: (text) => text.indexOf("@") !== -1 || text.indexOf("[^") !== -1
|
|
535
|
+
re: /\[\^([^\]\n]+)\]|@cite\{([^}\n]+)\}|\{cite:([^}\n]+)\}/g,
|
|
536
|
+
toNode: (match) => ({ kind: "citation", id: match[1] || match[2] || match[3] }),
|
|
537
|
+
fastCheck: (text) => text.indexOf("@cite") !== -1 || text.indexOf("[^") !== -1 || text.indexOf("{cite:") !== -1,
|
|
538
|
+
anticipation: {
|
|
539
|
+
start: /@cite\{|\{cite:/g,
|
|
540
|
+
end: /\}/g,
|
|
541
|
+
full: /@cite\{[^}\n]+?\}|\{cite:[^}\n]+?\}/g,
|
|
542
|
+
append: "}",
|
|
543
|
+
maxScanChars: 120
|
|
544
|
+
}
|
|
461
545
|
});
|
|
462
546
|
this.registerPlugin({
|
|
463
547
|
id: "mentions",
|
|
@@ -557,9 +641,23 @@ var rehypeParse = __toESM(require("rehype-parse"), 1);
|
|
|
557
641
|
var rehypeSanitize = __toESM(require("rehype-sanitize"), 1);
|
|
558
642
|
var rehypeStringify = __toESM(require("rehype-stringify"), 1);
|
|
559
643
|
var import_unified = require("unified");
|
|
560
|
-
var
|
|
644
|
+
var rehypeSanitizeModule = rehypeSanitize;
|
|
645
|
+
var defaultSchema = rehypeSanitizeModule.defaultSchema;
|
|
646
|
+
var resolvePlugin = (mod) => {
|
|
647
|
+
if (typeof mod === "function") return mod;
|
|
648
|
+
if (mod && typeof mod.default === "function") {
|
|
649
|
+
return mod.default;
|
|
650
|
+
}
|
|
651
|
+
if (mod && typeof mod.default?.default === "function") {
|
|
652
|
+
return mod.default?.default;
|
|
653
|
+
}
|
|
654
|
+
return mod;
|
|
655
|
+
};
|
|
656
|
+
var rehypeParsePlugin = resolvePlugin(rehypeParse);
|
|
657
|
+
var rehypeSanitizePlugin = resolvePlugin(rehypeSanitizeModule);
|
|
658
|
+
var rehypeStringifyPlugin = resolvePlugin(rehypeStringify);
|
|
561
659
|
var SANITIZED_SCHEMA = createSchema();
|
|
562
|
-
var sanitizeProcessor = (0, import_unified.unified)().use(
|
|
660
|
+
var sanitizeProcessor = (0, import_unified.unified)().use(rehypeParsePlugin, { fragment: true }).use(rehypeSanitizePlugin, SANITIZED_SCHEMA).use(rehypeStringifyPlugin).freeze();
|
|
563
661
|
function sanitizeHtmlInWorker(html) {
|
|
564
662
|
if (!html) return "";
|
|
565
663
|
try {
|
|
@@ -675,9 +773,27 @@ function mergeAttributes(existing, additions) {
|
|
|
675
773
|
}
|
|
676
774
|
|
|
677
775
|
// src/mixed-content.ts
|
|
678
|
-
|
|
776
|
+
var DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS = /* @__PURE__ */ new Set([
|
|
777
|
+
"span",
|
|
778
|
+
"em",
|
|
779
|
+
"strong",
|
|
780
|
+
"code",
|
|
781
|
+
"kbd",
|
|
782
|
+
"del",
|
|
783
|
+
"s",
|
|
784
|
+
"mark",
|
|
785
|
+
"sub",
|
|
786
|
+
"sup",
|
|
787
|
+
"i",
|
|
788
|
+
"b",
|
|
789
|
+
"u",
|
|
790
|
+
"small",
|
|
791
|
+
"abbr",
|
|
792
|
+
"a"
|
|
793
|
+
]);
|
|
794
|
+
function extractMixedContentSegments(raw, baseOffset, parseInline, options) {
|
|
679
795
|
if (!raw) return [];
|
|
680
|
-
const initial = splitByTagSegments(raw, baseOffset, parseInline);
|
|
796
|
+
const initial = splitByTagSegments(raw, baseOffset, parseInline, options);
|
|
681
797
|
const expanded = [];
|
|
682
798
|
for (const segment of initial) {
|
|
683
799
|
if (segment.kind === "text") {
|
|
@@ -688,22 +804,58 @@ function extractMixedContentSegments(raw, baseOffset, parseInline) {
|
|
|
688
804
|
}
|
|
689
805
|
return mergeAdjacentTextSegments(expanded, parseInline);
|
|
690
806
|
}
|
|
691
|
-
function splitByTagSegments(source, baseOffset, parseInline) {
|
|
807
|
+
function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
692
808
|
const segments = [];
|
|
693
809
|
const lowerSource = source.toLowerCase();
|
|
694
810
|
const tagPattern = /<([A-Za-z][\w:-]*)([^<>]*?)\/?>/g;
|
|
695
811
|
let cursor = 0;
|
|
696
812
|
let match = tagPattern.exec(source);
|
|
697
813
|
const baseIsFinite = typeof baseOffset === "number" && Number.isFinite(baseOffset);
|
|
814
|
+
const htmlAllowTags = normalizeHtmlAllowlist(options?.html?.allowTags);
|
|
815
|
+
const htmlAutoClose = options?.html?.autoClose === true;
|
|
816
|
+
const htmlMaxNewlines = normalizeNewlineLimit(options?.html?.maxNewlines);
|
|
817
|
+
const mdxAutoClose = options?.mdx?.autoClose === true;
|
|
818
|
+
const mdxMaxNewlines = normalizeNewlineLimit(options?.mdx?.maxNewlines);
|
|
819
|
+
const mdxAllowlist = normalizeComponentAllowlist(options?.mdx?.componentAllowlist);
|
|
698
820
|
while (match !== null) {
|
|
699
821
|
const start = match.index;
|
|
700
822
|
const tagName = match[1];
|
|
701
823
|
const matchText = match[0];
|
|
702
|
-
const
|
|
824
|
+
const tagNameLower = tagName.toLowerCase();
|
|
825
|
+
const isSelfClosing = matchText.endsWith("/>") || isVoidHtmlTag(tagNameLower);
|
|
826
|
+
const mdxCandidate = isLikelyMdxComponent(tagName);
|
|
827
|
+
const mdxAllowed = mdxCandidate && (!mdxAllowlist || mdxAllowlist.has(tagName));
|
|
828
|
+
if (mdxCandidate && mdxAllowlist && !mdxAllowed) {
|
|
829
|
+
tagPattern.lastIndex = start + 1;
|
|
830
|
+
match = tagPattern.exec(source);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
703
833
|
let end = tagPattern.lastIndex;
|
|
704
|
-
if (!isSelfClosing && !
|
|
834
|
+
if (!isSelfClosing && !mdxAllowed) {
|
|
705
835
|
const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
|
|
706
836
|
if (closingIndex === -1) {
|
|
837
|
+
if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
|
|
838
|
+
const tail = source.slice(end);
|
|
839
|
+
const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
|
|
840
|
+
if (newlineCount <= htmlMaxNewlines) {
|
|
841
|
+
if (start > cursor) {
|
|
842
|
+
const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
|
|
843
|
+
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
844
|
+
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
845
|
+
}
|
|
846
|
+
const rawSegment2 = source.slice(start);
|
|
847
|
+
const closedValue = `${rawSegment2}</${tagName}>`;
|
|
848
|
+
const segment2 = {
|
|
849
|
+
kind: "html",
|
|
850
|
+
value: closedValue,
|
|
851
|
+
range: createSegmentRange(baseOffset, start, source.length),
|
|
852
|
+
sanitized: sanitizeHtmlInWorker(closedValue)
|
|
853
|
+
};
|
|
854
|
+
segments.push(segment2);
|
|
855
|
+
cursor = source.length;
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
707
859
|
tagPattern.lastIndex = start + 1;
|
|
708
860
|
match = tagPattern.exec(source);
|
|
709
861
|
continue;
|
|
@@ -715,8 +867,8 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
715
867
|
const absoluteTo = baseIsFinite ? baseOffset + start : void 0;
|
|
716
868
|
pushTextSegment(segments, source.slice(cursor, start), absoluteFrom, absoluteTo, parseInline);
|
|
717
869
|
}
|
|
718
|
-
|
|
719
|
-
const kind =
|
|
870
|
+
let rawSegment = source.slice(start, end);
|
|
871
|
+
const kind = mdxAllowed ? "mdx" : "html";
|
|
720
872
|
const segment = {
|
|
721
873
|
kind,
|
|
722
874
|
value: rawSegment,
|
|
@@ -725,6 +877,17 @@ function splitByTagSegments(source, baseOffset, parseInline) {
|
|
|
725
877
|
if (kind === "html") {
|
|
726
878
|
segment.sanitized = sanitizeHtmlInWorker(rawSegment);
|
|
727
879
|
} else {
|
|
880
|
+
const tail = source.slice(end);
|
|
881
|
+
const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
|
|
882
|
+
if (mdxAutoClose && newlineCount > mdxMaxNewlines) {
|
|
883
|
+
tagPattern.lastIndex = start + 1;
|
|
884
|
+
match = tagPattern.exec(source);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
if (mdxAutoClose && !rawSegment.endsWith("/>")) {
|
|
888
|
+
rawSegment = selfCloseTag(rawSegment);
|
|
889
|
+
segment.value = rawSegment;
|
|
890
|
+
}
|
|
728
891
|
segment.status = "pending";
|
|
729
892
|
}
|
|
730
893
|
segments.push(segment);
|
|
@@ -849,6 +1012,48 @@ var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "meta", "input"
|
|
|
849
1012
|
function isVoidHtmlTag(tagName) {
|
|
850
1013
|
return VOID_HTML_TAGS.has(tagName.toLowerCase());
|
|
851
1014
|
}
|
|
1015
|
+
function normalizeNewlineLimit(value) {
|
|
1016
|
+
if (!Number.isFinite(value ?? Number.NaN)) {
|
|
1017
|
+
return 2;
|
|
1018
|
+
}
|
|
1019
|
+
return Math.max(0, value ?? 0);
|
|
1020
|
+
}
|
|
1021
|
+
function normalizeHtmlAllowlist(value) {
|
|
1022
|
+
if (!value) return DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
1023
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1024
|
+
for (const tag of value) {
|
|
1025
|
+
if (tag) {
|
|
1026
|
+
tags.add(tag.toLowerCase());
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return tags.size > 0 ? tags : DEFAULT_INLINE_HTML_AUTOCLOSE_TAGS;
|
|
1030
|
+
}
|
|
1031
|
+
function normalizeComponentAllowlist(value) {
|
|
1032
|
+
if (!value) return null;
|
|
1033
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1034
|
+
for (const tag of value) {
|
|
1035
|
+
if (tag) tags.add(tag);
|
|
1036
|
+
}
|
|
1037
|
+
return tags.size > 0 ? tags : null;
|
|
1038
|
+
}
|
|
1039
|
+
function countNewlines(value, limit) {
|
|
1040
|
+
let count = 0;
|
|
1041
|
+
for (let i = 0; i < value.length; i++) {
|
|
1042
|
+
if (value.charCodeAt(i) === 10) {
|
|
1043
|
+
count += 1;
|
|
1044
|
+
if (limit !== void 0 && count >= limit) {
|
|
1045
|
+
return count;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return count;
|
|
1050
|
+
}
|
|
1051
|
+
function selfCloseTag(rawTag) {
|
|
1052
|
+
if (rawTag.endsWith("/>")) return rawTag;
|
|
1053
|
+
const closeIndex = rawTag.lastIndexOf(">");
|
|
1054
|
+
if (closeIndex === -1) return rawTag;
|
|
1055
|
+
return `${rawTag.slice(0, closeIndex)}/>`;
|
|
1056
|
+
}
|
|
852
1057
|
function isLikelyMdxComponent(tagName) {
|
|
853
1058
|
const first = tagName.charAt(0);
|
|
854
1059
|
return first.toUpperCase() === first && first.toLowerCase() !== first;
|
|
@@ -2799,6 +3004,130 @@ var CustomStreamingMatcher = class {
|
|
|
2799
3004
|
return false;
|
|
2800
3005
|
}
|
|
2801
3006
|
};
|
|
3007
|
+
|
|
3008
|
+
// src/streaming/inline-streaming.ts
|
|
3009
|
+
var DEFAULT_FORMAT_ANTICIPATION = {
|
|
3010
|
+
inline: false,
|
|
3011
|
+
mathInline: false,
|
|
3012
|
+
mathBlock: false,
|
|
3013
|
+
html: false,
|
|
3014
|
+
mdx: false,
|
|
3015
|
+
regex: false
|
|
3016
|
+
};
|
|
3017
|
+
function normalizeFormatAnticipation(input) {
|
|
3018
|
+
if (input === true) {
|
|
3019
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION, inline: true };
|
|
3020
|
+
}
|
|
3021
|
+
if (!input) {
|
|
3022
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION };
|
|
3023
|
+
}
|
|
3024
|
+
return {
|
|
3025
|
+
inline: input.inline ?? false,
|
|
3026
|
+
mathInline: input.mathInline ?? false,
|
|
3027
|
+
mathBlock: input.mathBlock ?? false,
|
|
3028
|
+
html: input.html ?? false,
|
|
3029
|
+
mdx: input.mdx ?? false,
|
|
3030
|
+
regex: input.regex ?? false
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
function prepareInlineStreamingContent(content, options) {
|
|
3034
|
+
const enableMath = options?.math !== false;
|
|
3035
|
+
const anticipation = normalizeFormatAnticipation(options?.formatAnticipation);
|
|
3036
|
+
const enableInlineAnticipation = anticipation.inline;
|
|
3037
|
+
const enableMathInlineAnticipation = anticipation.mathInline;
|
|
3038
|
+
const enableMathBlockAnticipation = anticipation.mathBlock;
|
|
3039
|
+
const stack = [];
|
|
3040
|
+
const toggleToken = (token) => {
|
|
3041
|
+
const last = stack[stack.length - 1];
|
|
3042
|
+
if (last === token) {
|
|
3043
|
+
stack.pop();
|
|
3044
|
+
} else {
|
|
3045
|
+
stack.push(token);
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
let mathDisplayOpen = false;
|
|
3049
|
+
let mathDisplayCrossedNewline = false;
|
|
3050
|
+
for (let i = 0; i < content.length; i++) {
|
|
3051
|
+
const code = content.charCodeAt(i);
|
|
3052
|
+
if (code === 10 || code === 13) {
|
|
3053
|
+
if (mathDisplayOpen) {
|
|
3054
|
+
mathDisplayCrossedNewline = true;
|
|
3055
|
+
}
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
if (code === 96) {
|
|
3059
|
+
toggleToken("code");
|
|
3060
|
+
continue;
|
|
3061
|
+
}
|
|
3062
|
+
if (code === 126 && i + 1 < content.length && content.charCodeAt(i + 1) === 126) {
|
|
3063
|
+
toggleToken("strike");
|
|
3064
|
+
i += 1;
|
|
3065
|
+
continue;
|
|
3066
|
+
}
|
|
3067
|
+
if (code === 42) {
|
|
3068
|
+
if (i + 1 < content.length && content.charCodeAt(i + 1) === 42) {
|
|
3069
|
+
toggleToken("strong");
|
|
3070
|
+
i += 1;
|
|
3071
|
+
} else {
|
|
3072
|
+
toggleToken("em");
|
|
3073
|
+
}
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3076
|
+
if (enableMath && code === 36) {
|
|
3077
|
+
if (i + 1 < content.length && content.charCodeAt(i + 1) === 36) {
|
|
3078
|
+
toggleToken("math-display");
|
|
3079
|
+
if (mathDisplayOpen) {
|
|
3080
|
+
mathDisplayOpen = false;
|
|
3081
|
+
mathDisplayCrossedNewline = false;
|
|
3082
|
+
} else {
|
|
3083
|
+
mathDisplayOpen = true;
|
|
3084
|
+
mathDisplayCrossedNewline = false;
|
|
3085
|
+
}
|
|
3086
|
+
i += 1;
|
|
3087
|
+
} else {
|
|
3088
|
+
toggleToken("math-inline");
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
const hasIncompleteFormatting = stack.some((token) => token === "code" || token === "strike" || token === "strong" || token === "em");
|
|
3093
|
+
const hasIncompleteMathInline = stack.includes("math-inline");
|
|
3094
|
+
const hasIncompleteMathDisplay = stack.includes("math-display");
|
|
3095
|
+
const hasIncompleteMath = hasIncompleteMathInline || hasIncompleteMathDisplay;
|
|
3096
|
+
if (enableMath && hasIncompleteMath) {
|
|
3097
|
+
if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
|
|
3098
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
3099
|
+
}
|
|
3100
|
+
if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
|
|
3101
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
if (hasIncompleteFormatting && !enableInlineAnticipation) {
|
|
3105
|
+
return { kind: "raw", status: "raw", reason: "incomplete-formatting" };
|
|
3106
|
+
}
|
|
3107
|
+
if (!hasIncompleteFormatting && !hasIncompleteMath) {
|
|
3108
|
+
return { kind: "parse", status: "complete", content, appended: "" };
|
|
3109
|
+
}
|
|
3110
|
+
const appendForToken = (token) => {
|
|
3111
|
+
switch (token) {
|
|
3112
|
+
case "code":
|
|
3113
|
+
return "`";
|
|
3114
|
+
case "strike":
|
|
3115
|
+
return "~~";
|
|
3116
|
+
case "strong":
|
|
3117
|
+
return "**";
|
|
3118
|
+
case "em":
|
|
3119
|
+
return "*";
|
|
3120
|
+
case "math-inline":
|
|
3121
|
+
return "$";
|
|
3122
|
+
case "math-display":
|
|
3123
|
+
return "$$";
|
|
3124
|
+
default:
|
|
3125
|
+
return "";
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
const appended = stack.slice().reverse().map((token) => appendForToken(token)).join("");
|
|
3129
|
+
return { kind: "parse", status: "anticipated", content: content + appended, appended };
|
|
3130
|
+
}
|
|
2802
3131
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2803
3132
|
0 && (module.exports = {
|
|
2804
3133
|
CSP_HEADERS,
|
|
@@ -2843,8 +3172,10 @@ var CustomStreamingMatcher = class {
|
|
|
2843
3172
|
inlineNodesToPlainText,
|
|
2844
3173
|
isLikelyMdxComponent,
|
|
2845
3174
|
normalizeBlockquoteText,
|
|
3175
|
+
normalizeFormatAnticipation,
|
|
2846
3176
|
normalizeLang,
|
|
2847
3177
|
parseCodeFenceInfo,
|
|
3178
|
+
prepareInlineStreamingContent,
|
|
2848
3179
|
removeHeadingMarkers,
|
|
2849
3180
|
sanitizeCodeHTML,
|
|
2850
3181
|
sanitizeHTML,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Block, NodeSnapshot, InlineNode, Patch } from './types.cjs';
|
|
2
|
-
export { ASTInlinePlugin, CoalescingMetrics, CompiledMdxModule, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexInlinePlugin, SetPropsBatchEntry, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.cjs';
|
|
2
|
+
export { ASTInlinePlugin, CoalescingMetrics, CompiledMdxModule, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.cjs';
|
|
3
3
|
export { HighlightedLine, dedentIndentedCode, extractCodeLines, extractCodeWrapperAttributes, extractHighlightedLines, stripCodeFence } from './code-highlighting.cjs';
|
|
4
4
|
export { PerformanceTimer, StringBuffer, applyUpdate, debounce, detectMDX, generateBlockId, getBlockKey, normalizeBlockquoteText, normalizeLang, parseCodeFenceInfo, removeHeadingMarkers } from './utils.cjs';
|
|
5
|
-
export { extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.cjs';
|
|
5
|
+
export { MixedContentAutoCloseHtmlOptions, MixedContentAutoCloseMdxOptions, MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.cjs';
|
|
6
6
|
export { CSP_HEADERS, SanitizationPolicy, createSanitizationConfig, createTrustedHTML, initializeSecurity, initializeTrustedTypesPolicy, sanitizeCodeHTML, sanitizeHTML, sanitizeMathHTML, sanitizeURL } from './security.cjs';
|
|
7
7
|
export { InlineParseOptions, InlineParser, InlineParserOptions, applyASTPlugin, applyRegexPlugin } from './inline-parser.cjs';
|
|
8
8
|
export { sanitizeHtmlInWorker } from './worker-html-sanitizer.cjs';
|
|
9
9
|
export { BackpressureConfig, DEFAULT_BACKPRESSURE_CONFIG, calculateRawCredit, calculateSmoothedCredit, clampCredit, computeHeavyPatchBudget, smoothCredit } from './perf/backpressure.cjs';
|
|
10
10
|
export { CoalesceConfig, DEFAULT_COALESCE_CONFIG, coalescePatches, coalescePatchesLinear, coalescePatchesQuadratic, coalescePatchesWithMetrics } from './perf/patch-coalescing.cjs';
|
|
11
11
|
export { CustomStreamingMatcher, MatchResult } from './streaming/custom-matcher.cjs';
|
|
12
|
+
export { InlineStreamingInlineStatus, InlineStreamingPrepareResult, NormalizedFormatAnticipation, normalizeFormatAnticipation, prepareInlineStreamingContent } from './streaming/inline-streaming.cjs';
|
|
12
13
|
import 'dompurify';
|
|
13
14
|
|
|
14
15
|
declare function cloneBlock(block: Block): Block;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Block, NodeSnapshot, InlineNode, Patch } from './types.js';
|
|
2
|
-
export { ASTInlinePlugin, CoalescingMetrics, CompiledMdxModule, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexInlinePlugin, SetPropsBatchEntry, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.js';
|
|
2
|
+
export { ASTInlinePlugin, CoalescingMetrics, CompiledMdxModule, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.js';
|
|
3
3
|
export { HighlightedLine, dedentIndentedCode, extractCodeLines, extractCodeWrapperAttributes, extractHighlightedLines, stripCodeFence } from './code-highlighting.js';
|
|
4
4
|
export { PerformanceTimer, StringBuffer, applyUpdate, debounce, detectMDX, generateBlockId, getBlockKey, normalizeBlockquoteText, normalizeLang, parseCodeFenceInfo, removeHeadingMarkers } from './utils.js';
|
|
5
|
-
export { extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.js';
|
|
5
|
+
export { MixedContentAutoCloseHtmlOptions, MixedContentAutoCloseMdxOptions, MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.js';
|
|
6
6
|
export { CSP_HEADERS, SanitizationPolicy, createSanitizationConfig, createTrustedHTML, initializeSecurity, initializeTrustedTypesPolicy, sanitizeCodeHTML, sanitizeHTML, sanitizeMathHTML, sanitizeURL } from './security.js';
|
|
7
7
|
export { InlineParseOptions, InlineParser, InlineParserOptions, applyASTPlugin, applyRegexPlugin } from './inline-parser.js';
|
|
8
8
|
export { sanitizeHtmlInWorker } from './worker-html-sanitizer.js';
|
|
9
9
|
export { BackpressureConfig, DEFAULT_BACKPRESSURE_CONFIG, calculateRawCredit, calculateSmoothedCredit, clampCredit, computeHeavyPatchBudget, smoothCredit } from './perf/backpressure.js';
|
|
10
10
|
export { CoalesceConfig, DEFAULT_COALESCE_CONFIG, coalescePatches, coalescePatchesLinear, coalescePatchesQuadratic, coalescePatchesWithMetrics } from './perf/patch-coalescing.js';
|
|
11
11
|
export { CustomStreamingMatcher, MatchResult } from './streaming/custom-matcher.js';
|
|
12
|
+
export { InlineStreamingInlineStatus, InlineStreamingPrepareResult, NormalizedFormatAnticipation, normalizeFormatAnticipation, prepareInlineStreamingContent } from './streaming/inline-streaming.js';
|
|
12
13
|
import 'dompurify';
|
|
13
14
|
|
|
14
15
|
declare function cloneBlock(block: Block): Block;
|